Cameras and follow modes
Follow a character or a vehicle with first-person and third-person modes, set eye height, and switch the active camera at runtime.
Third-person cameras
A camera behind the character uses a negative local Z offset, for example {x=0, y=0.4, z=-1.5}, and looks back toward the character. Built-in follow camera modes expose their live smoothed transform through the normal game object API.
What a script reads from a camera. getPosition(), getRotation() and getForward() on a camera return the view the player is looking through, including when another object's script calls them inside update(). That makes the active camera a safe basis for camera-relative movement and for aiming. A built-in camera mode moves the camera after every script has run, so a read inside update() gives the view as it was last drawn, one frame behind the one being built.
This changed on 21 September 2026
Before then, a first_person or free camera read inside update() reported its authored, design-time transform, even though the screen showed the right view. An aim or movement direction taken from it pointed wherever the camera was placed in the editor. If you worked around that by reading the target character instead, the camera itself now gives the right answer.
Built-in camera control modes
A camera object's controlMode is free (default, no automatic following), first_person, first_person_absolute, or third_person. Set it from the camera's inspector, or through the MCP scene-object tools, instead of hand-rolling the offset every frame.
Every following mode aims at the camera's targetCharacterId. If that does not resolve to an object in the scene, the camera silently falls back to free-camera behaviour parked at its authored position — which is the first thing to check if a follow camera seems inert.
The target does not have to be a character. The field name is historical: it accepts any object with a transform, including wheeledVehicle and trackedVehicle objects. A chase camera for a car or a tank needs no hand-written script — set controlMode to third_person and point targetCharacterId at the vehicle. The camera's inspector lists characters and vehicles together in its Follow Target dropdown.
In a multiplayer game, point the target at an object bound to a player slot, or at a spawn template whose copies players are given, or turn on the camera's Follow local player setting (multiplayer.followLocalPlayer). Each player's browser then retargets the camera to that player's own character or vehicle, and keeps following whatever they control — into a vehicle the scene script hands them, and back out again. A camera aimed at any other object, without that setting, is not retargeted, so every player watches the same one. See Build and test a multiplayer game.
first_person
The camera snaps to the target character's position every frame, with no smoothing. followDistance and followHeight are ignored in this mode; they apply only to third_person.
Eye height. Set eyeHeight — the camera's own field, default 1.6 — to the eye height you want. It is measured from the follow target's origin, which for a character is its feet, so it is the eye height directly: eyeHeight = 1.65 puts the eye at 1.65m on a 2m character. There is no arithmetic to do. The editor exposes it as Eye Height on a first-person camera's inspector.
followOffset still applies, and it is added on top of eyeHeight rather than used in place of it — so leave followOffset.y at 0 unless you want a further nudge. Before eyeHeight existed the 1.6 was hardcoded and invisible, and the way to get a 1.65m eye was followOffset.y = 0.05; that still works and still means the same thing, but eyeHeight is what someone reading the scene later will understand. The offset is also rotated by the character's rotation before being added, so a non-zero x or z orbits with the character rather than staying world-fixed.
Because a character's origin is at its feet, eyeHeight is measured from the floor the character is standing on, not from its waist. See Characters.
Which way it looks. With no look input applied, the camera renders along its target's forward axis — local +Z, the direction a character or a vehicle faces. The facing you authored on the character is therefore what the player sees at spawn, and turning the character turns the view with it. getForward() on the camera reports that same rendered direction, so it agrees with what is on screen and can be used directly as a movement or aiming basis.
This changed on 9 September 2026
Three.js cameras render down their local -Z while everything else in Airogel faces +Z, and first_person used to inherit the character's rotation without reconciling the two — so the view started out facing behind the character. If you compensated for that by rotating the character or the camera 180° in an existing scene, remove the compensation: the view now agrees with the character's facing on its own.
Look. The mode consumes input.lookDeltaX and input.lookDeltaY automatically — you do not need to write look handling in Lua. But it can only turn the camera if something is producing those deltas, and what produces them depends on the device: arrow keys always, the mouse only when the scene enables desktop mouse look, and touch only when mobile controls are enabled. That is the single most common reason a first-person camera positions correctly but never rotates. Player input and touch controls has the full list.
Hiding the body. A first-person camera, in either mode, does not draw the object it follows or anything parented under it, so the player does not see the inside of their own head. The object is left out of that camera's view only — other cameras, other players' views, collisions and animation are unaffected — and its shadow goes with it. In a multiplayer room each browser hides its own player's object. Turn off Hide followed object in the inspector (hideFollowedObject: false; the default is true) to show the body.
Pitch limits. Minimum Pitch and Maximum Pitch (minPitch, maxPitch) limit how far the view tilts, in degrees from -90 to 90, positive up. minPitch: -60 stops the view 60° below level. Left unset, a first-person camera stops just short of straight up and down, about ±87°, and a third-person orbit at ±60°. A third-person limit is measured from the authored boom, not from the horizon. Minimum Pitch cannot be above Maximum Pitch. They apply to the built-in modes only; a scripted camera supplies its own clamp.
first_person_absolute
First Person (Absolute Aim) places the camera exactly as first_person does, with the same eyeHeight and followOffset, and differs only in how it turns. It takes its starting heading from the target once, then keeps its own world-space heading, so turning the body afterwards does not turn the view. Use it when the control script turns the character to face where the player is looking, for example from input.aimYaw in a multiplayer room. A plain first_person camera adds look to the body's rotation, so a script that turns the body from look counts the turn twice.
A published game picks up these camera settings when you republish it.
Desktop mouse look
Enable it per scene: Scene Settings > Player input controls > Desktop mouse look, or pointer_lock on the player-controls MCP payload. The player clicks the canvas to capture the mouse and presses Escape to release it. The canvas click that captures the mouse does not trigger an action. Subsequent left clicks trigger primary_fire unless an authored action explicitly binds Mouse0.
It is off by default, and deliberately so. Pointer lock hides the cursor and stops DOM events reaching guiButton elements, so a scene whose UI is GUI buttons would lose every click. Enable it for a game the player aims with; leave it off for anything menu-driven.
This is settable headlessly. pointer_lock is a field on scenes_update's player_controls object, so an agent building a scene entirely through MCP can turn mouse look on without anyone opening the editor. desktop_mouse_look is accepted as an alias for the same field, since that is the name the editor and these docs use for it. See MCP tool guide.
third_person
In third_person mode the camera booms behind its target automatically. These properties shape the boom:
followDistance(default 5) andfollowHeight(default 2) set the boom's base offset behind and above the target.followOffsetadds a local{x, y, z}nudge on top of that, for example to shift the camera to one side.followSmoothing(default 0.1) is a lerp time constant in seconds, not a 0-1 intensity:0tracks instantly, larger values lag more. The inspector's Smoothing field offers 0 to 1, which is the useful range. 2 is the maximum: a larger value is refused when you save it, and one that reaches the engine anyway is clamped to 2 with a console warning.lookAtOffsetmoves the point the camera aims at, in the target's local space. The inspector exposes its height as Target Height. Raise it to aim at a character's head instead of its feet. The same point is where the obstruction check below starts.- The inspector's Looking at Target button switches to Look Forward (
lookAtTarget: false). The camera keeps its place on the boom but faces the way the target faces instead of aiming at it, which suits a vehicle chase view.
The defaults are sized for a roughly human-scale target. A car or tank is wider and faster than a character, so a vehicle chase camera usually wants a larger followDistance than 5 and a little followSmoothing so the boom does not snap on every steering input.
Look is split by producer. Mouse movement and touch drag orbit the boom independently around the target. Arrow keys and the touch look stick do not orbit a third-person camera directly; they remain on the Lua input bus so a controller can turn the character, and the boom follows the target's rotation. This avoids applying one held turn twice. If Desktop mouse look is off, a desktop player has no independent orbit control, by design. By default the orbit tilts at most 60° above or below the boom; Minimum Pitch and Maximum Pitch change that.
Walls pull the camera in. A third-person camera casts a ray from the point it aims at (the target plus lookAtOffset) back toward where the boom wants to be. If the ray hits something, the camera moves in front of it, stopping collisionRadius (default 0.5) short of the surface, so the view never passes through geometry. It pulls in at once and eases back out once the line of sight has been clear for a moment. The ray ignores the follow target itself and trigger volumes. This is on by default for third_person cameras; the inspector's Enable Physics button (hasPhysics) turns it off. Line of Sight Distance (losDistance) limits how far along the boom the check looks; left unset, it covers the whole boom.
Attaching a script disables the control mode
The engine runs its built-in camera controller only for cameras that have no script attached. Giving a camera object a scriptId hands full control to that script and silently stops first_person, first_person_absolute and third_person from doing anything — the camera will sit wherever the script leaves it. This is deliberate, so a script can take over completely, but it is all-or-nothing: you cannot keep the built-in follow behaviour and add a little scripting on top. Put the script on another object if you only need to read or nudge the camera.
Cameras in slow motion
The built-in first_person, first_person_absolute and third_person controllers, and held-stick look, run on real time, so follow easing and look stay smooth while scene.setTimeScale slows the game or freezes it at 0. A scripted camera's update(deltaTime) gets game time like every other script, so it slows down with the world. Ease and move it by scene.getUnscaledDeltaTime(), the real seconds of the frame, instead. See Time scale and slow motion.
Lens settings
Every camera has a Camera Type: perspective (the default) or orthographic. A perspective camera's Field of View (fov) is its vertical angle in degrees, default 75. An orthographic camera uses Zoom (zoom, default 1) instead. Near Plane (near, default 0.1) and Far Plane (far, default 1000) set the closest and furthest distances drawn. Scripts can change a perspective camera's field of view while the scene runs; the rest are authored settings. See Changing the lens and follow at runtime.
Changing the lens and follow at runtime
A camera's script API has setFov(degrees) and getFov(), setFollowDistance(distance) and getFollowDistance(), and setFollowSmoothing(seconds) and getFollowSmoothing(). setFov changes a perspective camera's vertical field of view at once, clamped to 1–180°, and returns false on an orthographic camera. setFollowDistance (metres, at least 0) and setFollowSmoothing (0–2 seconds) change what the built-in third_person controller uses from its next frame. The changes last for the run: the saved camera keeps its authored values, and Stop restores them. On anything but a camera the setters return false and the getters nil.
A camera with its own script has no built-in controller, so call these from another script, such as the vehicle's or character's, a GUI script or the scene script, and reach the camera with scene.getActiveCamera() or scene.getObjectByName. Ease toward a target each frame rather than jumping. This widens the lens with a car's speed:
local car = scene.getObjectByName("Car")
function update(deltaTime)
local camera = scene.getActiveCamera()
local fov = camera.getFov()
if fov then
local target = 70 + math.min(math.abs(car.getVehicleForwardSpeed()), 40) * 0.4
camera.setFov(fov + (target - fov) * math.min(1, deltaTime * 4))
end
end
Stop also returns a first- or third-person camera's look to its authored heading, so every run starts facing the way the scene was saved. To do the same mid-run, for a respawn or a round reset, call resetLook() on the camera after turning the character back: it clears the look the player has added (first-person yaw and pitch, third-person orbit) and returns false on anything but a camera. A first_person_absolute camera then takes its heading from the character's facing at that moment.
In a multiplayer room these are presentation calls: the room server refuses them, so make them from a GUI script or a "presentation" script, which runs in each player's browser.
Camera shake and scripted camera moves
A camera's script API can shake the view and fly the camera through a cutscene, then hand it back to its follow mode. Call these on the camera from a GUI script, the scene script or another object's script, reaching it with scene.getActiveCamera() or scene.getObjectByName.
local camera = scene.getActiveCamera()
camera.shake(0.6, 0.4) -- trauma 0-1, seconds
camera.playCameraPath({
{ position = { x = 12, y = 6, z = 0 }, lookAt = { x = 0, y = 1, z = 0 }, duration = 2, easing = "inOutQuad" },
{ position = { x = 0, y = 6, z = 12 }, lookAt = { x = 0, y = 1, z = 0 }, duration = 3 },
}, { blendOut = 0.75 })
Shake
shake(intensity, duration) adds trauma from 0 to 1 that falls to zero over duration seconds. The offset grows with the square of the trauma, so small values are subtle. A second shake keeps the higher trauma and the later end, so a short jolt does not cut a long rumble short. getShake() returns the trauma left and stopShake() ends it at once. Shake only moves what is drawn: getPosition(), getRotation(), aiming and follow smoothing never see it.
Camera paths
playCameraPath(stops, options) moves the camera through a list of stops (at most 64). Every field of a stop is optional:
position: where to be. Left out, the camera stays at the previous stop's position.lookAt: a world point to face. It wins overrotation. When two stops in a row both havelookAt, the view keeps tracking the point during the move.rotation: a full quaternion. With neitherlookAtnorrotation, the previous view is kept.duration: seconds to reach this stop. The default, 0, is a cut.easing: one of thetweeneasing names, such as"linear","inOutQuad","outCubic"or"outBack".
Options are blendOut, the seconds taken to ease back to the follow view at the end (default 0.5), and hold: with hold = true the camera stays on the last stop until stopCameraPath. moveCameraTo(position, lookAt, duration, easing, options) is a one-stop path. stopCameraPath(blendOut) ends a path early, easing back over blendOut seconds, and returns false if nothing was playing. isCameraPathPlaying() is true while a path moves or holds the camera.
A path starts from wherever the camera is drawn, and a new path replaces the running one from the point it had reached. A path with a bad field returns false and leaves the running one alone. The built-in follow mode keeps running underneath, so when the path lets go the camera eases back to a view that has kept up with the player. A camera with its own script stays where the path ended unless its script moves it.
Shake and paths run on real time, so they keep going through scene.setTimeScale slow motion and pauses. Stop clears them. On anything but a camera the calls return false and the getters nil. In a multiplayer room they are presentation calls: make them from a GUI script or a "presentation" script.
Reduce camera motion
Players can turn on Reduce camera motion in the game's look settings. Until they choose, it follows the device's reduced-motion setting. While it is on, shake is not drawn and a path cuts to each stop instead of moving there, keeping the same timing. Scripts get the same results either way, so do not rely on shake or a camera move as the only way to tell the player something.
Switching the active camera at runtime
A camera authored with isActive becomes the camera the scene renders through when it runs — in preview, in a published game, and when you press Play in the editor. Stopping the run in the editor hands the viewport back to the editor camera. If you were already previewing a specific camera from the outliner, running the scene leaves you on that one.
scene.setActiveCamera(id) changes which camera the running scene renders through and returns true, or false if id is not a camera. It does not change the camera saved as active in the scene. scene.getActiveCamera() returns the live one's object API. Both are available to every object script, not just scene scripts. In a multiplayer room the view belongs to each player's browser, so call them from a camera or GUI script; the scene script and characters' control scripts run on the room server and cannot. See Scene scripts and storage.
setVisible() on a camera does not affect which camera renders — it is a rendering-visibility toggle and is not tied to a camera's active state.