Input
api.input is your window into the keyboard and mouse during Play - held keys, per-frame mouse-look deltas, and pointer-lock state.
Input in Awaken is intentionally low-level: the engine exposes raw held keys and raw mouse movement, and your script decides what they mean. There is no "jump action" or "look axis" baked into the engine - a character controller is entirely script logic on top of these primitives (see the Character Controller).
The Input surface
| Member | Type | Description |
|---|---|---|
isDown(key) | (string) => boolean | Is this key currently held? Case-insensitive. |
mouseDX | number | Horizontal mouse movement this frame (pixels). |
mouseDY | number | Vertical mouse movement this frame (pixels). |
pointerLocked | boolean | Whether the pointer is locked (mouse-look active). |
ts
onUpdate(api: ScriptApi, dt: number) {
if (api.input.isDown("w")) api.translate(0, 0, -3 * dt);
if (api.input.pointerLocked) {
this.yaw -= api.input.mouseDX * 0.0025;
}
}Held keys
isDown(key) reports whether a key is held right now. Keys are matched case-insensitively, and the value is a standard KeyboardEvent.key string:
- Letters and digits:
"w","a","s","d","1". - Space is a literal space:
api.input.isDown(" "). - Named keys:
"shift","control","arrowup","arrowleft","escape".
ts
const dx = (api.input.isDown("d") ? 1 : 0) - (api.input.isDown("a") ? 1 : 0);
const dz = (api.input.isDown("s") ? 1 : 0) - (api.input.isDown("w") ? 1 : 0);isDown is a state query - it stays true for as long as the key is held, every frame. For a one-shot "was just pressed" you track the previous state yourself, or drive events from collisions instead.
Mouse-look deltas
mouseDX and mouseDY are per-frame deltas - how far the mouse moved since the last frame, not an absolute position. This is exactly what a mouse-look camera wants: accumulate them into yaw and pitch.
They are accumulated during the frame and reset to zero after scripts run, so each frame you read that frame's movement. If nothing moved, both are 0.
ts
// first-person look
this.yaw -= api.input.mouseDX * this.sensitivity;
this.pitch -= api.input.mouseDY * this.sensitivity;
this.pitch = Math.max(-1.55, Math.min(1.55, this.pitch)); // clamp ±~89°Deltas are only meaningful while the pointer is locked - otherwise the OS cursor is free and the browser reports little or no movement.
Pointer lock
Mouse-look needs the cursor locked to the game canvas, so it can move infinitely without hitting a screen edge. Awaken wires this up for you:
- Clicking the game canvas during Play requests pointer lock. The cursor disappears and mouse movement starts feeding
mouseDX/mouseDY. pointerLockedflips totruewhile locked.- Esc releases the lock (browser default), and
pointerLockedreturns tofalse.
Guard look code with pointerLocked so the camera does not swing before the player has clicked in:
ts
onUpdate(api: ScriptApi) {
if (!api.input.pointerLocked) return; // wait for the click-to-lock
this.yaw -= api.input.mouseDX * this.sensitivity;
}This same wiring exists in the editor's Game view and in the standalone Player, so lock behaviour is identical in Play and in a shipped game.
How input reaches Play
The editor forwards keyboard and mouse events into a single Input object only while Play is active, mirroring the same listeners the standalone player uses. The game loop clears the mouse deltas after ScriptHost.update each frame, so your script always reads a fresh per-frame delta. Keyboard held-state persists across frames until the key is released; pressing Stop clears all input.
A movement example
A minimal WASD mover with mouse-look, built purely on api.input and api.moveAndSlide:
ts
export default class implements ScriptBehavior {
camera: Camera; // assign your scene camera
moveSpeed = 5; // m/s
sensitivity = 0.0025; // rad per pixel
private yaw = 0;
private pitch = 0;
onUpdate(api: ScriptApi, dt: number) {
if (api.input.pointerLocked) {
this.yaw -= api.input.mouseDX * this.sensitivity;
this.pitch = Math.max(-1.55, Math.min(1.55, this.pitch - api.input.mouseDY * this.sensitivity));
}
const f = (api.input.isDown("w") ? 1 : 0) - (api.input.isDown("s") ? 1 : 0);
const s = (api.input.isDown("d") ? 1 : 0) - (api.input.isDown("a") ? 1 : 0);
// move in the yaw frame
const mx = (Math.cos(this.yaw) * s + Math.sin(this.yaw) * f) * this.moveSpeed;
const mz = (-Math.sin(this.yaw) * s + Math.cos(this.yaw) * f) * this.moveSpeed;
api.moveAndSlide(mx * dt, 0, mz * dt);
}
}The full first-person controller - adding gravity, jump, and driving the camera entity - is on Examples and Character Controller.
See also
- ScriptApi Reference -
api.inputin the full API - Physics: Character Controller -
moveAndSlidefor collision movement - Examples - the complete first-person controller
- The Player Runtime - where the same input wiring runs shipped