Camera
A streamlined, code-driven camera system: one-call rigs for the common camera styles, with camera
blending, procedural noise (handheld sway), and impulse shake — and no draw() plumbing.
You create a rig once; the engine ticks the camera each frame and pushes the view/projection itself.
import "engine" for Scene, Cam, Ease, Impulse
class Game { construct new() {} init() { _scene = Scene.new() _player = _scene.addNode("player") // One line. No camera node, no setViewMatrix, no aspect math. _cam = Cam.thirdPerson(_scene, _player).distance(6).shoulder(0.6, 1.6).damping(0.25) } update(dt) { /* move _player; the camera follows */ } draw() { _scene.draw() } // that's it}Rigs are driven by a per-scene director that solves the active rig at the end of each frame (after
your update, so it tracks final transforms) and writes the view/projection. It coexists with the
manual Graphics.setViewMatrix path — a scene with no rig behaves exactly as before,
and a manual push in draw() overrides the director that frame (last-write-wins). See the
Camera Showcase example.
Static factory. Each Cam.<preset> creates a real camera + node in the scene and returns a
CameraRig; the first rig created for a scene becomes the live camera automatically.
Cam.thirdPerson(scene, target)
Section titled “Cam.thirdPerson(scene, target)”Returns: CameraRig — An over-the-shoulder follow camera that booms out behind target and faces it.
Parameters:
scene(Scene) — The scene to drive.target(Node) — The node the camera follows.
Cam.firstPerson(scene, head)
Section titled “Cam.firstPerson(scene, head)”Returns: CameraRig — Hard-locked to head (an eye node) with POV mouse-look.
Cam.topDown(scene, target, height)
Section titled “Cam.topDown(scene, target, height)”Returns: CameraRig — Looks down at target from height metres above (strategy/RTS).
Cam.orbit(scene, target, radius)
Section titled “Cam.orbit(scene, target, radius)”Returns: CameraRig — Orbits target on a ring of radius, heading/elevation driven by the mouse.
Cam.freeFly(scene)
Section titled “Cam.freeFly(scene)”Returns: CameraRig — A WASD + right-drag mouse-look fly camera (no target). The direct replacement
for a hand-rolled free camera.
Cam.setActive(rig)
Section titled “Cam.setActive(rig)”Makes rig the live camera immediately (an instant cut).
Cam.blendTo(rig, seconds, ease)
Section titled “Cam.blendTo(rig, seconds, ease)”Eases from the live camera to rig over seconds, using an Ease curve (position lerp +
rotation slerp + lens crossfade).
Cam.active(scene)
Section titled “Cam.active(scene)”Returns: CameraRig or null — The rig currently driving scene’s camera.
CameraRig
Section titled “CameraRig”A live camera rig. Every setter returns the rig, so calls chain. Which setters are meaningful depends on
the rig kind (e.g. radius for orbit, height for top-down, moveSpeed for free-fly).
Stability: the
Camfactory, the five named rigs,Ease,Impulse, and blend behaviour are a frozen contract (ADR 0058). TheCameraRigsetter parameter names/order are experimental for one release while they settle — pin your engine version if you depend on their exact shape.
Shared setters: distance(d) (boom length) · damping(tau) (smoothing time-constant in seconds;
0 = locked) · offset(x, y, z) · fov(radians) · near(n) · far(f) · sensitivity(s) (radians of
look per pixel) · noise(amplitude, frequency) (handheld sway; 0 clears) · collider(padding)
(pull the camera in front of any wall that would occlude the target, keeping padding metres of
clearance — a masked physics raycast; see the collider example) ·
setActive() · blendTo(seconds, ease).
Per-rig setters: shoulder(x, y) (third-person) · height(h) (top-down) · radius(r) (orbit) ·
moveSpeed(u) (free-fly).
Easing curves for blends (integer constants): Ease.linear · Ease.easeIn · Ease.easeOut ·
Ease.easeInOut (smoothstep) · Ease.cut (instant).
Impulse
Section titled “Impulse”Impulse.emit(scene, strength, x, y, z)
Section titled “Impulse.emit(scene, strength, x, y, z)”Shakes the scene’s live camera with a decaying, distance-attenuated impulse originating at the world point
(x, y, z) — for hits and explosions. Concurrent impulses sum.
onPlayerHit(dmg, hx, hy, hz) { Impulse.emit(scene, 0.4 + dmg * 0.05, hx, hy, hz) }SplitScreen
Section titled “SplitScreen”Local co-op split-screen. SplitScreen.new(scene, players) gives each player its own third-person
camera in its own screen region; the renderer draws the scene once per viewport with a divider
between them. With dynamic merge/split the views fuse into one group-framed camera when the
players cluster and split back apart when they spread — see the
split-screen example.
import "engine" for Scene, SplitScreen, SplitLayout
var split = SplitScreen.new(scene, [p1, p2, p3, p4]) // players = List of Nodesplit.layout(SplitLayout.grid) // auto / horizontal / vertical / gridsplit.distance(7)split.height(2)split.dynamic(true) // merge/split by proximitysplit.mergeDistance(5) // spread below this → merge to one viewsplit.splitDistance(11) // spread above this → split apart (hysteresis)split.transitionDuration(0.6) // animate the merge/split over 0.6s (0 = instant)SplitScreen.new(scene, players)
Section titled “SplitScreen.new(scene, players)”Returns: SplitScreen. players is a List of Node — one third-person camera is
created per player. The first SplitScreen for a scene takes over rendering (the single-camera path
is suspended while it is active).
Methods
Section titled “Methods”layout(mode)— a SplitLayout: how the regions are arranged.distance(d)·height(h)— boom length / eye height applied to every player camera.dynamic(on)—Bool; enable proximity-based merge/split.mergeDistance(d)/splitDistance(d)— the hysteresis band: players withinmergeDistancemerge to one group-framed view; spreading pastsplitDistancesplits them apart.transitionDuration(s)— seconds the merge↔split blend takes. The cells hold in place while each camera eases from its player pose to the shared group pose and the dividers fade, then collapse to one view.0is the old instant hard-switch. Default0.4.compositeMode(on)—Bool; force the RTT composite path (each viewport renders to an offscreen target, then composites) instead of the axis-aligned sub-rect path. Required for overlap / picture-in-picture and enabled automatically bySplitLayout.angled. The composited disjoint split is pixel-equivalent to the fast path, so turning it on is always safe.peel(on)—Bool; the animated rect-peel merge. While a merge is in flight, slide each viewport’s rect from its split cell toward fullscreen (z-stacked, over the composite path) — the Kronnect “views expand together” look — instead of the default fixed-rect camera-converge blend. See/examples/peel.perPlayerInput(on)—Bool(default on); player i reads gamepad i (right stick = look) instead of the shared mouse/keyboard — real split-screen co-op. Falls back to mouse/keyboard for any player whose gamepad is not connected;offroutes every player to the shared input.isMerged—Boolgetter; has the target state settled on merged?mergeFraction—Numgetter,0..1; the animated blend progress (0split →1merged). Handy for a HUD or to gate logic on the transition.playerCount—Numgetter.
SplitLayout
Section titled “SplitLayout”Split arrangements (integer constants): SplitLayout.auto (by count: 2 → side-by-side, 3/4 → grid) ·
SplitLayout.horizontal (columns) · SplitLayout.vertical (rows) · SplitLayout.grid (2×2 for 4) ·
SplitLayout.angled (2-player diagonal split — the divider is perpendicular to the on-screen
direction between the players, so it rotates as they move around each other, Kronnect-style; each
player renders full-screen and the renderer composites the two half-planes — it auto-enables
compositeMode). See /examples/angled-split.
Scope (v1): up to 4 composited viewports; overlays (sprites/text/UI) draw once full-screen. The merge/split is an animated camera-converge blend by default (
transitionDuration/mergeFraction, ADR 0060), with the RTT rect-peel (peel) + angled split available (ADR 0061). See ADR 0059–0061.
Viewport
Section titled “Viewport”A first-class composite viewport (ADR 0062) for picture-in-picture / minimap / security-cam —
independent of SplitScreen. Each Viewport owns its own camera; the renderer composites it at its
rect/z/opacity/border over the RTT composite path. Keep using Cam for the main view
and add Viewports on top (the main view becomes the fullscreen base), or build a fullscreen base
Viewport yourself.
import "engine" for Scene, Cam, Viewport
var cam = Cam.thirdPerson(scene, player) // the main view (fullscreen base)
var mini = Viewport.new(scene) // a top-down minimap in the top-right cornermini.rect(0.72, 0.04, 0.24, 0.24) // screen-fraction sub-rect (0..1)mini.topDown(player, 34) // straight-down camera 34 units upmini.border(3)mini.z(1) // above the main viewViewport.new(scene)
Section titled “Viewport.new(scene)”Returns: Viewport. Renders scene from its own camera into its sub-rect. Goes out of scope →
its camera is removed and it stops rendering.
Methods (fluent)
Section titled “Methods (fluent)”rect(x, y, w, h)— screen-fraction sub-rect (0..1); default fullscreen.z(order)— composite order (higher = on top; the mainCamview sits behind).opacity(a)—0..1composite alpha (cross-fade / ghost overlays).border(width)/borderColor(r, g, b)— a frame around the viewport (px;0= none).- Camera (pick one):
follow(target)(third-person) ·topDown(target, height)(minimap) ·orbit(target, radius)·firstPerson(head)·freeFly().
See the picture-in-picture example.
The Camera class (low-level)
Section titled “The Camera class (low-level)”View and projection. Attach to a Node via setNode(node) so the camera’s position and orientation follow the node. Most games use the rigs above instead — the Cam factory creates and drives a Camera for you — but you can drive a Camera yourself for full manual control.
Constructor
Section titled “Constructor”Camera.new()
Section titled “Camera.new()”Returns: Camera — A new camera. Attach it to a node with setNode and set a tag so Scene.findCameraByTag can find it.
Tag & node
Section titled “Tag & node”| Method / property | Returns / Parameters | Description |
|---|---|---|
getTag() | String | Tag string (e.g. for findCameraByTag) |
setTag(tag) | — | tag (String) — Set the tag |
node | Node or null | Node this camera is attached to |
setNode(node) | — | node (Node) — Attach camera to this node |
The camera’s view matrix is derived from the node’s world transform. Set the node’s position/rotation to move the camera.
View & projection
Section titled “View & projection”| Method | Returns | Parameters | Description |
|---|---|---|---|
getViewMatrix() | (matrix) | — | View matrix (world → view space) |
getProjectionMatrix(aspect) | (matrix) | aspect (Num) — width/height | Projection matrix for the given aspect ratio |
getFovYRadians() | Num | — | Vertical FOV in radians |
setFovYRadians(r) | — | r (Num) | Set vertical FOV (radians) |
getNearPlane() | Num | — | Near clip plane distance |
setNearPlane(n) | — | n (Num) | Set near plane |
getFarPlane() | Num | — | Far clip plane distance |
setFarPlane(f) | — | f (Num) | Set far plane |
Use with Graphics.setViewMatrix and Graphics.setProjectionMatrix when drawing the scene.
var aspect = Window.getWidth() / Window.getHeight()var view = _camera.getViewMatrix()var proj = _camera.getProjectionMatrix(aspect)Graphics.setViewMatrix(view)Graphics.setProjectionMatrix(proj)_scene.draw()Coordinate conversion
Section titled “Coordinate conversion”worldToScreen(worldX, worldY, worldZ, viewportWidth, viewportHeight)
Section titled “worldToScreen(worldX, worldY, worldZ, viewportWidth, viewportHeight)”Returns: List of three numbers [screenX, screenY, depth] or null if the point is behind the camera.
Parameters:
worldX,worldY,worldZ(Num) — World-space position.viewportWidth,viewportHeight(Num) — Viewport size in pixels.
Convert a world position to screen coordinates. screenX, screenY are in pixel space; depth is the depth buffer value or distance.
screenToWorld(screenX, screenY, depth, viewportWidth, viewportHeight)
Section titled “screenToWorld(screenX, screenY, depth, viewportWidth, viewportHeight)”Returns: List of three numbers [worldX, worldY, worldZ] — World position at the given screen point and depth.
Parameters:
screenX,screenY(Num) — Screen coordinates (e.g. mouse position).depth(Num) — Depth value or distance (e.g. 0 = near plane, 1 = far plane, or linear depth depending on implementation).viewportWidth,viewportHeight(Num) — Viewport size.
Convert a screen point and depth back to world space (e.g. for placing objects at the cursor).