Talos docs

CameraHandle

Since 0.6Changed in 0.7

The active camera rig.

Plain text

While a script drives it — it follows something, or a "fixed" rig was given a position — the rig is the rendered view and the listener; otherwise the scene's primary camera entity is.

Reached in a behaviour as ctx.camera.

Signature

export interface CameraHandle {
  /** Switch rigs; `params` are the rig's settings, per kind: see {@link CameraParams}. */
  set(kind: CameraKind, params?: CameraParams): void
  /**
   * What the rig tracks. `null` returns the view to the scene's primary camera and forgets every
   * `set` param, `fov` included.
   */
  follow(target: EntityHandle | string | null): void
  /**
   * Jolt the view up to `amplitude` m, decaying over `seconds` (0.4) at `frequency` Hz (25).
   */
  shake(amplitude: number, seconds?: number, frequency?: number): void
  /** The engine rig's yaw now, radians: the last `set`'s, or where a `recenterDelay` swing took it. */
  readonly yaw: number
  /** The engine rig's pitch now, radians; positive looks up. */
  readonly pitch: number
  /**
   * The heading of the view being drawn, radians about +Y with 0 facing -Z (the rig, else the scene camera;
   * 0 with none), measured at the top of the tick. The frame an `inputBasis: "camera"` controller reads its
   * stick in: forward is `[-sin(viewYaw), 0, -cos(viewYaw)]`.
   */
  readonly viewYaw: number
  /**
   * The vertical field of view being drawn, degrees: what a script set with `set(kind, { fov })`,
   * else the scene's primary camera's `fov`, else 60. A rig that names no fov of its own (the
   * built-in first-person rig by default) draws at this.
   */
  readonly fov: number
  /**
   * The box an orthographic lens frames, measured in the reference view (1920 x 1080), never the window
   * ({@link ViewSize}); `null` while the lens is perspective. The lens is read live. Because it is the same at
   * any window size, gameplay may use it (a clamp, "keep the ship on screen") and a replay or a co-op peer at
   * another size plays the same game. The drawn view always contains the box; a window that is not 16:9
   * shows more of the world beyond it on one axis.
   */
  readonly viewSize: ViewSize | null
  /**
   * The world ray through pixel `(x, y)` of view `view`'s cell (default 1, the view `ctx.camera` is): `origin`
   * on the near plane and a unit `dir`. Pixels are the cell's own, top-left 0,0, in the viewport
   * {@link ViewSize} reports (`pixelWidth` x `pixelHeight`, divided among the cells of a split-screen layout
   * as `ctx.views` draws them). It goes through the matrices the renderer draws that view with, as of the top of
   * the tick (like `viewYaw`), so the pixel you aim at is the pixel you see. `null` when that view has no
   * camera or `x`/`y` is not a number; a pixel outside the cell extrapolates. Throws `ViewsError`
   * `views-out-of-range` for a view outside 1 to 4.
   *
   * Under the mouse: `const p = ctx.input.pointer; if (p) ctx.camera.screenToRay(p.x, p.y, p.view)`. The
   * pointer is simulation input and is recorded; the ray is cast through the camera the run draws, so a
   * replay at another window size aims through that size's view of the same pointer pixel.
   */
  screenToRay(x: number, y: number, view?: number): Ray | null
  /**
   * The first collider along `screenToRay(x, y, opts.view)`, as `ctx.world.raycast` answers it. Triggers are
   * seen through unless `opts.triggers` is true. With `opts.ground` (a height), a ray that hits no collider
   * meets the plane y = ground instead, answered with `entity: null` and normal `[0, 1, 0]`. `null` for a miss,
   * no ray, or a ray parallel to or pointing away from the plane.
   */
  pick(x: number, y: number, opts?: CameraPickOptions): PickHit | null
  /**
   * The pixel of view `view`'s cell (default 1) that world `point` is drawn at: `screenToRay`'s inverse, in the
   * same pixels and through the same matrices. `null` when that view has no camera or the point is at or behind
   * the eye. A point in front of the camera but off screen is **not** null: it answers pixels outside the
   * cell (negative, or beyond `pixelWidth` / `pixelHeight`), so test the range yourself to know whether it is visible.
   */
  worldToScreen(point: Vec3, view?: number): { x: number; y: number } | null
  /** The engine rig's boom this frame: its length, whether a wall collapsed it, what did, and the character fade. */
  readonly arm: CameraArm
}

Import: import type { CameraHandle } from "@argus/api"

Members

