3.8 KiB
Scene System
Source: src/dusk/scene/
Overview
The scene system is the top-level coordinator for a running game state.
It manages one active scene at a time. Scenes are JS scripts -- each
scene is a .js asset file that exports an object with lifecycle hooks.
The scene system loads, ticks, and tears down these scripts, while the
C side runs the ECS and render pipeline on each tick.
Scene lifecycle (C side)
extern scene_t SCENE;
errorret_t sceneInit(void); // initialise the scene manager
errorret_t sceneUpdate(void); // process pending transition, tick active scene
errorret_t sceneRender(void); // render entities + render pipeline + UI
errorret_t sceneDispose(void); // dispose the active scene immediately
sceneUpdate each tick:
- Checks for a pending scene transition and performs it (dispose old, load and init new).
- Calls the JS scene's
update()hook. - Calls
entityManagerUpdate()to fire all entity update callbacks.
sceneRender each tick:
- Binds the screen.
- Calls
sceneRenderPipeline()-- renders all entities with aCOMPONENT_TYPE_RENDERABLEin priority order. - Renders UI.
- Calls the JS scene's
render()hook (for any custom drawing). - Unbinds the screen.
Scene lifecycle (JS side)
A scene file exports a plain object with these optional hooks:
var scene = {};
scene.init = async function() {
// Load assets, create entities, set up state.
// May be async -- await asset loads here.
};
scene.update = function() {
// Called each fixed-timestep tick.
};
scene.render = function() {
// Called each render tick, after ECS renderables.
};
scene.dispose = function() {
// Clean up entities and state.
};
module.exports = scene;
See CLAUDE.md -- "JavaScript (asset scripts)" for JS style rules.
Render pipeline (scenerenderpipeline.h)
sceneRenderPipeline(cameraEntityId) gathers all active
COMPONENT_TYPE_RENDERABLE components, sorts them by effective
priority, and draws each one using its shader.
Priority rules:
renderable.priority != 0-- use that value directly.renderable.priority == 0-- auto-derive: opaque geometry sorts before transparent geometry; sprite batches sort before shader materials; etc.- Lower priority number = drawn first (behind); higher = drawn last (on top).
The shader used for each renderable:
ENTITY_RENDERABLE_TYPE_SPRITEBATCHandCUSTOMdefault toSHADER_LIST_SHADER_UNLIT.ENTITY_RENDERABLE_TYPE_SHADER_MATERIALuses the shader indexed byrenderable.data.material.shaderTypeinSHADER_LIST_DEFS.
Transitioning between scenes
Scene transitions are handled entirely in JS via the Scene global.
The Scene object is a singleton with:
// Switch to a new scene. Calls dispose() on the current scene, then
// init() on the new one. Both happen synchronously this tick.
Scene.set(newSceneObject);
// The current scene object (may be null):
Scene.current
Typical scene-switch pattern:
// Inside a scene's update or event handler:
const nextScene = require("scenes/gameplay.js");
Scene.set(nextScene);
Scene.set is synchronous -- it calls dispose on the old scene and
init on the new scene before returning. If init needs async work
(loading assets), use an async function and await inside init:
nextScene.init = async function() {
await batch.load(); // wait for assets before proceeding
};
The C side does not defer the transition; the switch happens inside
the current sceneUpdate call.
Relationship to the engine loop
engineUpdate()
timeUpdate()
inputUpdate()
physicsManagerUpdate()
scriptUpdate() <- runs JS microjobs
sceneUpdate() <- JS update + ECS entity updates
engineUpdate() -> sceneRender()
screenBind()
sceneRenderPipeline() <- ECS renderables sorted by priority
uiRender()
sceneRender (JS hook)
screenUnbind / screenRender