Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit44fd9e59bd

README.md

Camera

Manages viewport priority, render-to-texture, and capture. State is stored in the native Camera ECS component; the Rust camera system handles render scheduling and render targets.

Public fields: fov, near, far, priority, textureHandle (the guid of the texture the camera renders into; empty = main viewport), renderLayers (which render layers this camera draws — a space-separated spec of names, e.g. "all", "all !ui", or "default sky"; ui/sky/debug/EditorUI are built-in layers), postProcessing (whether this camera runs the post-process chain), debugChannel, plus the behavior slot below.

Methods: :lookAt(target) (entity id string OR {x, y, z} table), :render(), :capture(), :setTargetTexture(tex?) (a renderer.texture.create handle to render into, or nil for the viewport).

entity(id).component.add("Camera", { fov = 90, priority = 10 })
entity(id).component.get("Camera"):capture()

The scene's play-mode camera is reached as layers.active.camera, a handle that reads and writes the fields above on whichever entity currently carries them. layers.active.camera.entity is the entity ref for that camera and layers.active.camera.entityId its id string, so a script that needs to attach something to the camera — an AudioListener, a child entity — goes through the ref:

local cam = layers.active.camera
cam.fov = 70                                  -- the camera's settings
cam.entity.component.add("AudioListener")     -- the entity carrying them

How the camera moves: behavior and follow

A Camera does not move itself. behavior names a component that does, and setting it attaches that component to this entity. Clearing it detaches whatever was attached.

local cam = entity(id).component.get("Camera")
cam.behavior = asset.ref("@builtin::controller.orbital_follow", "component")
cam.follow = playerBody

The attached behavior is an ordinary component on the camera's entity, so it is tuned the way any component is: read it with component.get and set its fields there. A component.add of the same type configures the instance the slot attached instead of adding a second one: the values it passes are written onto that instance and its proxy is returned. That add is the form a scene build.luau tunes the behavior with, because its values are saved with the scene and the loaded Camera's behavior takes them back. A key the component reads only as it is created (one of its initKeys, a private field) refuses the add and names the key.

cam.behavior = asset.ref("@builtin::controller.third_person_follow", "component")
local rig = entity(id).component.get("third_person_follow")
rig.cameraDistance = 6
-- the same, as an add of the type the slot attached
entity(id).component.add("third_person_follow", { cameraDistance = 6 })

follow is the standard slot every shipped behavior reads. Set the follow target on the Camera, not on the behavior, so swapping behaviors keeps it. A behavior that finds its own follow field empty falls back to this one, which is what lets a rig keep tracking the player across a behavior swap.

followResolves answers whether that slot names an entity that is live — true while it names a live one or names nothing at all, false once the target is despawned or the id names no entity. A rig whose target does not resolve holds its last pose, and this is the field that tells it from a rig posed correctly on a subject that has not moved. camera.get carries the same value beside follow, and a write naming an id with no entity behind it draws a warning where it lands.

cam.followResolves                            -- false once the followed entity is gone
tools.use("camera", "get", id).followResolves -- the same answer off the tool

The shipped behaviors live under @builtin::controller.*: orbital_follow, third_person_follow, first_person, free, orbit, chase, isometric, rts, birds_eye, side_scroller, cinematic, menu.

Writing your own

Any component can be a camera behavior. Write one that moves its own entity and attach it the same way:

cam.behavior = asset.ref("MyChaseCam", "component")

follow lives on the Camera, so a behavior gets no property notification of its own when the target changes. Declare onFollowChanged(newFollow, oldFollow) to be told the moment it does — the Camera calls it on the component it attached, which is what lets a rig re-pose on the new subject at once. A behavior that reads Camera.follow on its own schedule declares nothing and is attached the same way.

typed function public:onFollowChanged(newFollow: any, oldFollow: any)
    -- pose this entity against the new target
end

To make it appear in the discovery catalog alongside the shipped ones, declare the cameraBehavior tag in the component's .metadata:

{ "tags": ["cameraBehavior"] }

The tag governs discovery, not attachment. A tagged component is listed by layers.active.camera.behaviors and is what tooling offers when something asks "which camera behaviors exist"; an untagged component attaches just as well and simply stays out of that list. Tag the ones you want other people (and agents) to find.

for name, ref in pairs(layers.active.camera.behaviors) do
    print(name, ref.identity)
end

Texture colour space

textureColorSpace = "display" applies the display transform when rendering into a texture. "linear" writes scene-linear values instead. postProcessing independently controls the effects chain in either mode. Floating-point targets retain values above one; normalized targets clamp to their representable range. The main viewport uses display encoding.