skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
yakoub-ai/phaser4-gamedev168 installs

phaser-input

This skill should be used when the user asks to "handle input", "keyboard controls", "mouse click", "touch controls", "gamepad support", "drag and drop", "virtual joystick", "WASD movement", "detect click", "pointer events", "keyboard shortcut", or "input manager".

How do I install this agent skill?

npx skills add https://github.com/yakoub-ai/phaser4-gamedev --skill phaser-input
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The phaser-input skill provides educational resources, code snippets, and design patterns for handling input devices (keyboard, mouse, touch, gamepad) and a virtual joystick within the Phaser 4 game framework. No security issues or malicious practices were identified.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

What does this agent skill do?

Phaser 4 Input

Phaser 4's Input system supports keyboard, mouse/pointer, touch, and gamepad. All input is accessed through this.input inside a Scene.

Keyboard Input

Cursor Keys

Create a cursor key set to track arrow keys, space, and shift in one call:

private cursors!: Phaser.Types.Input.Keyboard.CursorKeys;

create(): void {
  this.cursors = this.input.keyboard!.createCursorKeys();
}

update(): void {
  if (this.cursors.left.isDown) { /* move left */ }
  if (this.cursors.right.isDown) { /* move right */ }
  if (this.cursors.up.isDown) { /* move up / jump */ }
  if (this.cursors.down.isDown) { /* move down / crouch */ }
  if (this.cursors.space.isDown) { /* fire / action */ }
  if (this.cursors.shift.isDown) { /* sprint / modifier */ }
}

WASD Keys

Use addKeys to bind arbitrary keys by name:

private wasd!: { up: Phaser.Input.Keyboard.Key; left: Phaser.Input.Keyboard.Key; down: Phaser.Input.Keyboard.Key; right: Phaser.Input.Keyboard.Key };

create(): void {
  this.wasd = this.input.keyboard!.addKeys({ up: 'W', left: 'A', down: 'S', right: 'D' }) as typeof this.wasd;
}

update(): void {
  if (this.wasd.left.isDown) { /* strafe left */ }
  if (this.wasd.right.isDown) { /* strafe right */ }
}

Single Key

Bind a single key using KeyCodes:

private spaceKey!: Phaser.Input.Keyboard.Key;
private shiftKey!: Phaser.Input.Keyboard.Key;

create(): void {
  this.spaceKey = this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
  this.shiftKey = this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT);
}

isDown vs JustDown vs JustUp

MethodBehavior
key.isDowntrue every frame the key is held
Phaser.Input.Keyboard.JustDown(key)true on the single frame the key is first pressed
Phaser.Input.Keyboard.JustUp(key)true on the single frame the key is released
update(): void {
  // Continuous — fires every frame while held
  if (this.cursors.left.isDown) {
    this.player.setVelocityX(-200);
  }

  // One-shot — fires once per key press
  if (Phaser.Input.Keyboard.JustDown(this.spaceKey)) {
    this.fireBullet();
  }

  // Release — fires once per key release
  if (Phaser.Input.Keyboard.JustUp(this.shiftKey)) {
    this.stopSprint();
  }
}

Key Combos (Konami Code, Cheat Codes)

create(): void {
  // Konami: Up Up Down Down Left Right Left Right A B
  const combo = this.input.keyboard!.createCombo(
    [38, 38, 40, 40, 37, 39, 37, 39, 65, 66],
    { resetOnMatch: true }
  );

  this.input.keyboard!.on('keycombomatch', (combo: Phaser.Input.Keyboard.KeyCombo) => {
    console.log('Konami code entered!');
    this.activateCheatMode();
  });
}

Disabling Browser Default Keys

Prevent the browser from intercepting arrow keys (page scroll) and other keys:

create(): void {
  // Disable global capture — let browser handle keys normally
  this.input.keyboard!.disableGlobalCapture();

  // Enable global capture — Phaser intercepts keys before the browser
  this.input.keyboard!.enableGlobalCapture();

  // Capture only specific keys (recommended approach)
  this.input.keyboard!.addCapture([
    Phaser.Input.Keyboard.KeyCodes.UP,
    Phaser.Input.Keyboard.KeyCodes.DOWN,
    Phaser.Input.Keyboard.KeyCodes.LEFT,
    Phaser.Input.Keyboard.KeyCodes.RIGHT,
    Phaser.Input.Keyboard.KeyCodes.SPACE,
  ]);
}

