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, T4.6) validates every map at loadMap β€” a malformed file throws a SceneParseError 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 (T4.1’s dead-field rule). Mouse look is read out-of-band via getMouseDelta()/setLookStick(), not through a binding.