Skip to content

Camera

<Camera follow> mounts the one camera inside a World: an orbit the player drags with the left button or one finger and zooms with the wheel or two fingers, within the engine’s distance and tilt bounds. What it keeps in frame is the prop.

Its props are in CameraProps.

useCamera({ follow, through, settle, latch }) is what the component hangs on: it registers the mounted controls with what they follow and returns the props drei’s controls take. A game with controls of its own spreads those the same way, and names its component as through, which is what the devtools row is called. settle, true unless given, pulls the eye in front of whatever stands between it and her; the side camera passes false. latch, false unless given, reads the stick in the direction the camera faced when the push began rather than the direction it faces now; the chase camera passes true.

useCamera also takes the field of view, the stops and the aim, which default to the engine’s. CameraRigOptions lists every option and its default.

The side camera and the fly camera pass their locked stops the same way. tuning holds the settings that only one camera reads: the chase camera’s turnRate, holdSeconds and latchTurn, and the fly camera’s metresPerSecond and rotateSpeed.

In the devtools, the camera’s row holds a Settings leaf with these values, settle, latch and the camera’s own tuning. An edit there shows on the next frame; a changed option wins over it.

A following camera aims at the Player where she is drawn, between the last two steps, at a height that slides from her eyes up close to her middle far away. The eye settles in front of whatever stands between it and her: a trunk, a rock, the ground. The orbit’s framing, where the player left the drag and the wheel, is what a game saves.

A following camera mounts behind the player, looking the way she faces, as Roblox’s camera does when a character spawns. The first held forward key walks her the way she faces, whichever way the last scene left the camera; a player spawned with facing 0 walks toward negative z. With no player in the world yet, it mounts behind that rest facing. It turns once, as it mounts, and never recentres after that. The tilt and the distance stay where the last scene left them, within the bounds. A free orbit and the fly camera keep where they mount.

frameCamera(world, { azimuth, polar, distance }) puts a framing back with no transition, on the spot rather than a second later. A game calls it once, to set the camera’s start angle, from an effect that runs once useQueryFirst(CameraTrait) from koota/react returns the mounted camera: called before Camera has mounted, it finds no orbit and does nothing. A saved framing takes the same shape.

frameShot(surface, camera, aspect) builds the camera for a screenshot from a map’s data and its ground, with no render, so an agent sees a map from any side without writing a view first. Blender, Unity, Godot and Unreal give a person the same top, front and side views and “frame selected” in the editor; here they are the fields of one call. The camera is a raw { eye, target, fov, ortho }, returned as written, or a framed shot:

  • angle: top, bottom, front, back, left or right, orthographic; iso, 45° up from the south-east in perspective, the default; or { azimuth, elevation } in degrees, in perspective. front stands at +z looking toward −z, as three’s default camera does, and azimuth 90 stands at +x. top keeps north up the image.
  • around: all, every prop or the whole ground when the map has none, the default; a prop, region, path or view by name; a point; or a list of points.
  • zoom: 2 stands twice as close in perspective, or spans half as much orthographic; 1 by default.
  • ortho: overrides the angle’s own projection.

A scene with no map, as a track game’s, passes the box it draws in place of the ground. A raw camera then shoots as written, all frames that box, and a point frames the point; a name throws, because there is no map to hold it.

A perspective shot stands at the distance that fits the subject’s bounding sphere at a 50° field of view, as the devtools’ focus does. An orthographic shot fits the subject’s box as the camera sees it, so a square map from above fills the frame. frameView’s region, path and prop framing is frameShot with iso.

<SideCamera /> frames the player from the side, for a platformer: an orbit that follows her with its direction, tilt and distance locked at its props, and no drag, wheel or touch to move them. The loop aims it at her each frame, as it aims the following camera, but the eye does not settle: it stays at its distance when a trunk stands between it and her, as Unreal’s side-scroller turns its spring arm’s collision test off, so the frame never lurches in while she walks past the trees. The direction the stick is read in never turns, so left and right on the stick always walk her across the screen: along the world’s x at the default azimuth. Forward on the stick walks her into the screen.

