logo
PlayerInput

input

PlayerInput

Free

Install with the CLI:

bjs download PlayerInput

PlayerInput

Turns raw keyboard and gamepad traffic into named game intents — jump, attack, moveForward — so the rest of the game reacts to actions, not key codes.

What it does

PlayerInputComponent holds an action map: a table of action names, each bound to a list of keyboard key codes (KeyW, Space, …) and gamepad button indices. PlayerInputSystem listens to window keydown/keyup and polls the Gamepad API each frame, feeds those presses into the component's core, and emits four events on the EventBus — playerInput.actionPressed, playerInput.actionReleased, playerInput.moveChanged, and playerInput.sourceChanged. A mover, a jump system, and an attack system each subscribe to the action they care about and stay ignorant of which key fired, so rebinding and gamepad support cost them nothing. The four directional actions also collapse into a normalized XZ move vector: diagonals clamp to unit length, a half-pushed stick stays at half speed, and a deadzone filters stick drift.

Use it in a scene

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Fastest path

bjs download scene PlayerInput

Pulls this exact scene into your project and runs it — no copy-paste. Add --all to grab the components and assets it needs too.

Or build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add PlayerInput
    bjs download PlayerInput

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "Player": {
    "tags": ["player"],
    "components": {
      "MeshPrimitive": {
        "primitive": "capsule", "radius": 0.5, "height": 1.6, "position": [0, 0.8, 0]
      },
      "PlayerInput": {
        "enabled": true,
        "deadzone": 0.15,
        "gamepadIndex": 0,
        "keyBindings": { "interact": ["KeyF", "KeyG"] }
      }
    }
  }
}

Binding overrides merge onto the defaults, so keyBindings above only changes interact and leaves every other action mapped as shipped.

Props

  • enabled (boolean, default true) — whether this entity's input is read; flip it at runtime with the playerInput.setEnabled event.
  • deadzone (number, default 0.15) — gamepad stick magnitude below which an axis reads as zero.
  • gamepadIndex (number, default 0) — which gamepad slot (0–3) to poll each frame.
  • keyBindings (Record<string, string[]>, default the table below) — action name → accepted KeyboardEvent.code values; merged onto the defaults.
  • gamepadBindings (Record<string, number[]>, default jump:[0], attack:[2], interact:[3]) — action name → accepted gamepad button indices; merged onto the defaults.

Default key bindings: moveForward [KeyW, ArrowUp], moveBackward [KeyS, ArrowDown], moveLeft [KeyA, ArrowLeft], moveRight [KeyD, ArrowRight], jump [Space], attack [KeyE, Enter], interact [KeyF].

Events

  • emits playerInput.actionPressed (PlayerInputEvents.ACTION_PRESSED) on the rising edge of an action — { entityId, action, source }.
  • emits playerInput.actionReleased (PlayerInputEvents.ACTION_RELEASED) on the falling edge — { entityId, action, source }.
  • emits playerInput.moveChanged (PlayerInputEvents.MOVE_CHANGED) only when the normalized move vector changes — { entityId, x, z }.
  • emits playerInput.sourceChanged (PlayerInputEvents.SOURCE_CHANGED) only when input shifts between 'keyboard' and 'gamepad'{ entityId, source }.
  • listens for playerInput.setEnabled (PlayerInputInputEvents.SET_ENABLED) — { enabled }; disabling also resets held state so no action stays stuck down.

Dependencies

None required. meta.json lists no sibling components — the System needs only the EventBus and the browser input APIs. Pair it with MeshPrimitive (or any movable entity) and a mover that subscribes to playerInput.moveChanged for a controllable character.

Notes

  • Action events are edge-triggered, not level-triggered: holding a key fires actionPressed once, then actionReleased once on release. Read playerInput.moveChanged for held movement, not a stream of presses.
  • moveChanged and sourceChanged fire only on an actual change, so a stationary player produces no per-frame event traffic.
  • Multiple keys can map to one action — releasing one while another is still held keeps the action active, because state is recomputed from every binding that feeds it.
  • The System sets _pauseable = false, so input keeps flowing while the rest of the world is paused (menus, pause screens). Gate it with playerInput.setEnabled instead.
  • Each gamepad is polled against a per-pad snapshot, so a held button counts as one press, not one per frame.

More like this

PlayerInputBridge
AiPilot
Animation
ArcCamera
Asteroid
Bullet
CameraFollow
CarDrive
DirectionalLight
EnemySpawner
EnvironmentTexture
FlightIntent
Flipper
FreighterCar
FreighterChain
FreighterHead
GameConfig
Health
HemisphericLight
Jump
KeyboardInput
KeyboardMover
Lifetime
LineOfSight
MachineGun
Mesh
MeshPrimitive
Missile
MissileLauncher
MissionDirector
MouseInput
Movement
Obstacle
ObstacleField
OilBlob
Parts
Physics
PinballBuilderInput
PlayerFlightInput
PlayerWalkAnimator
Plunger
PokerInput
RespawnTimer
RoundReset
Score
Shadow
ShipLoadout
SkeletonAnimator
SpaceShooterBullet
SpaceShooterHealth
SpaceShooterScore
TwinStickEnemy
TwinStickShooter
WaveDirector
Waypoint
WaypointTrack

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search