Pointer / Mouse Input

Global Pointer Events

Listen on this.input for events anywhere in the game canvas:

create(): void {
  this.input.on('pointerdown', (ptr: Phaser.Input.Pointer) => {
    console.log(`Clicked at canvas: ${ptr.x}, ${ptr.y}`);
    console.log(`World position: ${ptr.worldX}, ${ptr.worldY}`);
    console.log(`Left button: ${ptr.leftButtonDown()}`);
    console.log(`Right button: ${ptr.rightButtonDown()}`);
  });

  this.input.on('pointermove', (ptr: Phaser.Input.Pointer) => {
    // ptr.velocity.x / ptr.velocity.y for movement delta
  });

  this.input.on('pointerup', (ptr: Phaser.Input.Pointer) => {
    // handle release
  });
}

Use ptr.worldX / ptr.worldY when the camera has moved — these give the world-space position rather than screen-space.

Making Sprites Interactive

Call setInteractive() before listening to pointer events on a specific object:

create(): void {
  const button = this.add.image(400, 300, 'button');
  button.setInteractive({ useHandCursor: true });  // shows pointer cursor on hover

  button.on('pointerdown', () => {
    this.scene.start('GameScene');
  });

  button.on('pointerover', () => {
    button.setTint(0xcccccc);  // lighten on hover
  });

  button.on('pointerout', () => {
    button.clearTint();
  });
}

Drag and Drop

Enable dragging on a game object:

create(): void {
  const card = this.add.image(200, 200, 'card');
  card.setInteractive();
  this.input.setDraggable(card);

  this.input.on('dragstart', (ptr: Phaser.Input.Pointer, obj: Phaser.GameObjects.Image) => {
    obj.setTint(0x88ff88);
  });

  this.input.on('drag', (ptr: Phaser.Input.Pointer, obj: Phaser.GameObjects.Image, dragX: number, dragY: number) => {
    obj.setPosition(dragX, dragY);
  });

  this.input.on('dragend', (ptr: Phaser.Input.Pointer, obj: Phaser.GameObjects.Image) => {
    obj.clearTint();
  });
}

Input Zones

Use a Zone as an invisible interactive area (useful for UI hit areas):

create(): void {
  const zone = this.add.zone(400, 300, 200, 100).setInteractive();
  zone.on('pointerdown', () => {
    console.log('Zone was clicked');
  });
}

Right-Click (Context Menu)

Prevent the browser context menu from appearing on right-click:

create(): void {
  this.input.mouse!.disableContextMenu();

  this.input.on('pointerdown', (ptr: Phaser.Input.Pointer) => {
    if (ptr.rightButtonDown()) {
      console.log('Right click at', ptr.worldX, ptr.worldY);
    }
  });
}

Stop Event Propagation

Prevent a click on a game object from also firing the global pointerdown handler:

button.on('pointerdown', (
  ptr: Phaser.Input.Pointer,
  localX: number,
  localY: number,
  event: Phaser.Types.Input.EventData
) => {
  event.stopPropagation();
  this.handleButtonClick();
});

Touch Input

Touch events share the same pointer API — pointerdown, pointermove, pointerup fire on touch devices automatically.

Multi-Touch

Add extra pointer slots (default is 2 total; expand for more fingers):

create(): void {
  // Support up to 4 simultaneous touches
  this.input.addPointer(2);  // adds 2 more (total becomes 4)

  // Access individual touch pointers
  // this.input.pointer1  — first touch
  // this.input.pointer2  — second touch
  // this.input.pointer3  — third touch
}

Swipe Detection

Phaser has no built-in swipe API. Track the start position manually:

private swipeStart = { x: 0, y: 0 };
private readonly SWIPE_THRESHOLD = 50; // pixels

create(): void {
  this.input.on('pointerdown', (ptr: Phaser.Input.Pointer) => {
    this.swipeStart = { x: ptr.x, y: ptr.y };
  });

  this.input.on('pointerup', (ptr: Phaser.Input.Pointer) => {
    const dx = ptr.x - this.swipeStart.x;
    const dy = ptr.y - this.swipeStart.y;

    if (Math.abs(dx) > this.SWIPE_THRESHOLD || Math.abs(dy) > this.SWIPE_THRESHOLD) {
      if (Math.abs(dx) > Math.abs(dy)) {
        console.log(dx > 0 ? 'Swipe Right' : 'Swipe Left');
      } else {
        console.log(dy > 0 ? 'Swipe Down' : 'Swipe Up');
      }
    }
  });
}

