Cameras and controls
A camera decides where the scene is seen from. A camera rig moves it with the mouse and follows a target; movement control turns keys into player motion.
components: camera · camera_rig · movement_control · spin_control
Desktop · Android · iOS · Web
A scene document and the scene DSL are two ways to write the same ECS components. Both tabs below
build a player and a third-person camera that follows it. camera is in awake:scene:scene3d;
camera_rig and movement_control, and the systems that drive them, are in
com.awakekt.awake.scene:controls, which com.awakekt.awake.scene:authoring brings with it.
Add a follow camera
Give the camera entity a camera for the lens and a camera_rig for how it moves. The rig's
target names the node it follows; the player's movement_control marks it as the entity the keys
move.
{
"version": 1,
"nodes": [
{
"name": "player",
"transform": {
"position": { "x": 0.0, "y": 0.5, "z": 0.0 }
},
"components": [
{ "component": "movement_control", "speed": 6.0 }
]
},
{
"name": "camera",
"components": [
{
"component": "camera",
"eye": { "x": 0.0, "y": 4.0, "z": 8.0 },
"center": { "x": 0.0, "y": 1.0, "z": 0.0 },
"fovYDegrees": 60.0,
"near": 0.1,
"far": 500.0
},
{
"component": "camera_rig",
"mode": "ThirdPerson",
"target": "player",
"distance": 8.0,
"pitch": -0.3,
"offset": { "x": 0.0, "y": 1.5, "z": 0.0 }
}
]
}
]
}
world.scene {
val player = entity("player") {
transform(y = 0.5f)
with(MovementControl().apply { speed = 6f })
}
entity("camera") {
transform()
camera(
mode = CameraMode.ThirdPerson,
target = player,
lens = Lens.perspective(
eye = Vec3f(0f, 4f, 8f),
center = Vec3f(0f, 1f, 0f),
fovYDegrees = 60f,
far = 500f,
),
) {
distance = 8f
pitch = -0.3f
offsetPosition.set(0f, 1.5f, 0f)
needsReset = false // keep the pitch above; see the warning below
}
}
}
camera() adds both Camera and CameraRig. MovementControl has no helper, so with adds
it.
- Select the camera entity in the Hierarchy.
- In the Inspector, the Camera section sets Field of View, Near and Far. Viewport Preview turns on a preview from this camera.
Studio has no Add component entry for camera_rig or movement_control. Add them in the
scene document.
Load it
camera_rig and movement_control are not among the built-in components, so register them before
decoding. registerControls() adds both to a registry you pass to instantiate:
DefaultSceneComponentResolvers.install()
val registry = SceneComponentRegistry().registerControls()
val document = SceneLoader.decode(json)
val loaded = SceneLoader.instantiate(document, World(), registry).world
Make it move
Components hold settings; systems move things. Register the control systems in the scene, and tag
the camera that takes input with ActiveCamera:
DefaultSceneComponentResolvers.install()
SceneComponentRegistry.registerGlobal(MovementControlBinding)
SceneComponentRegistry.registerGlobal(CameraRigBinding)
val game = app {
ecs {
scene(SceneLoader.decode(json))
playerInputSystem() // keys to MovementControl
matrixRelativeMovementSystem() // MovementControl to Transform, camera-relative
cameraSystem() // CameraRig to Camera, on the entity tagged ActiveCamera
onReady { world.add(requireEntity("camera"), ActiveCamera()) }
}
}
scene(document) inside an app loads with the global registry, which is why this sample uses
registerGlobal instead of registerControls().
Helper in scene { } |
System | Reads | Writes |
|---|---|---|---|
playerInputSystem() |
PlayerInputSystem |
The scene's move, jump and run input actions: by default W A S D or the arrow keys, Space, and Shift held |
MovementControl.moveX, moveZ, jump, run on every entity whose driver is Player |
matrixRelativeMovementSystem(speed = 5f) |
MatrixRelativeMovementSystem |
MovementControl, the active camera's view |
Transform.position, relative to where the camera looks |
cameraSystem() |
CameraSystem |
mouse drag and scroll, the rig's target | the Camera lens of the entity tagged ActiveCamera |
cameraInputSystem() |
CameraInputSystem |
F1, F2, F4, F5 | the active rig's mode |
All four are frame systems. The ones that read input get it with whatever the UI claimed removed, so
typing in a text field does not walk the player. With no ActiveCamera, movement treats -Z as
forward.
Properties
camera
| Property | Type | Default | What it does |
|---|---|---|---|
eye |
vector | (0, 0, 5) |
Where the camera is. |
center |
vector | (0, 0, 0) |
The point it looks at. |
up |
vector | (0, 1, 0) |
Which way is up. |
fovYDegrees |
number | 60 in a scene document; 45 from Lens.perspective |
Vertical field of view, between 0 and 180. |
near |
number | 0.1 |
Near clip distance, greater than 0. |
far |
number | 100 |
Far clip distance, greater than near. |
primary |
boolean | true |
Whether this camera renders the scene. |
projection |
perspective · orthographic |
perspective |
orthographic keeps things the same size at any distance and ignores fovYDegrees; use it for 2D. |
orthoHalfHeight |
number | about 2.07 |
Half the vertical world extent an orthographic view covers; the width follows from the aspect ratio. Used only when projection is orthographic. |
camera_rig
| Property | Type | Default | What it does |
|---|---|---|---|
mode |
FirstPerson · ThirdPerson · FreeFly · Cinematic · TopDown |
ThirdPerson in a scene document; FirstPerson from camera() |
How the camera moves; see below. |
target |
node name | none | The node the camera follows. |
distance |
number | 5 |
How far a third-person or top-down camera sits from its pivot. |
minDistance, maxDistance |
number | 2, 20 |
The range scrolling zooms within. distance must lie inside it. |
pitch, yaw |
radians | 0 |
Starting angles. Negative pitch looks down. |
offset |
vector | (0, 1.8, 0) |
Added to the target's position to get the pivot, or the pivot itself with no target. |
flySpeed |
number | 10 |
Units per second for FreeFly. Greater than 0. |
In the scene DSL these are CameraRig fields; target is targetEntity and offset is
offsetPosition.
| Mode | Needs a target | What it does |
|---|---|---|
FirstPerson |
yes | Eye at the target plus offset; dragging turns yaw and pitch. |
ThirdPerson |
no | Orbits the pivot at distance; dragging orbits, scrolling zooms. The eye eases toward its pose. |
FreeFly |
no | W A S D move along the view, Q and E down and up, Shift is faster; dragging looks around. A host rebinds the keys with CameraGesturePolicy(flyActions = ...), named as in CameraFlyActions. |
Cinematic |
yes | Looks at the target from a fixed spot; no input. |
TopDown |
no | Looks down at 60 degrees from distance; dragging turns yaw, scrolling zooms. |
movement_control
| Property | Type | Default | What it does |
|---|---|---|---|
speed |
number or none | none | Units per second for this entity. None uses the movement system's speed. |
spin_control
| Property | Type | Default | What it does |
|---|---|---|---|
radians |
number | 0 |
The current angle about Y. |
speed |
number | 1 |
Turn rate, for the system that advances radians. Not negative. |
SpinSystem copies radians to Transform.rotation.y; it does not advance it. Advance it in your
own system, as Scene DSL shows.
How it works
CameraSystem runs every frame. It reads the pointer drag and scroll, updates the rig's yaw, pitch
and distance, then writes the lens eye and center from the rig, the target's Transform, and
the mode. Only the entity tagged ActiveCamera takes input.
A rig's target is resolved after the whole document is loaded, so the camera may come before the
node it follows. A target name that no node has stops the load with an error.
Tag the active camera
Neither a scene document nor camera() adds ActiveCamera. Without it CameraSystem leaves the
camera where it is. Add it in onReady, as above.
A DSL rig that sets mode resets its angles
Changing CameraRig.mode schedules a reset that replaces pitch, yaw and distance with the
mode's defaults on the next CameraSystem update. camera(mode = …) changes the mode, so set
needsReset = false in its block to keep angles you set. A loaded camera_rig already keeps
them.
MatrixRelativeMovementSystem goes through walls
It writes Transform.position directly. For an entity that should collide, use a character
controller instead, and don't run both on the same entity.
F2 does two things
With cameraInputSystem() registered, F2 switches the rig to ThirdPerson, and the scene runtime
also uses F2 to toggle its frame timing stats. Pass your own modeKeys map to CameraInputSystem
to move it.
See also
- Input for reading keys and the pointer in your own systems.
- Scene documents for registering components.
- Character controller for movement that collides.
- Scene document components for every component id.