Camera
MSFS 2024 lets an add-on take the camera: acquire it, place it relative to the world, an aircraft or the pilot’s eyepoint, and give it back. The SDK calls are bound on the engine, pkg/camera turns them into shots, and the airport map films its traffic with them.
Bindings
engine.Engine (MSFS 2024 only):
| Call | What it does |
|---|---|
CameraAcquire(clientID) |
Takes the camera; a SIMCONNECT_RECV_CAMERA_STATUS follows (Message.AsCameraStatus). |
CameraSet(data, mask) |
Places it: types.SIMCONNECT_DATA_CAMERA with the members in mask (position, rotation, target, field of view). |
CameraGet(referential) |
Asks where it is; Message.AsCameraData decodes the answer. |
CameraRelease(def) |
Gives it back. |
RequestCameraWorldLocker(pos, ref, id), DeleteCameraWorldLocker() |
Keeps the terrain, scenery and objects around a point loaded while the camera is away from the user aircraft. |
CameraGetStatus, SubscribeToCameraStatusUpdate, CameraEnableFlag, EnumerateCameraDefinitions, CameraSetUsingCameraDefinition |
The rest of the API. |
SimConnect packs SIMCONNECT_DATA_CAMERA to one byte (84 bytes), so it crosses the API as bytes: Bytes() and types.CameraDataFrom.
Conventions
These were measured live with SDK 1.7.3; the SDK documentation leaves them out.
- World referential: x is the latitude and y the longitude, both in degrees; z is the altitude in meters above sea level.
- Relative to an aircraft: x points to its left, y up, z forward, in meters.
- Rotation: what SimConnect returns does not follow the header’s pitch, bank, heading order.
pkg/cameraaims with a point to look at (targeted) instead of angles.
camera.On(object, right, up, forward) takes an offset to the right and flips it for SimConnect.
Poses and shots
p := camera.Pose{
Eye: camera.On(obj, 40, 6, -60), // right, up, forward of the aircraft
Target: camera.On(obj, 0, 2, 0), // look at it
FovDeg: 45,
}
A Shot is the camera over time: Length() and PoseAt(t). No shot runs longer than camera.MaxShot (10 s).
Holdkeeps one pose.Movegoes from one pose to another, eased (Smooth: no jerk at either end).Pathflies through keyframes on a Catmull-Rom curve. This is where combined moves come from: rising while circling while pushing in.Orbit,Chase,Track(a camera fixed in the world that follows an aircraft) andTrackZoom(the same, zooming).
Drone moves
Each move is a MoveFunc(object, size, side, duration), scaled to the aircraft’s span and length.
| Kind | Moves |
|---|---|
| Wide | RevealRise, Flyover, SpiralDescend, LeadChase, ParallaxTrack, HeroLowPushIn, PullBackReveal, TopDown, TopOrbit, SideDolly, HeadOnPass, ChaseRise |
| Detail | EngineDetail, NoseGearDetail, MainGearDetail, CockpitDetail, TailDetail, WingtipAlong, LightsDetail |
Director
d := camera.NewDirector(engine, "my-addon")
d.LockWorld(camera.At(lat, lon, altM)) // optional: no scenery loading on cuts
d.Play(shotA, shotB) // a cut; Then queues
// in the connection's goroutine, 30–60 times a second:
d.Tick(time.Now())
// and for every message:
d.Handle(msg)
The director acquires the camera on the first Tick. It sets the current shot’s pose on every tick and moves to the next shot when one ends; with nothing queued it holds the last pose. Release gives the camera back and deletes the world locker. Always release it: a camera left acquired stays stuck for the user.
On the airport map
The camera button in the status strip opens the director:
- 🎥 Camera
- Auto director cuts to the aircraft on the radio as you hear the call. It plays a short sequence for what that aircraft is doing: wide, a detail, wide again; on the runway and on final it opens with a camera beside the runway.
- Follow selected stays on the selected card’s aircraft.
- Tower looks from the airport’s tower. With an aircraft selected it turns with that aircraft. With none, it stands at the tower aimed at the middle of the airfield, and the Tower look buttons turn it: ⟲ ⟳ left and right, ▲ ▼ up and down, + − zoom; hold one to keep turning. The aim eases after each turn (
towerEase, 0.35 s). Swing makes it look round by itself, one slow swing side to side (110°, 70 s there and back).POST /api/camera {"mode":"look","yaw":4,"tilt":0,"fov":0}turns it;{"mode":"look","swing":true}swings. Keys turn it too (arrows, + and −, Shift faster), and so does the middle mouse button held and dragged (with the wheel while held for zoom); a double click of the middle button locks the look to the mouse with no button held, until another middle click or Esc. The Angle switch (off by default) draws on the map a cone of where it looks, labelled with its bearing, tilt and field of view;GET /api/cameragives them aslook. The tower is where you placed it (Airport tab: Tower, saved per airport in the user cache folder,towers.json). Otherwise it is the airport’s known tower (airport.Limits.Tower: LKPR’s facility position is not its tower), then the facility’sTOWER_LATITUDE/TOWER_LONGITUDE(TOWER_ALTITUDEis the ground there, not the cab), then over the airport reference point. The cab is 100 m above the airfield unless you set another height (10–250 m).GET/POST /api/tower. - When our camera is released, the simulator’s camera is set back to what it was before (live, it otherwise drops into chase).
- Simulator camera gives the camera back to the simulator and sets its own:
CAMERA STATEcockpit (2), chase (3), drone (4), fixed on plane (5), free (6, environment), written as INT32 likeexamples/set-variables. Measured live in MSFS 2024: the numbering on the 2024 SDK page (drone 8, showcase 7, follow traffic 23) was refused. ◀ ▶ step through the camera’s views (CAMERA VIEW TYPE AND INDEX:1) from the one it is on. API:POST /api/camera {"mode":"sim","sim":"drone"}or{"mode":"sim","step":1}. - 🎬 Scene plays a scripted film. A scene spawns its cast, listens to one of them on the radio, and plays beats: each waits for its cue, then cuts to its shots. If a beat’s shots end before the next cue, more shots of the same aircraft fill the gap.
Scenes are JSON files in -scenes (default scenes/), read on every play, so a scene can be edited and played again without a restart.
{
"name": "DEMO007 departure",
"cast": [{ "role": "dep", "kind": "departure", "tail": "DEMO007", "models": "csa-lines, a320", "tug": true, "procedure": true, "pushAfterSec": 35 }],
"listen": "dep",
"beats": [
{ "cue": "start", "shots": [{ "move": "crane", "who": "dep", "sec": 10 }] },
{ "cue": "dep:lights:B", "shots": [{ "move": "topOrbit", "who": "dep", "sec": 10 }] },
{ "cue": "dep:departing", "shots": [{ "move": "runwaySide", "who": "dep", "along": 900, "right": 90, "fov": 40, "fovTo": 22, "sec": 7 }] },
{ "cue": "dep:height:1500", "end": true, "shots": [{ "move": "chaseRise", "who": "dep", "sec": 10 }] }
]
}
Cues
A cue holds once reached, so a beat is not missed when two things happen close together.
| Cue | When it holds |
|---|---|
start |
At once. |
t:SECONDS |
That long after the scene began. |
ROLE:STATE |
The aircraft has reached that controller state, e.g. dep:pushback, dep:taxiing, arr:rollout. |
ROLE:airborne |
The departure has left the ground. |
ROLE:lights:X |
That light has come on: N nav, B beacon, S strobe, T taxi, L landing, O logo, W wing. |
ROLE:height:FEET |
The departure has climbed that high above the runway. |
ROLE:heard |
A call to or from the aircraft has been heard since the last beat. |
A beat’s listen switches the radio to that aircraft’s frequency from then on. move is one of the drone moves above (revealRise, topOrbit, mainGear, …), crane (the opening), or runwaySide / underApproach (fixed beside the runway, zooming from fov to fovTo).
GET /api/camera mode, subject, shot
POST /api/camera {mode, id} off | follow (id: the card) | auto
GET /api/camera/scenes the scenes
POST /api/camera/scene?name=&icao= play one