MemberSignatureDescription
setset(kind: CameraKind, params?: CameraParams): voidSwitch rigs; params are the rig's settings, per kind: see {@link CameraParams}.
followfollow(target: EntityHandle | string | null): voidWhat the rig tracks. null returns the view to the scene's primary camera and forgets every set param, fov included.
shakeshake(amplitude: number, seconds?: number, frequency?: number): voidJolt the view up to amplitude m, decaying over seconds (0.4) at frequency Hz (25).
yawreadonly yaw: numberThe engine rig's yaw now, radians: the last set's, or where a recenterDelay swing took it.
pitchreadonly pitch: numberThe engine rig's pitch now, radians; positive looks up.
viewYawreadonly viewYaw: numberThe heading of the view being drawn, radians about +Y with 0 facing -Z (the rig, else the scene camera; 0 with none), measured at the top of the tick. The frame an inputBasis: "camera" controller reads its stick in: forward is [-sin(viewYaw), 0, -cos(viewYaw)].
fovreadonly fov: numberThe vertical field of view being drawn, degrees: what a script set with set(kind, { fov }), else the scene's primary camera's fov, else 60. A rig that names no fov of its own (the built-in first-person rig by default) draws at this.
viewSizereadonly viewSize: ViewSize | nullThe box an orthographic lens frames, measured in the reference view (1920 x 1080), never the window ({@link ViewSize}); null while the lens is perspective. The lens is read live. Because it is the same at any window size, gameplay may use it (a clamp, "keep the ship on screen") and a replay or a co-op peer at another size plays the same game. The drawn view always contains the box; a window that is not 16:9 shows more of the world beyond it on one axis.
screenToRayscreenToRay(x: number, y: number, view?: number): Ray | nullThe world ray through pixel (x, y) of view view's cell (default 1, the view ctx.camera is): origin on the near plane and a unit dir. Pixels are the cell's own, top-left 0,0, in the viewport {@link ViewSize} reports (pixelWidth x pixelHeight, divided among the cells of a split-screen layout as ctx.views draws them). It goes through the matrices the renderer draws that view with, as of the top of the tick (like viewYaw), so the pixel you aim at is the pixel you see. null when that view has no camera or x/y is not a number; a pixel outside the cell extrapolates. Throws ViewsError views-out-of-range for a view outside 1 to 4. Under the mouse: const p = ctx.input.pointer; if (p) ctx.camera.screenToRay(p.x, p.y, p.view). The pointer is simulation input and is recorded; the ray is cast through the camera the run draws, so a replay at another window size aims through that size's view of the same pointer pixel.
pickpick(x: number, y: number, opts?: CameraPickOptions): PickHit | nullThe first collider along screenToRay(x, y, opts.view), as ctx.world.raycast answers it. Triggers are seen through unless opts.triggers is true. With opts.ground (a height), a ray that hits no collider meets the plane y = ground instead, answered with entity: null and normal [0, 1, 0]. null for a miss, no ray, or a ray parallel to or pointing away from the plane.
worldToScreenworldToScreen(point: Vec3, view?: number): { x: number; y: number } | nullThe pixel of view view's cell (default 1) that world point is drawn at: screenToRay's inverse, in the same pixels and through the same matrices. null when that view has no camera or the point is at or behind the eye. A point in front of the camera but off screen is not null: it answers pixels outside the cell (negative, or beyond pixelWidth / pixelHeight), so test the range yourself to know whether it is visible.
armreadonly arm: CameraArmThe engine rig's boom this frame: its length, whether a wall collapsed it, what did, and the character fade.

Example

The tested fixture fixtures/recipes/jump-pad-and-elevator/scenes/behaviors/camera-zone.ts.

import type { Behavior } from "@argus/api"

/**
 * A trigger volume that retunes the camera while the player is inside it.
 *
 * The engine has no camera-zone concept and needs none: a volume, a `trigger` component and this.
 * Here the player's behaviour owns the camera (it re-sends its params when the view is turned), so
 * the zone tells it what to change rather than calling `ctx.camera.set` itself. In a scene where
 * nothing else writes the camera, the zone can call `ctx.camera.set(kind, params)` directly.
 */
interface Props {
  /** `ctx.camera.set` params to merge in on the way in, and on the way out. */
  inside?: Record<string, unknown>
  outside?: Record<string, unknown>
}

export default {
  onTrigger(ctx, trigger) {
    const params = trigger.entering ? ctx.props.inside : ctx.props.outside
    if (params) ctx.events.emit("camera-zone", { target: trigger.other.id, params })
  },
} satisfies Behavior<Record<string, never>, Props>