Talos docs

Behavior

Since 0.6

What a behavior module exports as default.

Plain text

Every hook is optional. A throw disables this instance and emits script-error; the others keep running. One exception: a stale handle — a DespawnedEntityError, from a handle kept past its entity — thrown from update, pausedUpdate, onCollision, onTrigger, onEvent or a ctx.events.on listener ends that call only; the instance runs again next tick and the error is reported (at most once a second) with disabled: false. In init it disables like any throw. Each hook has a wall-clock budget (2 ms by default); ten consecutive overruns are reported as a script-error (phase budget, disabled: false, at most once every 600 ticks after the first) and the instance keeps running. A runaway loop is still stopped and disables the instance.

Signature

export interface Behavior<S = Record<string, any>, P = Record<string, any>> {
  /** Once after attach, and again after each hot reload (with `state` preserved). */
  init?(ctx: Ctx<S, P>): void
  /** Every fixed tick; `dt` is 1/60 s. Not while the world is held (`ctx.session.pause()`, a `pausesWorld` screen). */
  update?(ctx: Ctx<S, P>, dt: number): void
  /**
   * Every fixed tick while the world is held, in place of `update`: where a pause menu reads its
   * own key, since `update` is not running. `ctx.time.now` stands still; `dt` is still 1/60 s.
   */
  pausedUpdate?(ctx: Ctx<S, P>, dt: number): void
  /**
   * Before detach and before a hot reload swaps the module. `reason` says which: `"detach"` (the entity,
   * or the scene, is going) or `"reload"` (`init` follows, with `state` kept), so an effect that belongs
   * to the entity going away is not run by an edit.
   */
  dispose?(ctx: Ctx<S, P>, reason?: "detach" | "reload"): void
  onCollision?(ctx: Ctx<S, P>, collision: Collision): void
  onTrigger?(ctx: Ctx<S, P>, trigger: TriggerEvent): void
  /**
   * The usual pair, with its usual meaning: `other` is the other participant from this side — the volume
   * hears who entered, and the entrant hears the volume. (In {@link onTrigger}'s payload `other` is always
   * the entrant.) Each runs after `onTrigger` for the same contact.
   */
  onTriggerEnter?(ctx: Ctx<S, P>, other: EntityHandle): void
  /** The other participant left: see {@link onTriggerEnter}. */
  onTriggerExit?(ctx: Ctx<S, P>, other: EntityHandle): void
  /** Any `emit`, from any behavior or from the app. */
  onEvent?(ctx: Ctx<S, P>, event: GameEvent): void
}

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

Members

MemberSignatureDescription
initinit?(ctx: Ctx<S, P>): voidOnce after attach, and again after each hot reload (with state preserved).
updateupdate?(ctx: Ctx<S, P>, dt: number): voidEvery fixed tick; dt is 1/60 s. Not while the world is held (ctx.session.pause(), a pausesWorld screen).
pausedUpdatepausedUpdate?(ctx: Ctx<S, P>, dt: number): voidEvery fixed tick while the world is held, in place of update: where a pause menu reads its own key, since update is not running. ctx.time.now stands still; dt is still 1/60 s.
disposedispose?(ctx: Ctx<S, P>, reason?: "detach" | "reload"): voidBefore detach and before a hot reload swaps the module. reason says which: "detach" (the entity, or the scene, is going) or "reload" (init follows, with state kept), so an effect that belongs to the entity going away is not run by an edit.
onCollisiononCollision?(ctx: Ctx<S, P>, collision: Collision): void
onTriggeronTrigger?(ctx: Ctx<S, P>, trigger: TriggerEvent): void
onTriggerEnteronTriggerEnter?(ctx: Ctx<S, P>, other: EntityHandle): voidThe usual pair, with its usual meaning: other is the other participant from this side — the volume hears who entered, and the entrant hears the volume. (In {@link onTrigger}'s payload other is always the entrant.) Each runs after onTrigger for the same contact.
onTriggerExitonTriggerExit?(ctx: Ctx<S, P>, other: EntityHandle): voidThe other participant left: see {@link onTriggerEnter}.
onEventonEvent?(ctx: Ctx<S, P>, event: GameEvent): voidAny emit, from any behavior or from the app.

Example

The tested fixture fixtures/app/agent.ts.

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

// Sends a nav agent to `props.target` once, then reports arrival through ctx.state so the
// test can read it without polling the crowd directly.
export default {
  init(ctx) {
    ctx.state.arrived = false
    ctx.nav?.goTo(ctx.props.target as [number, number, number])
  },
  update(ctx) {
    if (ctx.nav?.arrived) ctx.state.arrived = true
  },
} satisfies Behavior