Scene DSL
Build scenes in Kotlin: nested entity blocks that create ECS entities, give them a transform and attach components.
module: com.awakekt.awake.scene:authoring
Desktop · Android · iOS · Web
The scene DSL and a scene document are two ways to describe the same ECS content. Both tabs below build the same Harbor Town entities; the scene document is the form AwakeKt Studio saves.
Build a scene
Each entity creates one entity. Give it a name, a transform, and components; an entity inside
another is its child, and its transform is relative to the parent.
{
"version": 1,
"name": "Harbor Town",
"nodes": [
{
"name": "dock",
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 4.0 },
"scale": { "x": 6.0, "y": 0.2, "z": 2.0 }
}
},
{
"name": "lighthouse",
"transform": {
"position": { "x": 3.0, "y": 0.0, "z": -2.0 }
},
"children": [
{
"name": "beacon",
"transform": {
"position": { "x": 0.0, "y": 6.0, "z": 0.0 }
},
"components": [
{ "component": "spin_control", "speed": 0.5 }
]
}
]
}
]
}
- In the Hierarchy, click Add entity and choose Structure: Empty Entity.
- In the Inspector, set its Name, then its Position, Rotation and Scale under Transform.
- For the beacon, open Add component and choose Spin Control, then set Speed.
File > Save Scene writes the scene document in the first tab.
world.scene { } runs immediately on a World you own. It is what tests, tools and the samples on
this site use.
Two entry points
| Entry point | Where | When it runs | Use it for |
|---|---|---|---|
world.scene { … } |
any World |
immediately | Tests, tools, building content into a world you manage. |
app { scene("name") { … } } |
inside app { } |
when the renderer is ready | A game: the scene runtime owns the World, runs your systems and renders. |
app { ecs { … } } is the same as app { scene { … } } without a name.
Inside app { scene { } }, entity blocks are collected and run once the renderer is ready, next to
the systems they need:
val game = app {
scene("harbor-town") {
entity("lighthouse") {
transform(x = 3f, z = -2f)
entity("beacon") {
transform(y = 6f)
spin(speed = 0.5f)
}
}
frameSystem("beacon-spin") { BeaconSpin() } // advances the angle
frameSystem("spin") { SpinSystem() } // writes it to Transform.rotation.y
}
}
BeaconSpin is an ordinary ECS system:
class BeaconSpin : System {
override fun update(world: World, delta: Float) {
world.queryEach<SpinControl> { _, spin -> spin.radians += spin.speed * delta }
}
}
To run a scene document instead, pass it to scene(document); see
Scene documents.
Helpers
Inside an entity { } block:
| Helper | Adds | Component id in a scene document |
|---|---|---|
transform(x, y, z, sx, sy, sz, rx, ry, rz) or transform(position) |
Transform |
the node's transform |
camera(mode, target, lens, primary) { … } |
Camera and CameraRig |
camera and camera_rig |
directionalLight(…), pointLight(…) |
Light |
light |
ambientLight(…) |
AmbientLight |
ambient_light |
fog(…) |
Fog |
fog |
skybox(…) |
Skybox |
skybox |
meshRenderer(mesh, material, cullMode), alias mesh(…) |
MeshRenderer |
mesh_renderer |
audioSource(…), audioListener() |
AudioSource, AudioListener |
none |
with(component) |
any component | the component's own id, if it has one |
configure(::Factory) { … } |
any component, created once | |
entity(name) { … } |
a child entity | the node's children |
On world.scene { } and in app { scene { } } there are also one-line entity helpers:
sun, lamp, skyboxEntity, fogEntity, ambientLightEntity, sound, and
defaultOrbitCamera. Inside app { scene { } } only: cameraEntity and meshEntity.
Write your own helper
A helper is an extension on EntityScope. configure creates the component the first time and
updates the same instance after that, so calling the helper twice does not add a second one:
with(component) always replaces the entity's component of that type.
How it works
The DSL calls the same World API a system does: entity is world.create() plus a Name, and each
helper is world.add. Nothing is recorded in between, so what you build is exactly what the scene
runtime and SceneLoader.fromWorld see.
Loading the Harbor Town scene document gives the same Transform, Name and SpinControl
components as the DSL above, and SceneLoader.fromWorld on the DSL world gives back that document.
Give every entity a transform
A node in a scene document always gets a Transform; an entity in the DSL only gets one from
transform(). A top-level entity without it has no position and is left out when the world is
saved as a scene document. A child entity without it gets a Transform but no parent link, so it
does not follow its parent.
scene { } inside ecs { } replaces earlier entities
In app { ecs { … } }, a nested scene { … } or scene(document) replaces the entities declared
before it with entity(…). Use one or the other in a block.
Resolving entities by name
In onReady, update, or a system factory, requireEntity("beacon"),
findEntity("beacon"), requireTransform(…) and requireCamera(…) look entities up by the name
you gave them.
See also
- ECS for components, queries and systems.
- Scene documents for the same content as a
*.scene.jsonfile. - Cameras and controls for
cameraand its rig. - Lights and shadows for the light helpers.
- App lifecycle for what runs when.