InputState
Since 0.6
Present on ctx.input only for the player entity.
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
| Member | Signature | Description |
|---|---|---|
axis | axis(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). |
axisParts | axisParts(name: string): AxisParts | 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. |
action | action(name: string): ActionState | |
pressed | pressed(name: string): boolean | |
held | held(name: string): boolean | |
released | released(name: string): boolean | |
context | readonly context: string | 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. |
setContext | setContext(name: string): boolean | 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. |
actions | actions(): string[] | Every action the live context binds, in name order. What a rebinding screen lists. |
bindings | bindings(action: string): ActionBinding | null | 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. |
rebind | rebind(action: string, binding: Binding): void | 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. |
resetBindings | resetBindings(): void | Drops 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>