Skip to content

@web-engine-dev/input

Unified input system with action mapping for web-engine-dev. Supports keyboard, mouse, gamepad, and touch input with customizable action bindings.

Features

  • Action Mapping: Bind inputs to game actions
  • Multi-Device Support: Keyboard, mouse, gamepad, touch
  • Input Buffering: Queue inputs for responsiveness
  • Composite Axes: Combine keys into axes
  • Context Stacks: Switch between control schemes
  • Rebinding: Runtime key remapping

Installation

bash
npm install @web-engine-dev/input
# or
pnpm add @web-engine-dev/input

Quick Start

typescript
import { ActionMapBuilder, InputManager } from '@web-engine-dev/input';

// Create input manager
const input = new InputManager();
input.initialize();

// Define action map
const actions = new ActionMapBuilder('gameplay')
  .addAction('jump', 'button')
  .bindKey('Space', 'jump')
  .bindGamepad('a', 'jump')
  .addAction('move', 'axis2d')
  .bindCompositeAxis('move', {
    up: 'KeyW',
    down: 'KeyS',
    left: 'KeyA',
    right: 'KeyD',
  })
  .bindGamepadAxis('leftStickX', 'move')
  .bindGamepadAxis('leftStickY', 'move', { invert: true })
  .build();

input.registerActionMap(actions);

// Check input each frame
input.update(deltaTime);
const jump = input.getAction('jump');
if (jump?.type === 'button' && jump.justPressed) {
  player.jump();
}

const movement = input.getAction('move');
if (movement?.type === 'axis2d') {
  // By convention, +Y is up/forward (W/Up). For gamepad Y axes, use `invert: true`.
  player.move(movement.x, movement.y);
}

API Overview

Input Manager

MethodDescription
initialize(config?)Initialize input devices
update(deltaTime)Update input state per frame
registerActionMap(map)Register an action map
unregisterActionMap(name)Unregister an action map
enableActionMap(name)Enable a map
disableActionMap(name)Disable a map
getAction(name)Get current action value
isActionTriggered(name)Check if action triggered this frame
onAction(name, handler)Subscribe to action events
onInputDeviceConnected(handler)Subscribe to device connect events
onInputDeviceDisconnected(handler)Subscribe to device disconnect events
onPointerLockChanged(handler)Subscribe to pointer lock changes
onInputContextChanged(handler)Subscribe to input context changes
getContextStack()Get current context stack

Bindings

typescript
// Keyboard
{ type: 'keyboard', key: 'Space' }
{ type: 'keyboard', key: 'KeyW', modifiers: { shift: true }, value: 1 }

// Axis from buttons (supports negative values)
{ type: 'keyboard', key: 'KeyW', value: 1 }
{ type: 'keyboard', key: 'KeyS', value: -1 }
{ type: 'mouseButton', button: 'left', value: 1 }
{ type: 'gamepadButton', button: 'leftTrigger', value: 1 }

// Composite axis from keys
{ type: 'compositeAxis', up: 'KeyW', down: 'KeyS', left: 'KeyA', right: 'KeyD' }

// Builder helper for composite axes
new ActionMapBuilder('movement')
  .addAction('move', 'axis2d')
  .bindCompositeAxis('move', { up: 'KeyW', down: 'KeyS', left: 'KeyA', right: 'KeyD' })
  .build();

// Mouse
{ type: 'mouseButton', button: 'left' }
{ type: 'mouseAxis', axis: 'x', sensitivity: 0.5 }

// Gamepad
{ type: 'gamepadButton', button: 'a', gamepadIndex: 0 }
{ type: 'gamepadAxis', axis: 'leftStickX', deadzone: 0.15 }

// Touch
{ type: 'touch', gesture: 'tap', fingerCount: 1 }
{ type: 'touch', gesture: 'pan', axis: 'panX', sensitivity: 0.5 }
{ type: 'touch', gesture: 'pinch', axis: 'pinch' }
{ type: 'touch', gesture: 'rotate', axis: 'rotate', invert: true }
{ type: 'touch', gesture: 'swipe', axis: 'panX' }

Context Stacks

typescript
// Push context for menus
input.pushContext('menu');

// Pop context to return
input.popContext();

// Different bindings per context
const menuActions = new ActionMapBuilder('menu-actions')
  .addAction('select', 'button')
  .bindKey('Enter', 'select')
  .addAction('back', 'button')
  .bindKey('Escape', 'back')
  .build();

input.registerActionMap(menuActions);

Events

typescript
// Action events
input.onAction('jump', (event) => {
  if (event.phase === 'started') {
    player.jump();
  }
});

// Device connect/disconnect
input.onInputDeviceConnected((event) => {
  console.log('Connected:', event.deviceType, event.deviceId);
});

input.onInputDeviceDisconnected((event) => {
  console.log('Disconnected:', event.deviceType, event.deviceId);
});

// Pointer lock
input.onPointerLockChanged((event) => {
  console.log('Pointer lock:', event.locked);
});

// Input context changes
input.onInputContextChanged((event) => {
  console.log('Context change:', event.contextName, event.pushed);
});

Rebinding

typescript
// Replace existing binding
input.addBindingOverride('jump', {
  action: 'jump',
  originalBinding: { type: 'keyboard', key: 'Space' },
  newBinding: { type: 'keyboard', key: 'KeyJ' },
});

// Clear overrides
input.clearBindingOverrides('jump');

Peer Dependencies

  • @web-engine-dev/math - Vector math for axes

Proprietary software. All rights reserved.