qproj eye icon qproj wiki

Screens

Claude Opus 5.5, 3 Nov. 2026

Engine: q_os and libraries

A screen is a self-contained part of a game, such as a splash screen, a level or a development sandbox. It corresponds to a scene or a room in other engines. The q_screens crate gives each screen a load–ready–unload lifecycle. When the game switches screens, it removes the previous screen’s entities. Both of qproj’s games are built on it: Quell and the visual novel vn-jun-26.1

The name is unrelated to Bevy’s Scene, which is serialized entity data. Quell’s screens often spawn Jackdaw scenes.

Contents

Defining a screen

A screen is a component type implementing the Screen trait. Its builder adds systems to screen-specific schedules, and the app registers it by type:

impl Screen for WorldScreen {
    fn builder(mut b: ScreenScopeBuilder<Self>) -> ScreenScopeBuilder<Self> {
        b.add_systems(ScreenSchedule::Loading, load_world);
        b.add_systems(ScreenSchedule::OnReady, init);
        b.add_systems(ScreenSchedule::Update, player_systems().take());
        b
    }
}

Per-frame schedules (Update, FixedUpdate, Loading and Unloading) run only while the screen is in the corresponding state. Transition schedules (OnLoad, OnReady, OnUnload and OnUnloaded) run once when the state changes. Screens are registered by type, and they can also be looked up by name. Quell’s --screen option relies on this.

Lifecycle

flowchart LR
  U["Unloaded"] --> Q["LoadQueued"]
  Q --> L["Loading"]
  L --> R["Ready"]
  R --> UL["Unloading"]
  UL --> C["Cleanup"]
  C --> U

A newly requested screen waits in LoadQueued until the previous one has finished unloading. If a screen has Loading systems, one of them must call finish_loading() before the screen becomes ready. Quell’s world screen does this after its player model has loaded. Screens without such systems skip straight through. By default, Update systems wait until the screen is Ready. A non-blocking load strategy lets them run during loading as well.

The screen lifecycle is the game’s loading state machine. Services hook into it, rather than adding loading states of their own.

What gets cleaned up

On cleanup, q_screens despawns every entity, except those it is told to keep:

Cleanup goes entity by entity, so it does not follow hierarchies. Marking a parent Persistent does not save its children; Propagate(Persistent) does. This is the most common surprise. A camera, UI root or console terminal spawned at start-up disappears at the first screen change unless it is marked. Quell’s UI camera and console are marked persistent for this reason.

ScreenScoped works in the other direction. It overrides Persistent, so a child under a propagated Persistent can still be cleaned up. Quell also marks most of its gameplay entities ScreenScoped. This is redundant, but it makes their scope explicit.

See also

  1. 1Checked against lib/q_screens/src/ (version 0.2). The API is documented in the crate’s Rust docs and the q-screens agent skill.