Scenes & Entities¶
A scene is the container for everything that exists in a part of your game: entities, cameras, lights, physics, scripts, and the systems that update them each frame. A game is typically made of one or more scenes that you create, configure, and switch between at runtime.
Scenes¶
A scene owns a collection of entities and drives their updates. Internally, Scene
extends EntityRegistry, so it owns entity allocation, component storage, and subsystem
registration. You create a scene, populate it with entities, and tell the engine to run
it.
You can have several scenes loaded at the same time. The most common pattern is one main scene for gameplay with additional scene layers for overlays, menus, or transition effects.
| Operation | C++ API |
|---|---|
| Set the main scene | Engine::setScene(scene) |
| Add an overlay/layer | Engine::addSceneLayer(scene) |
| Execute once and discard | Engine::executeSceneOnce(scene) |
| Remove one layer | Engine::removeScene(scene) |
| Clear all scenes | Engine::removeAllScenes() |
For project-level scene switching, use SceneManager to register scene factories and
load scenes by name or numeric ID:
That form registers a stack that can be loaded. To also overlay it with
addChildScene, pass a second factory that only creates the scenes — see
registerScene. Scenes registered by
the editor always get both.
See Switching scenes with SceneManager for full scene transitions and on-demand overlays at runtime.
Scene types¶
All scenes use the same Scene class and ECS foundation at runtime. The scene type
is an authoring concept that tells the editor which defaults, camera setup, and tools to
use.
| Type | Editor defaults | Typical use |
|---|---|---|
| 3D | Perspective camera, 3D gizmos, lighting setup | Open-world levels, first/third-person games, 3D platformers |
| 2D | Orthographic camera, canvas gizmos, 2D tools | Top-down games, side-scrollers, puzzle games, tilemaps |
| UI | Screen-space canvas, anchor gizmos | Menus, HUDs, settings screens, overlays |
A 3D scene can contain UI entities, and a UI scene can reference 2D or 3D child scenes. This compositing model is the foundation for in-game HUDs: create a separate UI scene and add it as a scene layer on top of the gameplay scene.
-- Load a HUD scene on top of the main gameplay scene
Engine.setScene(gameplayScene)
Engine.addSceneLayer(hudScene)
Entities¶
An entity is a lightweight integer identifier that lives in a scene. By itself it holds no behavior or data; it gains both by having components attached to it. This is the core of the Entity Component System.
Higher-level objects in Doriax — Sprite, Model, Camera, Button, etc. — are
convenience wrappers. They create or reference an entity and manipulate the components
it needs for you.
scene = Scene()
triangle = Polygon(scene)
triangle.position = Vector3(300, 300, 0)
triangle:setColor(0.6, 0.2, 0.6, 1)
Transforms¶
Most visible entities have a Transform component that defines their position, rotation, and scale in space. Transforms can be parented to build hierarchies — moving a parent moves all its children with it.
entity.position = Vector3(300, 300, 0)
entity.rotation = Quaternion(0, 0, 0)
entity.scale = Vector3(1, 1, 1)
When an entity has a parent, its world transform is derived from the full parent chain. Local transforms are relative to the parent; world transforms are absolute.
Entities without transforms¶
Not every entity needs a Transform. Non-transform entities are useful for:
- Logic-only scripts and global state
- Non-spatial (2D panned) audio
- Scene environment components like Fog or Skybox
- Physics joints that link two body entities
- Actions and animations targeting another entity
In the editor's Structure Panel, these entities appear in a separate area above the transform hierarchy. They can be selected and edited but cannot be parented until a Transform is added.
Scene settings¶
Every scene owns rendering and interaction state that affects all entities within it:
| Setting | Purpose |
|---|---|
| Active camera | The Camera entity used for rendering |
| Background color | Clear color for the scene |
| Shadow quality | PCF filtering level for smoother shadow edges (NONE to HIGH) |
| Light state | OFF, ON, or AUTO (auto-detects lights in the scene) |
| Global illumination | Ambient light color and intensity |
| Ambient occlusion (SSAO) | Screen-space ambient occlusion — enable plus radius, intensity, and bias — see Rendering Pipeline |
| Reflections (SSR) | Screen-space reflections — enable plus distance, thickness, intensity, and glossy blur — see Rendering Pipeline |
| Ambient light (2D) | Flat ambient color and intensity for 2D scenes, with its own 2D shadow quality — see 2D lighting |
| Physics gravity | Per-world gravity in m/s² — 2D (Box2D) and 3D (Jolt) are independent — see Physics |
| Fixed resolution | Render the main scene at a fixed internal size and upscale it to the window — see Multiple Resolutions |
| UI events | Enables/disables UI input processing for the scene |
| Default shaders | Per-type custom shaders (Mesh, UI, Sky, Points, Lines) used by every component of that type without its own custom shader — see Custom Shaders |
| Canvas size | Logical resolution for 2D and UI scenes |
Edit these in the editor by selecting the scene root in the Structure panel with no entity selected; the settings appear in the Properties window. Which sections are shown depends on the scene type — 2D and UI scenes hide 3D-only options such as SSAO and SSR. As with component fields, any setting changed from its default shows a small reset arrow next to its label that restores the default, and every change is saved with the scene and undoable.
Scene stacks¶
When the engine runs, every loaded scene lives in an ordered list of layers, and a group of scenes that are loaded together is called a scene stack:
- The main scene is always the bottom layer. There is exactly one per stack.
- Each additional scene is a layer drawn on top of the layers before it.
- The last layer is the topmost — it renders over everything below it.
In the editor, each scene file is the main scene of its own stack, and you attach other
scenes to it as child scenes. At export, the editor turns each stack into a factory
that calls Engine::setScene() for the main scene and Engine::addSceneLayer() for each
child, then registers it with SceneManager.
Child scenes¶
A child scene reference lets one scene load and run another as part of the same stack. Child scenes share the same engine tick but keep their own entity and component storage, so they stay cleanly separated. Use them for:
- Persistent UI — a HUD or menu scene that stays loaded across gameplay level changes.
- Shared setup — a single lighting and skybox scene included in every level.
- Additive worlds — large open worlds assembled from independently-authored chunks. Bodies in different chunks don't collide (see the note below).
- Scene streaming — load and unload sections without a full scene transition.
- Pop-ups and pause menus — UI that appears on demand and is removed without reloading the level.
Each scene has its own physics world
Every scene runs its own 2D and 3D physics world. A body in one scene never collides with a body in another scene. A ray cast only tests the scene you pass it, and a joint connects only bodies of its own scene. Keep everything that must touch physically in one scene, and use child scenes for parts that don't, such as a HUD, lighting or menus. If chunks of an additive world have to interact, see Physics across scenes.
Entity IDs are also numbered per scene. That is why the editor only lets you reference a child scene's entity from a script property.
Adding a child scene in the editor¶
Select the parent scene in the Structure panel and either:
- right-click the scene root and choose Add child scene → SceneName, or
- drag a
.scenefile from the Resources Browser onto the scene root.
A child scene can have child scenes of its own, but a scene can't contain itself, directly or through other child scenes. The editor refuses an add that would create such a cycle.
Child scenes are listed above the parent's entities. Click the eye icon next to a child scene to load it inline, so you can see and edit its entities in context — this is an editor convenience and does not change runtime behavior.
Start active¶
Each child scene reference has a Start active flag (right-click the child scene node → Start active). It controls whether the child scene is added to the engine automatically when its parent loads:
| Start active | Behavior when the parent loads | Use it for |
|---|---|---|
| On (default) | The child scene is created and added as a layer. It runs and renders immediately. | HUDs, persistent overlays, shared lighting — anything always present. |
| Off | The child scene is still created and prepared (its entities, scripts, and Scene* are all ready), but it is not added to the engine. |
Pause menus, dialogs, level sections — anything you reveal later. |
A child scene that starts inactive is ready but hidden. Because it is already built,
showing it later with SceneManager.addChildScene
is instant — there is no loading cost at the moment it appears.
Start active is not what makes a scene addable at runtime; any registered scene can be overlaid, and one that is not a child scene is simply built the first time you add it. What the flag controls is whether the scene comes up with its parent, and inactive children are the ones already paid for. See Preloaded or built on demand.
Scene order¶
The order of the layers follows the order the child scenes are listed under their parent in the Structure panel, which is the order you added them. The main scene stays at the bottom; each child scene draws on top of the ones above it in the list. Put a HUD or menu scene last so it renders over the gameplay below it.
To change the order, remove the child scenes and add them back in the sequence you want —
the one added last ends up on top. At runtime, SceneManager.addChildScene always adds a
scene on top of the current layers, so re-adding is also how you bring a layer to the
front.
Switching scenes with SceneManager¶
SceneManager is the runtime entry point for
moving between scenes. The editor registers every scene stack for you at startup, so from
a script you refer to scenes by their name (or numeric ID) and never touch the
factory functions directly. There are two ways to change what is on screen.
Migrate to another scene¶
SceneManager.loadScene performs a full transition: it removes every currently loaded
scene (Engine::removeAllScenes() — main and all layers), then builds the requested
stack from scratch and runs it. Use it to move between levels, menus, and game-over
screens.
One stack at a time
loadScene clears everything first, so you do not call it once per layer. Attach
persistent layers (a HUD, shared lighting) as start-active child scenes of the
target scene instead — they come up with it in a single loadScene call.
Called from a running scene
From a script, a physics contact or an input event the transition happens at the start
of the next frame, so the calling scene and its scripts finish what they are doing
intact; SceneManager::isLoadPending() is true until then. See
SceneManager.
Show a loading screen¶
Building a large scene takes a moment. Pick a UI scene as the Loading Scene in
Project Settings → General → Loading and
loadScene shows it over the old scene, replaces the old scene behind it, and removes it
once the new scene has loaded. From code:
SceneManager.loading and SceneManager.loadingProgress drive its animation. Turn on
async loading so it keeps animating while
textures and sounds stream in. A level starts its gameplay in
Engine.onSceneLoaded, which fires once it
has loaded, still behind the loading scene.
Overlay a scene without leaving the current one¶
SceneManager.addChildScene and removeChildScene add or remove a scene on top of
what is already running, with no transition. This is how you show a pause menu, dialog, or
HUD toggle over live gameplay. Any registered scene can be overlaid — it does not have to
belong to the loaded stack. If its scenes have not been built yet, addChildScene builds
them before showing them.
-- Open a pause menu over the running game
function onPause()
if SceneManager.addChildScene("PauseMenu") then
Engine.pauseGameEvents(true) -- freeze gameplay updates
end
end
-- "Resume" button handler inside the PauseMenu scene
resumeBtn = Button(scene)
resumeBtn.label = "Resume"
local btnComp = resumeBtn:getButtonComponent()
btnComp.onPress = function()
SceneManager.removeChildScene("PauseMenu")
Engine.pauseGameEvents(false)
end
// Open a pause menu over the running game
if (SceneManager::addChildScene("PauseMenu")) {
Engine::pauseGameEvents(true);
}
// "Resume" button handler inside the PauseMenu scene
resumeBtn.getComponent<ButtonComponent>().onPress = []() {
SceneManager::removeChildScene("PauseMenu");
Engine::pauseGameEvents(false);
};
Because addChildScene puts the scene at the top of the layer list, the overlay always
draws above the gameplay below it. removeChildScene takes it back out and leaves the
main scene and other layers untouched — it does not destroy the scene, so adding it again
brings it back exactly as you left it, scroll position and all.
addChildScene is also safe from a script's init while that script's own stack is
still loading — a level that opens its HUD from code. The stack it adds survives the
load; only scenes no running stack involves are torn down by the transition.
Preloaded or built on demand¶
Because addChildScene can build a stack itself, you choose when a scene pays for its
own setup:
| How the scene is set up | Built | First time it is shown |
|---|---|---|
| Child scene, Start active → On | With its parent | Already on screen |
| Child scene, Start active → Off | With its parent | Instant — it is ready and waiting |
| Not a child scene at all | On the first addChildScene |
Pauses to create entities and start scripts |
Keep something you toggle often — a pause menu, a HUD panel — as an inactive child, so showing it costs nothing. Leave a heavy screen you may never open — a full inventory, a map, an end-of-game summary — out of the child list, so a session that never opens it never pays for it.
Cross-scene references need the scene to exist
If a script in one scene references entities in another (see SceneManager.setScenePtr), the referenced scene must be built when that script initializes. Keep it a child scene — active or inactive — so it comes up with its parent. A scene built on demand does not exist yet when the parent's scripts start.
Knowing where you are¶
SceneManager also reports the current state, which is handy for transition logic and
save systems:
| Query | Returns |
|---|---|
SceneManager.getCurrentSceneName() |
Name of the most recently loaded scene |
SceneManager.getCurrentSceneId() |
ID of the most recently loaded scene |
SceneManager.getSceneNames() |
All registered scene names |
SceneManager.getSceneId(name) |
Numeric ID for a name (0 if unknown) |
SceneManager.loading |
true from loadScene until the new scene has loaded and its loading scene has closed |
See the SceneManager reference for the complete API.
Creating UIs as scenes¶
User interfaces fit the child-scene model naturally. The recommended pattern is to author each interface — HUD, main menu, pause menu, settings — as its own UI scene, then bring it on screen as a layer:
- Create a UI scene and add widgets to it
(
Panel,Button,Text, and so on). - Add it as a child scene of the gameplay scene it belongs to.
- Choose its Start active flag:
- On for a permanent HUD that should appear with the level.
- Off for a menu or dialog you open later with
SceneManager.addChildScene.
- Keep the UI scene last in the child list so its widgets draw above the game.
Because a UI scene is screen-space and self-contained, you can swap menus without touching gameplay data and load or unload interfaces independently. For widget construction, anchoring, events, and canvas scaling, see the User Interface manual and the First UI Scene tutorial.
Bundles¶
Bundles are reusable entity hierarchy templates — similar to prefabs in other engines. They let you define a configured group of entities once and place the same hierarchy in any number of scenes.
How bundles work¶
A bundle is authored visually in the editor (or defined in script) and saved as a YAML
file. During export, the bundle is converted into a factory function registered with
BundleManager. At runtime, calling createBundle runs the factory and returns the
root entity of the new hierarchy.
Unlike simple duplication, bundles:
- Are a single source of truth — editing the bundle definition updates every scene that references it.
- Are shared across scenes — the same bundle can be instantiated in many scenes without duplicating authored data.
- Can be created and destroyed at runtime — useful for enemies, projectiles, UI cards, and any pooled or on-demand hierarchy.
Creating a bundle in the editor¶
- Build the entity hierarchy you want to reuse.
- In the Resources Browser, open the folder the bundle should be saved to.
- Right-click the root entity in the Structure panel and choose Save as Bundle (dragging the entity onto the Resources Browser does the same thing).
The editor writes <EntityName>.bundle into that folder and replaces the hierarchy in
the scene with an instance of it; rename the file in the Resources Browser to change the
bundle name. Bundles can live in any project folder — bundles/ is just a convention.
See Bundles for the full editor workflow.
Using a bundle at runtime¶
Bundle registration is generated automatically by the export step (it emits a C++
registerBundle call that wires the factory to the bundle name/ID). At runtime you
mainly call createBundle and destroyBundle. Note that createBundle takes the bundle
name first, then the scene, and returns the root Entity. Entity IDs are
scene-local: to parent the instance under an existing entity, look it up in the destination
scene with createBundle(name, scene, "parentName"), or pass an object that already carries
its scene. Every call creates its own root, so a bundle can be spawned as many times as you
want. Member entities are created when the factory runs, so their ids differ between the
editor and a build; reach them by name under the instance root with
findEntity(name, root) rather than
by id.
A spawned instance runs its scripts like the scene does: Lua init() and C++ constructors
run before createBundle returns. destroyBundle stops them, then removes the instance along
with anything parented under it since, bundles spawned there included. A C++ script that
destroys its own bundle is deleted during that call, so it must return without touching its
members.
There is no direct .bundle file loader
You don't load a .bundle file by path at runtime. Export turns each file into a
registered factory, and you spawn it by the bundle name — the file's path with the
.bundle extension removed and forward slashes (so bundles/enemies/EnemyShip.bundle
is created as "enemies/EnemyShip"). Only the bundles a scene instantiates and the
ones listed in Project → Bundles are registered, so a bundle spawned from script
alone has to be listed there. Use BundleManager.getBundleNames() to list everything
that is registered.
-- Create an instance (name first, then scene)
local root = BundleManager.createBundle("enemy_grunt", scene)
-- Attach under a named entity in that scene
BundleManager.createBundle("enemy_grunt", scene, "spawn")
-- Destroy it later (scene first, then root entity)
BundleManager.destroyBundle(scene, root)
// Registration is normally emitted by export, e.g.:
BundleManager::registerBundle(1, "enemy_grunt",
[](Scene* scene, Entity parent) -> bool {
// factory body
return true;
});
Entity root = BundleManager::createBundle("enemy_grunt", &scene);
BundleManager::createBundle("enemy_grunt", &scene, "spawn");
// Destroy when done
BundleManager::destroyBundle(&scene, root);
See BundleManager for the full API reference.
Next steps¶
- Entity Component System — how data and behavior are organized under the hood.
- Scripting — add gameplay logic to entities.
- Project Workflow — how to manage scenes and bundles in the editor.