Behavior
What a behavior module exports as default.
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
| Member | Signature | Description |
|---|---|---|
init | init?(ctx: Ctx<S, P>): void | Once after attach, and again after each hot reload (with state preserved). |
update | update?(ctx: Ctx<S, P>, dt: number): void | Every fixed tick; dt is 1/60 s. Not while the world is held (ctx.session.pause(), a pausesWorld screen). |
pausedUpdate | pausedUpdate?(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. |
dispose | dispose?(ctx: Ctx<S, P>, reason?: "detach" | "reload"): 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. |
onCollision | onCollision?(ctx: Ctx<S, P>, collision: Collision): void | |
onTrigger | onTrigger?(ctx: Ctx<S, P>, trigger: TriggerEvent): void | |
onTriggerEnter | onTriggerEnter?(ctx: Ctx<S, P>, other: EntityHandle): 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. |
onTriggerExit | onTriggerExit?(ctx: Ctx<S, P>, other: EntityHandle): void | The other participant left: see {@link onTriggerEnter}. |
onEvent | onEvent?(ctx: Ctx<S, P>, event: GameEvent): void | Any 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