Preventing Browser Gestures

create(): void {
  // Prevent pinch-zoom, browser swipe navigation, pull-to-refresh
  this.input.mouse!.disableContextMenu();
  // In your HTML, also add to the canvas element:
  // style="touch-action: none;"
  // Or in Phaser config: input: { activePointers: 3 }
}

Gamepad Input

The Gamepad plugin is included by default in Phaser 4. Access it via this.input.gamepad.

Connection

create(): void {
  this.input.gamepad!.on('connected', (pad: Phaser.Input.Gamepad.Gamepad) => {
    console.log(`Gamepad connected: ${pad.id}`);
  });

  this.input.gamepad!.on('disconnected', (pad: Phaser.Input.Gamepad.Gamepad) => {
    console.log(`Gamepad disconnected`);
  });
}

Reading Buttons

update(): void {
  const pad = this.input.gamepad!.pad1;
  if (!pad) return;

  // Face buttons
  if (pad.A) { this.jump(); }
  if (pad.B) { this.dodge(); }
  if (pad.X) { this.attack(); }
  if (pad.Y) { this.interact(); }

  // Shoulder / trigger buttons
  if (pad.L1) { /* left bumper */ }
  if (pad.R1) { /* right bumper */ }
  if (pad.L2) { /* left trigger (analog, 0–1) */ }
  if (pad.R2) { /* right trigger (analog, 0–1) */ }

  // D-pad
  if (pad.up)    { /* d-pad up */ }
  if (pad.down)  { /* d-pad down */ }
  if (pad.left)  { /* d-pad left */ }
  if (pad.right) { /* d-pad right */ }
}

Analog Sticks

Values range from -1 to 1 on each axis. Apply a dead zone to prevent drift:

private readonly DEAD_ZONE = 0.1;

update(): void {
  const pad = this.input.gamepad!.pad1;
  if (!pad) return;

  let lx = pad.leftStick.x;
  let ly = pad.leftStick.y;
  let rx = pad.rightStick.x;
  let ry = pad.rightStick.y;

  // Apply dead zone
  if (Math.abs(lx) < this.DEAD_ZONE) lx = 0;
  if (Math.abs(ly) < this.DEAD_ZONE) ly = 0;
  if (Math.abs(rx) < this.DEAD_ZONE) rx = 0;
  if (Math.abs(ry) < this.DEAD_ZONE) ry = 0;

  this.player.setVelocity(lx * 300, ly * 300);

  // Aim with right stick
  if (rx !== 0 || ry !== 0) {
    const angle = Math.atan2(ry, rx) * (180 / Math.PI);
    this.player.setAngle(angle);
  }
}

Virtual Joystick (Mobile)

Phaser 4 has no built-in virtual joystick. For mobile games, implement a pointer-based joystick or use the reference implementation.

See references/virtual-joystick.md for a complete, production-ready TypeScript class that renders a base circle and thumb and exposes normalized direction.x / direction.y values.

Two common bugs are documented in that reference file: (1) First-Contact Lock — the base must stay locked during a drag; if the base "jumps" as the user drags, someone is updating baseX/baseY in onPointerMove. (2) Cross-scene listener loss — if the joystick works in one scene but not another, instantiate it ONCE in a persistent InputScene launched via scene.launch(), not per-gameplay-scene.

Usage pattern:

private joystick!: VirtualJoystick;

create(): void {
  this.joystick = new VirtualJoystick(this, 120, this.scale.height - 120);
}

update(): void {
  if (this.joystick.isActive) {
    this.player.setVelocity(
      this.joystick.direction.x * 200,
      this.joystick.direction.y * 200
    );
  } else {
    this.player.setVelocity(0, 0);
  }
}

Additional Resources

Reference Files

  • references/input-api.md — Complete API reference for InputPlugin, KeyboardPlugin, Key, Pointer, Gamepad, and all input events
  • references/virtual-joystick.md — Full TypeScript virtual joystick implementation for mobile games

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/yakoub-ai/phaser4-gamedev/phaser-input">View phaser-input on skillZs</a>