Builtin Library
README.md
orbital_follow
Three-ring orbit camera. Framing is authored as three rings around the follow target — a bottom, a centre and a top, each with a height above the target's origin and a radius out from its axis. Vertical look slides the camera along the spline through those rings; horizontal look orbits around them. The shape of the orbit is geometry you can see and edit rather than a pair of pitch limits you cannot.
Public fields: follow, ringBottom / ringCenter / ringTop (each { height, radius }), verticalAxis (0 at the bottom ring, 1 at the top), horizontalAxis (the world heading the camera looks along, in radians about +Y from world +Z), radiusScale, aimHeight (the aim point above the target's origin), damping (per-axis time constants in seconds of the camera's offset from its subject: they soften a change of framing, a look, a zoom or a pull-in, and the subject's own motion carries the camera at any speed), cameraRadius (radius of the pull-in sweep; 0 turns the pull-in off), cameraIgnore (entities whose colliders the pull-in passes through), sensitivity, invertY (off by default: pushing the mouse forward looks UP, drawing the camera toward the bottom ring; on pitches the other way), zoomSpeed, minRadiusScale, maxRadiusScale.
The rig owns that heading. Binding a follow target aims it along the subject's facing, so the shot opens from behind whoever the camera is told to follow, and look input turns it from there — the subject's facing is never read again. That is what makes the camera steerable for a character who turns to face where it is walking: hold "forward" and the character walks the line the camera looks down, instead of chasing a heading that moves as it turns.
The default rings frame a roughly 1.8 m humanoid standing at its origin: the centre ring sits above head height at the widest radius for a resting shot from behind and slightly above, the bottom ring drops to knee height and pulls in to look up, and the top ring climbs and tightens for a near-overhead look down.
Writing any framing field re-poses the rig immediately with damping bypassed, so editing a ring moves the camera to the shot that ring describes.
follow falls back to the Camera.follow slot when it is empty, so a rig selected through Camera.behavior tracks whatever that camera already follows.
Controls: mouse to orbit and to slide along the rings (play mode requests pointer lock, so no button is held), scroll to scale the ring radii.
local cam = entity(id).component.get("Camera")
cam.behavior = asset.ref("@builtin::controller.orbital_follow", "component")
entity(id).component.get("controller.orbital_follow").ringCenter = { height = 2.6, radius = 6 }
The camera pulls in when a collider is in the way
Each frame the rig sweeps a sphere of cameraRadius from the point it aims at out to where its framing puts the camera, and stands the camera where the sphere first meets a collider. Every collider in the world stops that sweep, whether or not anything draws it: an invisible walk bound (a BoxCollider with no Model), a trigger volume and a solid wall all pull the camera in alike. The follow subject is the one entity the sweep passes by default.
So a camera that snaps in close to the player, or collapses onto it, while nothing visible stands behind the player is meeting a collider nobody can see. Two fields settle it:
cameraIgnorelists the entities whose colliders the camera sees through. Name the walk bounds and gameplay volumes there and the sweep passes them as it passes the subject, while walls the player can see still pull the camera in. Each entry is an entity (or its id), and it is the collider's own entity that is listed.cameraRadius = 0turns the pull-in off: the camera stands where its framing puts it, through every collider.
local rig = entity(camId).component.get("controller.orbital_follow")
rig.cameraIgnore = { entity.find("NorthBound"), entity.find("SouthBound") }
-- or drop the pull-in entirely
rig.cameraRadius = 0
Attach through Camera.behavior
The Camera owns the behavior slot, so assigning the rig there is what lets the Camera tell it when follow changes. A rig added straight onto the entity with component.add still poses, but the Camera does not know about it and a follow assigned afterwards will not reach it.
Player cameras: leave follow unset
Entering play drops and respawns PlayerPrototype subtrees, so the body is recreated with a NEW entity id. A follow authored on the rig keeps pointing at the original, which is no longer live, and the camera freezes at its last pose instead of tracking the player.
For a player camera, set the target on the Camera and leave the rig's own follow empty:
entity(rigId).component.get("Camera").follow = body -- PlayerPrototype rebinds this on every spawn
PlayerPrototype refreshes Camera.follow to the live body each time it clones, and the rig reads that slot whenever its own follow is empty. Set the rig's follow only for a camera that watches a fixed, non-respawning subject.