Runtime¶
The runtime layer provides the scene graph (Scene + Node), transforms, and update/render traversal.
Scene¶
A Scene:
- Owns the root node, the root UI, and the active camera.
- Creates nodes via a shared NodeRegistry.
- Updates all Updatable components each frame, then runs LateUpdatable components.
- Draws all Drawable components each frame.
- Updates UI elements and renders them after the scene graph.
- Queues node destruction for safe cleanup.
createNode() registers a scene-owned node but does not add it to the scene
tree. Parent every created node with addChild(node),
rootNode()->addChild(node), or another node's addChild(node) before relying
on update or render traversal.
Key methods:
- createNode(name)
- findNode(name)
- destroyNode(node)
- destroyUIElement(element)
- updateComponents(dt) and render(queue)
- lateUpdateComponents(dt)
- updateUI(dt) and rootUI()
- screenToWorld() and worldToScreen()
- plotNodeToUI() and plotUIToNode() for mapping scene and UI positions
- mouseToWorld()
- lighting() for ambient and directional model lighting
Node¶
A Node is a tree element with:
- A Transform component built-in.
- A collection of custom components.
- A list of children nodes.
Components can be added with addComponent<T>(). If a component implements Drawable it is rendered. If it implements Updatable it receives per-frame updates. If it implements LateUpdatable, it receives a second pass after all regular component updates and physics stepping.
Use getComponent<T>(), removeComponent(pointer), or
removeComponents<T>() to query or remove attached components. isActive on
either a node or component suppresses its runtime participation. Scene-owned
nodes should be destroyed with Scene::destroyNode() so destruction occurs at
the safe end-of-frame point. removeChild() and clearChildren() only detach
nodes; they do not destroy the registry-owned nodes. A later addChild() may
reattach a detached node or reparent an attached one.
auto* group = createNode("group");
addChild(group); // Attach to this scene's root.
auto* item = createNode("item");
group->addChild(item);
auto* renderer = item->addComponent<snowpulse::SpriteRenderer>();
if (item->getComponent<snowpulse::SpriteRenderer>() == renderer) {
item->removeComponent(renderer);
}
group->removeChild(item);
rootNode()->addChild(item);
destroyNode(item); // Deferred until the safe cleanup point.
Frame Traversal Order¶
Per frame, runtime traversal order is:
Scene::update(dt)(application scene logic)Scene::updateComponents(dt)(Updatable+ physics step)Scene::lateUpdateComponents(dt)(LateUpdatable)Scene::updateUI(dt)EventBus::dispatchQueued()- Deferred node and UI-element destruction
Transform¶
Transform provides position, rotation, scale, and cached matrices:
- localMatrix and worldMatrix are updated by Scene::updateWorldTransforms().
Coordinate Conversion and Lighting¶
screenToWorld(), worldToScreen(), and mouseToWorld() use the active
camera. plotNodeToUI() converts a node's world position into the coordinate
space used by the scene UI, while plotUIToNode() maps an element's computed
world pivot back into scene coordinates.
const glm::vec2 worldPoint = scene()->screenToWorld({ 320.0f, 180.0f });
const glm::vec2 screenPoint = scene()->worldToScreen(worldPoint);
const glm::vec2 pointerWorld = scene()->mouseToWorld();
const glm::vec2 labelPosition = scene()->plotNodeToUI(node);
const glm::vec2 elementWorld = scene()->plotUIToNode(element);
Each scene owns a SceneLighting value:
auto& lighting = scene()->lighting();
lighting.ambientColor = { 0.75f, 0.82f, 1.0f };
lighting.ambientIntensity = 0.5f;
lighting.directionalDirection = { -0.35f, -0.5f, -0.8f };
lighting.directionalColor = { 1.0f, 0.92f, 0.78f };
lighting.directionalIntensity = 0.8f;
Lighting is consumed by model rendering. Normal 2D sprite and UI batches keep their existing unlit shader behavior.
UI Root¶
Scene creates a UIRoot when a UIElementRegistry is present in AppContext. Add UI elements to scene()->rootUI().
auto* ui = context()->uiRegistry();
auto* root = scene()->rootUI();
auto* label = ui->create<snowpulse::UIText>("status");
label->font(assetManager()->loadFont("fonts/04b_19.json"));
label->text = "Ready";
root->addChild(label);
Sample Usage¶
class MyScene : public snowpulse::Scene {
public:
bool init() override {
auto* visual = createNode("visual");
auto* sprite = visual->addComponent<snowpulse::SpriteRenderer>();
sprite->sprite(appContext()->assetManager()->loadSprite("images/icon.png"));
addChild(visual);
return true;
}
void update(const float& dt) override {
(void)dt;
}
void shutdown() override {}
};
Related Docs¶
For map-centric scenes, attach TiledRenderer to a node and keep application
spawning and binding in application-side systems (for example, navigation or
trigger binders).