Its props are in SideCameraProps.

It aims hard, with no lag and no dead zone. Unreal’s side-scroller template is the same shape: a spring arm with its rotation fixed and its lag off.

import { Player, SideCamera, World } from "@daniel-zarinski/engine";
export function Level() {
return (
<World map="meadow">
<Player />
<SideCamera />
</World>
);
}

<ChaseCamera /> stays behind the player. It is the following camera with one difference: each frame its direction turns toward the way she faces, at a quarter turn a second, the short way round. The tilt and the distance stay where the drag and the wheel leave them, within the bounds, and the eye settles in front of a trunk as the following camera’s does. A drag wins: the camera stops turning while the player drags it and for a second after the drag ends. fov sets its lens, 50 degrees unless given. Roblox’s Follow camera and Unreal’s spring arm that inherits the pawn’s yaw are the same camera.

A camera that turns toward her on its own needs the stick read differently, or a stick held a little off straight spins the view: she walks a little to the side, the camera turns after her, and the stick, read against the camera, now points further to the side. So while the chase camera is mounted, the stick is read in the direction the camera faced when the push began. That direction holds until the stick lets go or turns more than 30 degrees, and then the camera’s direction is read again. A held forward walks her straight along the way the push began while the camera comes round behind her. The following camera and the side camera read the stick against the camera’s direction on every frame.

import { ChaseCamera, Player, World } from "@daniel-zarinski/engine";
export function Run() {
return (
<World map="meadow">
<Player />
<ChaseCamera />
</World>
);
}

<ChaseCamera track={track} /> chases the entity riding a TrackMover on that track rather than the player’s hero, the player’s own where several ride it. The eye stands on the track 6.8 m behind the rider and 3.5 m above it, and looks 4 m ahead down the track at 1.5 m above it, both carried across with the rider. Through a bend the eye stays over the track behind, where a camera hung off the rider’s facing would swing out past the outside of the corner. Before the start and past the end, the eye carries on along the end’s direction. The eye eases after the rider and the aim eases faster, so the rider stays framed while the eye trails; a rider put more than 10 m from the eye, as at a restart, is cut to. Unity’s Cinemachine spline dolly and Unreal’s CameraRig_Rail are the same camera. Roblox and Godot leave it to the game.

This camera has no orbit, so no drag or wheel turns it, and a touch game keeps the drag for its own controls. The stick is read as it is, with no camera direction: a track mover reads only its sideways part.

import {
ChaseCamera,
Entity,
RunContext,
Track,
TrackMover,
} from "@daniel-zarinski/engine";
const run = new Track([
{ x: 0, y: 0, z: 0, width: 6 },
{ x: 8, y: -4, z: -20 },
{ x: -8, y: -8, z: -45 },
]);
export function Run() {
return (
<>
<Entity authority={RunContext.Client}>
<TrackMover track={run} distance={6} />
</Entity>
<ChaseCamera track={run} fov={52} />
</>
);
}

<FreeCamera /> is a fly camera with no target and no orbit centre, as Unreal’s spectator pawn and Godot’s editor camera: a spectator’s or an editor’s view of the world. It takes no props. It mounts where the canvas camera stands, facing where it faced, and from there the movement keys fly it: forward and back along its line of sight, left and right across it, and E and Q climb and descend along the world’s up. A drag with the left button or one finger turns it about itself. It flies on the same keys and stick the player walks on, so a scene mounts it instead of a player, not beside one, and a modal that pauses holds it still as it holds the world.

import { Camera, CameraTarget, Player, World } from "@daniel-zarinski/engine";
export function Run() {
return (
<World map="meadow">
<Player />
<Camera follow={CameraTarget.Player} />
</World>
);
}

A lobby that shows the world without a player mounts <Camera follow={null} /> instead: a free orbit, for a menu, that stays where the drag leaves it. The default follows the player, as Roblox’s camera follows the character unless a script says otherwise.