| name | phaser-input |
| description | 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". |
| version | 0.4.0 |
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) { }
if (this.cursors.right.isDown) { }
if (this.cursors.up.isDown) { }
if (this.cursors.down.isDown) { }
if (this.cursors.space.isDown) { }
if (this.cursors.shift.isDown) { }
}
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) { }
if (this.wasd.right.isDown) { }
}
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
| Method | Behavior |
|---|
key.isDown | true 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 {
if (this.cursors.left.isDown) {
this.player.setVelocityX(-200);
}
if (Phaser.Input.Keyboard.JustDown(this.spaceKey)) {
this.fireBullet();
}
if (Phaser.Input.Keyboard.JustUp(this.shiftKey)) {
this.stopSprint();
}
}
Key Combos (Konami Code, Cheat Codes)
create(): void {
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 {
this.input.keyboard!.disableGlobalCapture();
this.input.keyboard!.enableGlobalCapture();
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) => {
});
this.input.on('pointerup', (ptr: Phaser.Input.Pointer) => {
});
}
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 });
button.on('pointerdown', () => {
this.scene.start('GameScene');
});
button.on('pointerover', () => {
button.setTint(0xcccccc);
});
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 {
this.input.addPointer(2);
}
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;
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 {
this.input.mouse!.disableContextMenu();
}
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;
if (pad.A) { this.jump(); }
if (pad.B) { this.dodge(); }
if (pad.X) { this.attack(); }
if (pad.Y) { this.interact(); }
if (pad.L1) { }
if (pad.R1) { }
if (pad.L2) { }
if (pad.R2) { }
if (pad.up) { }
if (pad.down) { }
if (pad.left) { }
if (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;
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);
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