Talos docs

InputState

Since 0.6

Present on ctx.input only for the player entity.

Plain text

Action names come from the scene's action map, meta.input in the scene document (or the built-in map when the scene has none). A behavior reads actions and never keys, which is what makes rebinding a JSON edit and hot reload rebind a running scene. An action the live context does not bind reads neutral — never an error, so a behavior needs no guard.

Reached in a behaviour as ctx.input.

Signature

export interface InputState {
  /**
   * `"move"` and `"look"` are always defined; each component is in -1..1. The mouse is folded in
   * as a rate, so for turning a view read {@link InputState.axisParts} instead (the built-in
   * camera rigs do).
   */
  axis(name: string): Vec2
  /**
   * The same axis split into its stick `rate` and its mouse `delta` (an angle in degrees). A
   * camera turns by `rate × speed × dt + delta`: the mouse then turns exactly as far as the hand
   * moved, with no top speed.
   */
  axisParts(name: string): AxisParts
  action(name: string): ActionState
  pressed(name: string): boolean
  held(name: string): boolean
  released(name: string): boolean
  /**
   * The live context of the scene's action map, e.g. `"gameplay"` or `"menu"`.
   *
   * Scene-wide, not per entity: "the menu is open" is a fact about the scene, so every
   * behavior sees the same answer.
   */
  readonly context: string
  /**
   * Switch the whole scene to another context. `false`, and nothing changes, when the map has
   * no context of that name. The switch lands on the next tick.
   */
  setContext(name: string): boolean
  /** Every action the live context binds, in name order. What a rebinding screen lists. */
  actions(): string[]
  /**
   * What the live context binds `action` to right now, or null when it binds no such action.
   *
   * Reports the map as played, the player's own overrides included, so a rebinding screen and the
   * game can never disagree about which key jump is on.
   */
  bindings(action: string): ActionBinding | null
  /**
   * Binds one action of the live context to something else, and remembers it.
   *
   * The change lands on the map at once, survives {@link SessionHandle.restart}, and is written to
   * the overrides file the runtime was launched with — `argus run --input-overrides <file>`, or
   * `<project>/.argus/input.json` under the editor's Play. A run given no file keeps the rebind
   * until the process exits and says so on stderr. The scene document is never touched: `meta.input`
   * is the author's map and a player remapping their keys is not an edit to it.
   *
   * **A name nothing answers to is refused.** A typo that bound nothing would leave the player with
   * a dead action and no way to find out why, so this throws an `Error` whose `name` is
   * `InputBindingError` and whose message opens with the reason — `input-unknown-key`,
   * `input-unknown-binding`, `input-unknown-action` or `input-binding-arity`. Nothing changes when
   * it throws.
   */
  rebind(action: string, binding: Binding): void
  /** Drops every override, going back to the scene's own map, and empties the overrides file. */
  resetBindings(): void
}

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

Members

MemberSignatureDescription
axisaxis(name: string): Vec2"move" and "look" are always defined; each component is in -1..1. The mouse is folded in as a rate, so for turning a view read {@link InputState.axisParts} instead (the built-in camera rigs do).
axisPartsaxisParts(name: string): AxisPartsThe same axis split into its stick rate and its mouse delta (an angle in degrees). A camera turns by rate × speed × dt + delta: the mouse then turns exactly as far as the hand moved, with no top speed.
actionaction(name: string): ActionState
pressedpressed(name: string): boolean
heldheld(name: string): boolean
releasedreleased(name: string): boolean
contextreadonly context: stringThe live context of the scene's action map, e.g. "gameplay" or "menu". Scene-wide, not per entity: "the menu is open" is a fact about the scene, so every behavior sees the same answer.
setContextsetContext(name: string): booleanSwitch the whole scene to another context. false, and nothing changes, when the map has no context of that name. The switch lands on the next tick.
actionsactions(): string[]Every action the live context binds, in name order. What a rebinding screen lists.
bindingsbindings(action: string): ActionBinding | nullWhat the live context binds action to right now, or null when it binds no such action. Reports the map as played, the player's own overrides included, so a rebinding screen and the game can never disagree about which key jump is on.
rebindrebind(action: string, binding: Binding): voidBinds one action of the live context to something else, and remembers it. The change lands on the map at once, survives {@link SessionHandle.restart}, and is written to the overrides file the runtime was launched with — argus run --input-overrides <file>, or <project>/.argus/input.json under the editor's Play. A run given no file keeps the rebind until the process exits and says so on stderr. The scene document is never touched: meta.input is the author's map and a player remapping their keys is not an edit to it. A name nothing answers to is refused. A typo that bound nothing would leave the player with a dead action and no way to find out why, so this throws an Error whose name is InputBindingError and whose message opens with the reason — input-unknown-key, input-unknown-binding, input-unknown-action or input-binding-arity. Nothing changes when it throws.
resetBindingsresetBindings(): voidDrops every override, going back to the scene's own map, and empties the overrides file.

Example

The tested fixture fixtures/recipes/best-score-save/scenes/behaviors/best-score-save.ts.

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

/**
 * A best score that outlives the run, kept with `ctx.save`.
 *
 * Space scores, Return ends the round, and the best is saved before the round restarts.
 *
 * `ctx.state` is wiped by `ctx.session.restart()`, by `loadScene` and by quitting; `ctx.save` is
 * not. So the best is read from the save in `init` — which runs again after every restart — and
 * written there the moment it is beaten, never kept only in state.
 */
interface State {
  score: number
  best: number
}

/** One store per application, so a key is worth namespacing: 1–64 of `A-Z a-z 0-9 . _ -`. */
const BEST = "best-score.best"

export default {
  init(ctx) {
    ctx.state.score = 0
    ctx.state.best = Number(ctx.save.get(BEST) ?? 0) || 0
    ctx.hud.score(0)
    ctx.hud.set("best", ctx.state.best)
  },

  update(ctx) {
    if (ctx.input !== null && ctx.input.pressed("score")) {
      ctx.state.score += 1
      ctx.hud.score(ctx.state.score)
    }
    if (ctx.input !== null && ctx.input.pressed("finish")) {
      if (ctx.state.score > ctx.state.best) {
        ctx.state.best = ctx.state.score
        ctx.save.set(BEST, ctx.state.best)
      }
      // Lands at the end of this tick; the save is written at the end of it too, first.
      ctx.session.restart()
    }
  },
} satisfies Behavior<State>