Skip to content

InputSystem

Captures and normalizes keyboard and mouse input events from the browser, then exposes a clean query API accessible directly on this.world inside Behaviour scripts.

Overview

InputSystem manages input state for the current frame and is responsible for:

  • Listening to keydown / keyup DOM events and tracking which keys are currently held, newly pressed (down), or just released (up) this frame.
  • Listening to mousedown / mouseup / mousemove / wheel events for left, middle, and right buttons plus pointer position and scroll deltas.
  • Flushing pending input changes at the start of each frame so getKeyDown / getKeyUp are only true for a single frame.
  • Providing pointer-lock support: when a lock target element is set, mouse position and delta calculations are bounded to that element's rect.

API exposed to scripts

Call these methods directly on this.world from any Behaviour script. There is no world.input accessor.

MethodDescription
this.world.getKey(key)true while the key is held this frame.
this.world.getKeyDown(key)true only on the first frame the key is pressed.
this.world.getKeyUp(key)true only on the frame the key is released.
this.world.getMouse(button)true while the mouse button is held.
this.world.getMouseDown(button)true only on the first frame the button is pressed.
this.world.getMouseUp(button)true only on the frame the button is released.
this.world.getMousePosition(){ x, y } in viewport pixels.
this.world.getMouseDelta(){ x, y } movement delta since the previous frame.
this.world.consumeMouseDelta(){ x, y } movement delta; zeroes after reading.
this.world.getScrollDelta(){ x, y } scroll delta since the previous frame.

Mouse buttons can be specified as "left", "middle", or "right" (or the numeric indices 0, 1, 2).

KeyboardKeys reference

Import KeyboardKeys from @relu-interactives/spatial-ecs to access all available key constants with full TypeScript autocomplete.

ts
import { KeyboardKeys } from "@relu-interactives/spatial-ecs";

Letters

ConstantValue
KeyboardKeys.AKeyboardKeys.Z"a""z"

Digits

ConstantValue
KeyboardKeys.Digit0KeyboardKeys.Digit9"0""9"
ConstantValue
KeyboardKeys.ArrowUp"arrowup"
KeyboardKeys.ArrowDown"arrowdown"
KeyboardKeys.ArrowLeft"arrowleft"
KeyboardKeys.ArrowRight"arrowright"
KeyboardKeys.Space" "
KeyboardKeys.Enter"enter"
KeyboardKeys.Escape"escape"
KeyboardKeys.Tab"tab"
KeyboardKeys.Backspace"backspace"
KeyboardKeys.Delete"delete"
KeyboardKeys.Insert"insert"
KeyboardKeys.Home"home"
KeyboardKeys.End"end"
KeyboardKeys.PageUp"pageup"
KeyboardKeys.PageDown"pagedown"

Modifier keys

Generic variants match either left or right physical key. Use the specific variants when the side matters.

ConstantValueNotes
KeyboardKeys.Shift"shift"Either shift key
KeyboardKeys.ShiftLeft"shiftleft"Left Shift only
KeyboardKeys.ShiftRight"shiftright"Right Shift only
KeyboardKeys.Control"control"Either Ctrl key
KeyboardKeys.ControlLeft"controlleft"Left Ctrl only
KeyboardKeys.ControlRight"controlright"Right Ctrl only
KeyboardKeys.Alt"alt"Alt / Option
KeyboardKeys.Meta"meta"Cmd (macOS) / Win (Windows)
KeyboardKeys.CapsLock"capslock"

Function keys

ConstantValue
KeyboardKeys.F1KeyboardKeys.F12"f1""f12"

Punctuation & symbols

ConstantValue
KeyboardKeys.Minus"-"
KeyboardKeys.Equal"="
KeyboardKeys.BracketLeft"["
KeyboardKeys.BracketRight"]"
KeyboardKeys.Backslash"\\"
KeyboardKeys.Semicolon";"
KeyboardKeys.Quote"'"
KeyboardKeys.Backquote"`"
KeyboardKeys.Comma","
KeyboardKeys.Period"."
KeyboardKeys.Slash"/"

Other

ConstantValue
KeyboardKeys.NumLock"numlock"
KeyboardKeys.ScrollLock"scrolllock"
KeyboardKeys.Pause"pause"
KeyboardKeys.PrintScreen"printscreen"
KeyboardKeys.ContextMenu"contextmenu"

Script examples

Basic movement

Move an object along the X/Z plane with WASD and sprint with Shift.

ts
import { KeyboardKeys, Behaviour } from "@relu-interactives/spatial-ecs";

export default class PlayerMovement extends Behaviour {
  speed = 5;
  sprintMultiplier = 2;

  protected onUpdate() {
    const sprint = this.world.getKey(KeyboardKeys.Shift) ? this.sprintMultiplier : 1;
    const step = this.speed * sprint * this.deltaTime;

    if (this.world.getKey(KeyboardKeys.W)) this.transform.position.z -= step;
    if (this.world.getKey(KeyboardKeys.S)) this.transform.position.z += step;
    if (this.world.getKey(KeyboardKeys.A)) this.transform.position.x -= step;
    if (this.world.getKey(KeyboardKeys.D)) this.transform.position.x += step;
  }
}

One-shot action on key press

Toggle visibility on Space, fire on left-click.

ts
import { KeyboardKeys, Behaviour } from "@relu-interactives/spatial-ecs";

export default class ActionHandler extends Behaviour {
  protected onUpdate() {
    // getKeyDown fires only on the first frame the key is pressed.
    if (this.world.getKeyDown(KeyboardKeys.Space)) {
      this.transform.visible = !this.transform.visible;
    }

    // Left mouse button
    if (this.world.getMouseDown("left")) {
      console.log("Fired!");
    }
  }
}

Distinguishing left vs right modifier

ts
import { KeyboardKeys, Behaviour } from "@relu-interactives/spatial-ecs";

export default class ModifierDemo extends Behaviour {
  protected onUpdate() {
    if (this.world.getKeyDown(KeyboardKeys.ControlLeft)) {
      console.log("Left Ctrl pressed");
    }

    if (this.world.getKeyDown(KeyboardKeys.ControlRight)) {
      console.log("Right Ctrl pressed");
    }

    // Generic: matches either left or right Shift
    if (this.world.getKey(KeyboardKeys.Shift)) {
      console.log("A Shift key is held");
    }
  }
}

Reading mouse delta (camera look)

ts
import { Behaviour } from "@relu-interactives/spatial-ecs";

export default class CameraLook extends Behaviour {
  sensitivity = 0.002;

  protected onUpdate() {
    const { x, y } = this.world.consumeMouseDelta();
    this.transform.rotation.y -= x * this.sensitivity;
    this.transform.rotation.x -= y * this.sensitivity;
  }
}

Behavior notes

  • Frame flush — pending input queued during the previous frame's browser event callbacks is promoted to the current frame's down/up sets at the start of update(). This guarantees getKeyDown is never missed even if the frame rate drops.
  • Key normalization — key names are lowercased before storage so "ArrowUp" and "arrowup" are treated identically.
  • Left/right modifiers — the system tracks both event.key (e.g. "shift") and event.code (e.g. "shiftleft") so generic and specific modifier queries both work simultaneously.
  • Register / unregister — call register(element) to attach all DOM listeners to a specific element, and unregister() to remove them. This should be called during world setup and teardown.

API reference