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.