Skip to content

Input

The InputManager (packages/engine/src/input/input-manager.ts) maps raw device events to named actions so your gameplay code asks “is jump pressed?” rather than “is Space down?”. It’s polled once per frame in the input phase.

Devices map through named actions to per-frame queries
Raw devices → named actions → queries.
await input.loadMap('inputmaps/default.inputmap.json');
// each frame:
input.poll(); // at the start (input phase)
if (input.isPressed('jump')) { /* … */ }
if (input.isJustPressed('attack')) { /* … */ }
input.endFrame(); // at the end — clears per-frame diffs

poll() reads gamepad state and computes the just-pressed / just-released edges for this frame; endFrame() clears those per-frame diffs. The engine runtime wires this for you.

  • isPressed(action) — held this frame.
  • isJustPressed(action) — became pressed this frame.
  • isJustReleased(action) — became released this frame.
  • getMouseDelta() — accumulated mouse movement this frame.
  • setLookStick(x, y) — feed a virtual look stick (e.g. from on-screen touch controls); its value is injected as mouse delta during poll().
  • registerAction(name, bindings) — define an action in code.
  • requestPointerLock(element) — request pointer lock (for FPS-style mouse look).
  • setEnabled(enabled) / isEnabled() — gate input delivery without removing listeners; disabling flushes held keys and buttons so nothing sticks on re-enable (the editor uses this to isolate play-mode input).
  • dispose() — remove listeners.

Keydown events are also ignored while a text field (input, textarea, or a contenteditable element) has focus, so typing a name into an editor panel or an in-game chat box never triggers game actions.

An input map is a versioned file of actions, each with a list of bindings. The Zod schema (packages/engine/src/input/schema.ts) validates every map at loadMap — a malformed file throws an AssetParseError naming the file, instead of failing deep inside a query method:

BindingFields
keycode (from KeyboardEvent.code, e.g. "KeyW")
mouse_buttonbutton (0 = left, 1 = middle, 2 = right)
gamepad_buttonbutton
gamepad_axisaxis, direction (positive / negative), deadzone?

Two declared binding kinds — mouse_move and gamepad_axis_pair — have no runtime reader (no query method ever handles them), so authoring either one throws at parse (the dead-field rule). Mouse look is read out-of-band via getMouseDelta()/setLookStick(), not through a binding.