Scene Management

A Turian scene can be instantiated as a node tree (like a Godot scene or a Unity prefab). The Scene Manager adds the other half — treating scenes the way Unity's SceneManager does: named, addressable units that are loaded and unloaded as a whole, with additive scenes, persistent objects, async loading, and lifecycle events.

The boot scene comes from your [Project Settings]({{< ref "project-settings" >}}) (first_scene); from there your game drives scene transitions through the engine.SceneManager service.


Accessing the manager from a script

The manager is injected as a service, reachable from any component's Frame:

const engine = @import("engine");

pub const SceneDirector = struct {
    pub const is_component = true;

    // Wired in the inspector, not hard-coded — see "Scene references as fields".
    level_a: engine.TypedAssetRef(.scene) = .{},
    level_b: engine.TypedAssetRef(.scene) = .{},

    pub fn start(self: *SceneDirector, frame: engine.Frame) void {
        const mgr = frame.service(engine.SceneManager) orelse return;
        // Keep the bootstrap scene (camera/director) alive across level swaps.
        if (mgr.getActiveScene()) |boot| mgr.setScenePersistent(boot, true);
        mgr.requestLoad(self.level_a.guid(), .additive);
    }

    pub fn update(self: *SceneDirector, frame: engine.Frame) void {
        const mgr = frame.service(engine.SceneManager) orelse return;
        if (frame.input.wasKeyPressed(.num_2)) mgr.requestLoad(self.level_b.guid(), .single);
    }
};

Scene references as fields

Rather than pasting scene GUIDs into your code, declare an engine.TypedAssetRef(.scene) field. The inspector renders a scene-asset picker for it (drag a scene from the asset browser, or use the ... button to choose from a list filtered to scenes). The selected GUID is saved into the scene file and hydrated back into the field at runtime, so a script reads it with field.guid():

next_scene: engine.TypedAssetRef(.scene) = .{},

pub fn update(self: *@This(), frame: engine.Frame) void {
    const mgr = frame.service(engine.SceneManager) orelse return;
    if (frame.input.wasKeyPressed(.space))
        mgr.requestLoad(self.next_scene.guid(), .single);
}

This is the same typed-asset-reference mechanism used for meshes and materials, specialised to scenes — designers wire transitions without touching code.

Load modes

Mode Behaviour
.single Unloads every non-persistent scene, then loads the new one — the classic "next level" transition.
.additive Loads the scene alongside the ones already loaded.

Persistent scenes (DontDestroyOnLoad)

mgr.setScenePersistent(handle, true) marks a scene to survive .single loads. Put your camera, HUD, and controller in a persistent bootstrap scene and they outlive every level transition — the Scene Manager equivalent of Unity's DontDestroyOnLoad. (Individual nodes can also be moved into a dedicated persistent scene with markDontDestroyOnLoad.)

Deferred requests

Scripts request scene changes with requestLoad / requestUnload. These are applied at the end of the frame, never mid-update — so a script can safely unload the very scene it is running in. (loadScene / unloadScene are also available for immediate, host-side control.)

Tracking & queries

const active = mgr.getActiveScene();          // ?SceneHandle
var buf: [engine.SCENE_MANAGER_MAX_SCENES]engine.SceneHandle = undefined;
const loaded = mgr.getLoadedScenes(&buf);     // []SceneHandle
const here = mgr.findById(LEVEL_A);           // ?SceneHandle
const ok = mgr.isLoaded(handle);

Lifecycle events

Subscribe to be notified when scenes change state:

mgr.subscribe(onSceneEvent, ctx); // fires loaded / unloaded / activated / deactivated

Async loading

Large scenes can load on a worker thread without blocking the frame:

const h = try mgr.loadSceneAsync(io, id, .single);
// ... keep rendering a loading screen ...
if (mgr.isReady(h)) _ = mgr.finishAsync(io, h, .single);

Error handling

Synchronous loads return typed errors (NoLoader, LoadFailed, TooManyScenes); for async loads the per-scene reason is available via mgr.getError(handle).

Example

See examples/scene-management/ in the repository for a complete, runnable demo of boot + persistent bootstrap + additive load + runtime switching.


← All docs Edit on GitLab