Read player input
Goal
Move the player with WASD or the left stick, and fire with the left mouse button or the right trigger — without a key code appearing anywhere in your game code. When you are done, input.axis2('move') drives the character and input.pressed('fire') fires exactly once per click.
Files you will edit
src/game.ts— declare the bindings.src/systems/move.ts— read the actions.
Steps
Declare the action map in
src/game.ts. Names are yours; bindings areKeyboardEvent.codevalues, friendly aliases ('W','Space','LMB') orGamepad*names.tsimport { defineGame } from 'gameable'; import { move } from './systems/move'; export default defineGame({ assets: './assets.json', world: 'arena', actions: { fire: ['LMB', 'GamepadRT'], jump: ['Space', 'GamepadA'], move: { axis2: ['A', 'D', 'S', 'W'], gamepadAxes: [0, 1] }, }, systems: [move], });Read them in
src/systems/move.ts.axis2returns[x, y]in -1..1, with+yforward; the tuple is reused every call, so destructure it instead of keeping the reference.tsimport { input, type Ctx } from 'gameable'; const SPEED = 5; export function move(ctx: Ctx): void { const [x, y] = input.axis2('move'); ctx.player.velocity[0] = x * SPEED; ctx.player.velocity[2] = -y * SPEED; if (input.pressed('jump')) ctx.player.jump(); }Use the edge readers, not the held reader, for one-shot actions.
pressed('fire')is true on exactly one simulation step per click — including a click that starts and ends between two rendered frames, and however many frames the display drew since the last step — whiledown('fire')stays true for as long as the button is held, which is what an automatic weapon wants.tsif (input.pressed('fire')) fireOnce(); if (input.down('fire')) holdTrigger();For a mouse-look camera, ask for pointer lock and read the frame's delta. Deltas are zero unless the lock is held, so this is safe to run always.
tsimport { input, type Ctx } from 'gameable'; const SENSITIVITY = 0.0022; export function look(ctx: Ctx): void { if (input.pressed('fire') && !input.locked) input.requestPointerLock(); ctx.camera.yaw -= input.mouse.dx * SENSITIVITY; ctx.camera.pitch -= input.mouse.dy * SENSITIVITY; }
Verify
sh
npm run devHold W and the player walks forward; the left stick does the same. Click and the weapon fires once per click, not once per frame. Then assert it headlessly:
sh
npm testts
// tests/smoke.spec.ts
it('fires once per click', () => {
harness.mouseDown(0);
harness.frame();
expect(harness.shots).toBe(1);
harness.frame(); // still held, no second shot
expect(harness.shots).toBe(1);
});See also
- Engine modules
- The engine loop
gameable/inputAPI, andpackages/input/README.md- Build your first FPS