Scene JSON Format
A scene is an entities map. Each entity has optional tags and a required components object. Component keys are the component name (matching the lazy registry); values are the initial data for that component.
Minimum complete scene
That's a complete, loadable scene. Three entities, three components, one camera that follows the player capsule under ambient lighting.
Schema
You never list components or systems separately — the loader discovers both from
the components keys your entities actually use.
What ArcadeGame.loadScene does with this
- Reads the JSON
- Walks
entities, collects every unique key from anycomponentsblock - For each unique name, calls the lazy resolver:
MeshPrimitive→() => import('./Components/MeshPrimitive/MeshPrimitive')ArcCamera→() => import('./Components/ArcCamera/ArcCamera')- …
- Awaits all imports in parallel
- Registers each module's
<Name>Componentand every<Name>…Systemit exports (a component may drive several — e.g. a missile's guidance + impact) with the underlying SceneLoader - Calls
sceneLoader.loadSceneFromData(json)andsceneLoader.instantiateScene(name, world) - Adds the auto-created Systems to the World
The first time a scene references Physics, the Physics module is fetched. The second scene that references Physics reuses the already-cached module. Component names not in the registry produce a console warning but don't throw.
A more realistic scene
A capsule the player controls with WASD, an orbiting camera, a sun, a floor with shadows:
That JSON, with the 5-line main.ts from the installation page, is a complete playable scene.
Pools & blueprints — recycling spawned entities
Bullets, enemy waves, debris — anything you spawn and destroy repeatedly — should be pooled: pre-allocated once and reused, so firing never churns entities or meshes. You declare a pool by putting a Pool marker component on a scene entity. That entity becomes a blueprint: its other components are the template each pooled instance carries.
At load, PoolSystem reads the Pool marker, pre-builds size parked copies of the blueprint's other components, then consumes the blueprint entity. A spawner brings one into play with world.acquire("Bullets") — which returns the entity, so the spawner can set its spawn pose:
Or, fully decoupled, fire a pool.spawn event ({ pool: "Bullets", x, y, z }) and read pool.spawned for the new entity id. A spent slot is returned with world.removeEntity(bullet) — a pooled entity is recycled, not destroyed — or a pool.release event.
Why a blueprint, and not a nested config
The point of putting the template in the scene is system registration. ArcadeGame starts a component's System only when it sees that component keyed on a scene entity. A pooled Bullet is never authored directly — it only ever exists inside the pool — so if its type appeared nowhere in the scene, BulletSystem would never start, and your bullets would spawn at the muzzle and just hang there, dead.
Because a blueprint's components (Bullet, MeshPrimitive, …) are ordinary top-level scene keys, their Systems register through the normal path. Pooled instances come alive with no extra wiring — and no "empty marker" entities.
Fields
| Field | Default | Meaning |
|---|---|---|
Pool.size | 16 | Slots to pre-allocate. Caps concurrent live instances; the oldest is recycled past this. |
Pool.name | the blueprint entity's id | The name spawners pass to world.acquire(name). Set it only to decouple the pool name from the entity id. |
On acquire
Each acquire resets the slot before it re-enters play: its tags snap back to exactly the blueprint's, every declared component's config is re-applied (so HP / speed / lifetime return to the blueprint values), a declared MeshPrimitive proxy is moved to the spawn {x, y, z}, and pool.acquired fires so the spawner can apply per-spawn dynamics. Runtime state the config didn't set (mesh handles, timers) is left intact.
Per-component data shapes
Each component's data shape mirrors its public fields. See the component-specific sections of these docs:
- MeshPrimitive — primitive kind, dimensions, position
- Input & Movement —
KeyboardMover,PlayerInput,Movement - Cameras —
ArcCamera,CameraFollow - Lighting & Shadows —
DirectionalLight,HemisphericLight,Shadow - Physics — shape, motion, mass, locks
- Score & UI —
Score,Scoreboard - Animation & Mesh —
.glbloading and animation playback
Referencing other entities
Some components hold a reference to another entity — by name, not by component:
The string "Player" resolves to whichever entity in the same scene is named "Player". The cross-reference is established when the scene instantiates — the camera's System looks up the target the first frame it ticks.
If the target doesn't exist yet at instantiation time (e.g., dynamically spawned), the camera will fall back to its current pose until the target appears, then snap.
Tags
Tags are first-class in System queries — KeyboardMover for example uses the 'player' tag to know which entity to drive. Don't omit them when the docs say a component requires them.
What's not in JSON
- Functions / callbacks — Components are data only. Behavior lives in the System; the JSON only sets the data.
- Live entity references — use entity names as strings; the runtime resolves them.
- Renderer handles — Systems create those at runtime; you don't author them.
Where to next
- ArcadeGame walkthrough — the loader internals
- MeshPrimitive — the most-used component
