Camera¶
player.camera gives the server full control of a player's camera:
where it is, where it points (at a spot or thing, or by pan and tilt),
roll, field of view and focus (depth of field). Use it for cutscenes, intros, spectating and
showing places.
- Shots: one camera position, cut to or glided to.
- Sequences: shots, holds and paths played in order.
- Paths: flying through a list of points on a smooth curve.
- Freecam: the player flies the camera themselves.
Moves run on the player's game, frame by frame, so they're smooth whatever their ping, and the server hears when each step starts and ends.
Client scripts have the same API, as sagl.camera.
await player.camera.set({ position: { x: 1500, y: -1700, z: 60 }, lookAt: { x: 1480, y: -1750, z: 13 } });
await player.camera.set({ position: { x: 1450, y: -1700, z: 40 }, lookAt: { vehicle: car }, fov: 40, roll: 5, ms: 5000 });
player.camera.reset();
Shots¶
Cuts or glides to one shot. Resolves true when the camera gets there, as
their game shows it, or false if something replaced it first. The camera
then stays there, still following anything it looks at, until the next
shot or reset().
| Option | Type | Description |
|---|---|---|
position |
Vector3 | { player \| ped \| vehicle, offset?, relative? } |
Where it is. A spot, or riding along with a player, ped or vehicle at offset from its middle. With relative: true the offset turns with it (a chase cam or bonnet cam); without, it keeps its world direction (a helicopter shot following a car). |
lookAt |
Vector3 | { player \| ped \| vehicle, offset? } |
What it looks at: a spot, or a thing it keeps looking at as it moves. |
rotation |
{ pan, tilt? } |
Instead of lookAt: pan in degrees from north, anticlockwise (as headings), and tilt up from level. Riding along relative, pan is from the thing's heading. |
roll |
number |
Degrees. Positive turns the camera clockwise, so the picture tilts anticlockwise. |
fov |
number |
Field of view, 5 to 120 degrees. The game's is 70; lower zooms in. |
focus |
number | Vector3 | { player \| ped \| vehicle, offset? } | "auto" | null |
Depth of field: sharp at this many metres from the camera, at a spot, or on a player, ped or vehicle (at offset from its middle; kept in focus as it and the camera move), or "auto" (the nearest thing in the middle of the screen, like a camera's autofocus). Nearer and further things blur. null turns it off. See Focus. |
aperture |
number |
How shallow the focus is, as a lens's f-number: lower blurs more (1.4 very shallow, 4 the default, 16 hardly at all). |
ms |
number |
Glides here from where the camera is, over this long. Default 0: a cut. |
ease |
"linear" | "in" | "out" | "inOut" |
How it speeds up and slows down. Default "inOut". true and false mean "inOut" and "linear". |
widescreen |
boolean |
Cinematic: black bars top and bottom, subtitles in the bottom one, and every HUD put away until reset(). |
Leave out lookAt and rotation to keep looking the way it was, and
roll, fov, focus and aperture to keep them as they were.
Everything that changes glides together: position, direction (turning the
shortest way), roll, field of view and focus (a focus pull, when it moves
from one distance to another).
If something the camera follows isn't around on the player's game (too far away, or gone), the camera stays where it last saw it.
// A chase cam behind and above a car, then a slow zoom on its driver.
await player.camera.set({ position: { vehicle: car, offset: { x: 0, y: -7, z: 2.5 }, relative: true }, lookAt: { vehicle: car } });
await player.camera.set({ position: { x: 2490, y: -1680, z: 16 }, lookAt: { player: driver, offset: { x: 0, y: 0, z: 0.6 } }, fov: 20, ms: 4000 });
Sequences¶
Plays steps in order: shots (as set), holds and paths. The whole
sequence goes to the player's game at once, so it plays exactly as
written. At most 32 steps, and 256 path points in all.
const intro = player.camera.play([
{ position: a, lookAt: { vehicle: car }, fov: 60 }, // cut
{ position: b, lookAt: { vehicle: car }, fov: 35, ms: 4000 }, // glide
{ hold: 2000 }, // stay 2 s
{ path: [p1, p2, p3, p4], ms: 10000, bank: true }, // fly through
{ position: c, rotation: { pan: 90, tilt: -10 }, roll: 15, ms: 1500, ease: "out" },
], { widescreen: true });
intro.onStep((i) => { if (i === 3) player.subtitle("Welcome to Grove Street", 3000); });
if (await intro.done) player.camera.reset();
CameraSequence:
| Member | Description |
|---|---|
done |
Resolves true when the last step finishes, false if it was stopped, replaced, reset, or the player left first. |
onStep(fn) |
Calls fn(index) as each step starts on their game. Returns a function that removes it. |
stop() |
Stops it. The camera stays where it is. |
With loop: true it goes back to the first step after the last, until
stopped. done then only resolves (false) when it's stopped.
A hold, { hold: ms }, stays where the step before left the camera, still
following anything it looked at.
Paths¶
{ path: [p1, p2, p3], ms: 8000 }
{ path: [p1, p2, p3], speed: 15, lookAt: { player }, smoothing: 0.5 }
The camera flies through every point. It starts at the first point (a cut there, if the camera isn't already there) and moves evenly along the curve, so it doesn't rush between far-apart points.
| Option | Type | Description |
|---|---|---|
path |
{ x, y, z, pan?, tilt?, roll?, fov? }[] |
The points, at least 2. Points can carry their own angles, roll and field of view, which are blended from point to point. |
ms |
number |
How long the whole path takes. |
speed |
number |
Metres a second, when ms isn't given. Default 10. |
smoothing |
number |
0 flies straight lines between points; 1 (default) a smooth curve through every point. The curve doesn't overshoot or loop at sharp corners. |
lookAt |
"ahead" | a spot or thing |
"ahead" looks where it's going. A spot or thing keeps looking at it. Left out: the points' own pan and tilt if every point has them, otherwise "ahead". |
rotation |
{ pan, tilt? } |
Fixed angles all along, instead of lookAt. |
lookAhead |
number |
With "ahead": how many metres along the path it looks. Default 5. |
roll |
number |
Degrees, all along. Ignored if the points have their own. |
bank |
boolean |
Rolls into turns like an aircraft, instead of roll. |
fov |
number |
All along. Ignored if the points have their own. |
focus, aperture |
as shots | All along. A thing in focus stays in focus as the camera moves. |
ease |
as shots | Speeding up from the start and slowing at the end. Default "inOut"; "linear" keeps one speed throughout. |
Freecam¶
Lets the player fly the camera. The mouse turns it, the movement keys
move it, sprint goes up, crouch goes down, jump goes faster, and the mouse
wheel zooms. speed is
in m/s (default 10). Their ped stays where it is, without controls, until
reset() (or another shot) ends it.
player.camera.position and front follow the freecam, as the player's
sync reports them, so a spectator mode can see where they're looking.
Building shots in game¶
Freeroam has a camera mode for taking pictures and writing shots and paths
(examples/freeroam/client-files/scripts/camera.js). Type /camera: the
HUD and chat go, and the keys show in the bottom right. Fly with the mouse
and movement keys, and:
| Key | Does |
|---|---|
| Page Up / Down (held) | Rise and sink. |
| Alt (held) | Everything slower: moving, looking, zoom, roll, focus. |
| Z / X (held) | Zoom in and out. |
| Q / E (held) | Roll. |
| V | Depth of field on (autofocus) and off. |
| R / F (held) | Focus nearer and further by hand. |
| [ / ] (held) | More and less blur. |
| Home | Level, and zoom back out. |
| F8 | Saves a picture to the player's Pictures\SAGL folder. |
| F9 | Saves the shot: position, angles and lens, as code. |
| P / K / L | Drop a path point, take the last back, save the path. |
| F1 | How it works. |
| Backspace | Leaves camera mode. |
Shots and paths are saved as code, ready to paste into set or play,
to camera-shots.txt in freeroam's folder. The keys can be changed in
Settings, Controls.
Other members¶
| Member | Description |
|---|---|
stop() |
Stops whatever's playing (or the freecam). The camera stays where it is. |
reset() |
Back behind them, as normal. player.respawn() does it too. |
front |
Where their camera looks (a unit vector), as their last sync. null before it, and for passengers. As SA-MP's GetPlayerCameraFrontVector. |
position |
Where their camera is, as their last sync. null as front. As SA-MP's GetPlayerCameraPos. |
While the server holds the camera¶
The player's ped stays where it is, and their controls work as before.
Freeze them with player.frozen if needed. Aiming isn't affected:
the game aims from the ped, not from this camera.
When the camera is more than 150 m from the player, their game loads the world around the camera instead of around them: buildings in full detail, collision, traffic and peds. Meanwhile the player (or their vehicle) is held still where they are, so they can't fall through ground that's no longer loaded. This ends once the camera comes back within 100 m. A cut to somewhere new takes a few seconds to load in full: start with a hold, or cover it with a fade.
Focus¶
await player.camera.set({ position: a, lookAt: { vehicle: car }, fov: 35, focus: { vehicle: car }, aperture: 1.8 });
await player.camera.set({ position: a, lookAt: { player: driver }, focus: { player: driver, offset: { x: 0, y: 0, z: 0.6 } }, ms: 1500 }); // pull focus to the driver's head
await player.camera.set({ position: a, lookAt: sign, focus: sign }); // a spot
player.camera.set({ position: a, lookAt: { vehicle: car }, focus: null }); // all sharp again
Focus gives the shot depth of field: what's at the focus distance is
sharp, and things nearer and further blur more the further they are from
it, as with a real lens. aperture sets how much: at 1.8 only a narrow
band is sharp, at 8 most of the picture is. Focus glides with the rest of
a move, so changing it over ms pulls focus from one thing to another,
and turning it on or off fades the blur in or out.
Shooting through glass: a car's windscreen counts as the nearest thing
for focus, so people seen through it blur if you focus on them. Focus on
the glass instead (a spot on the windscreen, such as
car.offset({ ahead: 0.9, up: 0.5 })), and they come out sharp. A ped in
a vehicle is also where the vehicle is, so { ped } focuses on the
vehicle's middle.
It works by reading the depth of the 3D picture, which SAGL makes readable on nearly all graphics cards (and under Wine and DXVK). On the rare card that can't, shots simply stay sharp. The HUD, chat and menus are never blurred.
Screen effects can use the same depth: see
depthAt and the built-in
dof effect, which gives depth of field
without moving the camera.