| name | input-keyboard-mouse-touch |
| description | Use this skill when handling user input in Phaser 4. Covers keyboard keys, mouse clicks and movement, touch events, pointer handling, drag and drop, hit areas, interactive objects, and gamepad support. Triggers on: keyboard, mouse, touch, pointer, drag, drop, click, input, gamepad, cursor keys. |
Input: Keyboard, Mouse, Touch, and Gamepad
Phaser provides a unified input system accessed via this.input in any Scene. It supports keyboard polling and events, mouse/pointer interaction with Game Objects (click, hover, drag), multi-touch, mouse wheel, and gamepad input. Input can be handled through event listeners or by polling state each frame.
Key source paths: src/input/InputPlugin.js, src/input/Pointer.js, src/input/keyboard/KeyboardPlugin.js, src/input/keyboard/keys/Key.js, src/input/keyboard/keys/KeyCodes.js, src/input/keyboard/combo/KeyCombo.js, src/input/gamepad/GamepadPlugin.js, src/input/gamepad/Gamepad.js, src/input/events/, src/input/keyboard/events/
Related skills: ../sprites-and-images/SKILL.md, ../events-system/SKILL.md, ../scenes/SKILL.md
Quick Start (basic keyboard + pointer input)
class MyScene extends Phaser.Scene {
create() {
this.cursors = this.input.keyboard.createCursorKeys();
this.input.keyboard.on('keydown-SPACE', (event) => {
console.log('Space pressed');
});
this.input.on('pointerdown', (pointer) => {
console.log('Clicked at', pointer.x, pointer.y);
});
const sprite = this.add.sprite(400, 300, 'player');
sprite.setInteractive();
sprite.on('pointerdown', (pointer, localX, localY, event) => {
console.log('Sprite clicked at local', localX, localY);
});
}
update() {
if (this.cursors.left.isDown) {
}
if (this.cursors.right.isDown) {
}
if (Phaser.Input.Keyboard.JustDown(this.cursors.space)) {
}
}
}
Core Concepts
The Input Plugin (this.input)
Accessed via this.input in any Scene. It is an EventEmitter that handles all input for that Scene.
Key properties:
this.input.enabled (boolean) - toggle input processing for the Scene
this.input.topOnly (boolean, default true) - only emit events from the top-most Game Object under the pointer
this.input.keyboard - the KeyboardPlugin instance
this.input.gamepad - the GamepadPlugin instance
this.input.mouse - the MouseManager reference
this.input.activePointer - the most recently active Pointer
this.input.mousePointer - the mouse Pointer (pointers[0], distinct from pointer1 which is the first touch)
this.input.pointer1 through this.input.pointer10 - individual pointer references
this.input.dragDistanceThreshold (number, default 0) - pixels a pointer must move before drag starts
this.input.dragTimeThreshold (number, default 0) - ms a pointer must be held before drag starts
this.input.pollRate (number, default -1) - how often pointers are polled; 0 = every frame, -1 = only on movement
Key methods:
addPointer(quantity) - add extra pointers for multi-touch (default is 2; max 10)
setHitArea(gameObjects, hitArea, hitAreaCallback) - set custom hit area on Game Objects
setHitAreaCircle(gameObjects, x, y, radius, callback)
setHitAreaEllipse(gameObjects, x, y, width, height, callback)
setHitAreaRectangle(gameObjects, x, y, width, height, callback)
setHitAreaTriangle(gameObjects, x1, y1, x2, y2, x3, y3, callback)
setHitAreaFromTexture(gameObjects, callback) - use the texture frame dimensions
setDraggable(gameObjects, value) - enable or disable dragging
makePixelPerfect(alphaTolerance) - returns a callback for pixel-perfect hit testing
Pointers
A Pointer object encapsulates both mouse and touch input. By default Phaser creates 2 pointers. Use this.input.addPointer(quantity) for more (up to 10 total).
Key properties:
x, y - position in screen space (read from position.x, position.y)
worldX, worldY - position translated through the most recent Camera
downX, downY - position when button was pressed
upX, upY - position when button was released
isDown (boolean) - true if any button is held
primaryDown (boolean) - true if primary button (left click / touch) is held
button (number) - which button was pressed/released (0=left, 1=middle, 2=right)
buttons (number) - bitmask of currently held buttons (1=left, 2=right, 4=middle, 8=back, 16=forward)
wasTouch (boolean) - true if input came from touch
velocity (Vector2) - smoothed velocity of pointer movement
angle (number) - angle of movement in radians
distance (number) - smoothed distance moved per frame
movementX, movementY - relative movement when pointer is locked
deltaX, deltaY, deltaZ - mouse wheel scroll amounts
locked (boolean) - whether pointer lock is active
camera - the Camera this Pointer last interacted with
Key methods:
leftButtonDown(), rightButtonDown(), middleButtonDown(), backButtonDown(), forwardButtonDown() - check specific buttons
leftButtonReleased(), rightButtonReleased(), middleButtonReleased() - check recent release
getDistance() - distance between down position and current/up position
getDistanceX(), getDistanceY() - horizontal/vertical distance
getDuration() - ms between down and current time or up time
getAngle() - angle between down and current/up position
updateWorldPoint(camera) - recalculate worldX/worldY for a given camera
positionToCamera(camera, output) - translate pointer position through a camera
Interactive Game Objects (setInteractive)
Call gameObject.setInteractive() to enable input on a Game Object. This uses the texture frame as the hit area by default.
sprite.setInteractive();
sprite.setInteractive(new Phaser.Geom.Circle(32, 32, 32), Phaser.Geom.Circle.Contains);
sprite.setInteractive(new Phaser.Geom.Ellipse(50, 50, 100, 60), Phaser.Geom.Ellipse.Contains);
sprite.setInteractive(new Phaser.Geom.Triangle(0,64,32,0,64,64), Phaser.Geom.Triangle.Contains);
sprite.setInteractive(new Phaser.Geom.Polygon(points), Phaser.Geom.Polygon.Contains);
sprite.setInteractive({ pixelPerfect: true, alphaTolerance: 1 });
sprite.setInteractive(this.input.makePixelPerfect());
sprite.setInteractive(this.input.makePixelPerfect(150));
sprite.setInteractive({
draggable: true,
dropZone: false,
useHandCursor: true,
cursor: 'pointer',
pixelPerfect: true,
alphaTolerance: 1
});
container.setSize(200, 200);
container.setInteractive();
Common Patterns
Keyboard Input (cursors, addKey, isDown, JustDown)
Cursor keys return an object with up, down, left, right, space, shift Key objects:
this.cursors = this.input.keyboard.createCursorKeys();
if (this.cursors.up.isDown) { }
if (this.cursors.space.isDown) { }
addKey creates a Key object for any key:
const keyW = this.input.keyboard.addKey('W');
const keySpace = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
const keyA = this.input.keyboard.addKey('A', true, false);
addKeys creates multiple keys at once:
const keys = this.input.keyboard.addKeys('W,S,A,D');
if (keys.W.isDown) { }
const keys = this.input.keyboard.addKeys({
up: Phaser.Input.Keyboard.KeyCodes.W,
down: Phaser.Input.Keyboard.KeyCodes.S,
left: Phaser.Input.Keyboard.KeyCodes.A,
right: Phaser.Input.Keyboard.KeyCodes.D
});
Polling vs events:
if (keyW.isDown) { }
if (keyW.isUp) { }
if (Phaser.Input.Keyboard.JustDown(keyW)) { }
if (Phaser.Input.Keyboard.JustUp(keyW)) { }
if (this.input.keyboard.checkDown(keySpace, 250)) {
}
this.input.keyboard.on('keydown-SPACE', (event) => { });
this.input.keyboard.on('keyup-SPACE', (event) => { });
this.input.keyboard.on('keydown', (event) => {
console.log(event.key);
});
const spaceBar = this.input.keyboard.addKey('SPACE');
spaceBar.on('down', (key, event) => { });
spaceBar.on('up', (key, event) => { });
Key object properties:
isDown / isUp (boolean)
keyCode (number)
altKey, ctrlKey, shiftKey, metaKey (boolean) - modifier state when pressed
duration (number) - ms held in previous down-up cycle
timeDown, timeUp (number) - timestamps
repeats (number) - repeat count while held
emitOnRepeat (boolean) - if true, fires 'down' event on each repeat
enabled (boolean) - can this key be processed
Prevent browser default behavior:
this.input.keyboard.addCapture('SPACE,UP,DOWN,LEFT,RIGHT');
this.input.keyboard.addCapture([ 32, 37, 38, 39, 40 ]);
this.input.keyboard.removeCapture('SPACE');
Common KeyCodes: BACKSPACE(8), TAB(9), ENTER(13), SHIFT(16), CTRL(17), ALT(18), ESC(27), SPACE(32), LEFT(37), UP(38), RIGHT(39), DOWN(40), A-Z(65-90), ZERO-NINE(48-57), F1-F12(112-123). Access via Phaser.Input.Keyboard.KeyCodes.SPACE etc.
Mouse/Pointer Click and Hover
Scene-level pointer events (fire anywhere on the canvas):
this.input.on('pointerdown', (pointer, currentlyOver) => {
});
this.input.on('pointerup', (pointer, currentlyOver) => { });
this.input.on('pointermove', (pointer, currentlyOver) => { });
this.input.on('wheel', (pointer, currentlyOver, deltaX, deltaY, deltaZ) => { });
Game Object pointer events (require setInteractive):
sprite.setInteractive();
sprite.on('pointerdown', (pointer, localX, localY, event) => {
});
sprite.on('pointerup', (pointer, localX, localY, event) => { });
sprite.on('pointermove', (pointer, localX, localY, event) => { });
sprite.on('pointerover', (pointer, localX, localY, event) => { });
sprite.on('pointerout', (pointer, event) => { });
sprite.on('wheel', (pointer, deltaX, deltaY, deltaZ, event) => { });
Right-click handling:
this.input.on('pointerdown', (pointer) => {
if (pointer.rightButtonDown()) {
}
});
this.input.mouse.disableContextMenu();
Pointer lock (FPS-style mouse capture):
this.input.on('pointerdown', () => {
this.input.mouse.requestPointerLock();
});
this.input.on('pointerlockchange', (event, locked) => {
});
Drag and Drop
const sprite = this.add.sprite(400, 300, 'item');
sprite.setInteractive();
this.input.setDraggable(sprite);
this.input.on('dragstart', (pointer, gameObject) => {
gameObject.setTint(0xff0000);
});
this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
gameObject.x = dragX;
gameObject.y = dragY;
});
this.input.on('dragend', (pointer, gameObject) => {
gameObject.clearTint();
});
const zone = this.add.zone(600, 300, 200, 200).setRectangleDropZone(200, 200);
this.input.on('drop', (pointer, gameObject, dropZone) => {
gameObject.x = dropZone.x;
gameObject.y = dropZone.y;
});
this.input.on('dragenter', (pointer, gameObject, dropZone) => { });
this.input.on('dragleave', (pointer, gameObject, dropZone) => { });
this.input.on('dragover', (pointer, gameObject, dropZone) => { });
Game Object-level drag events are also available:
sprite.on('drag', (pointer, dragX, dragY) => {
sprite.x = dragX;
sprite.y = dragY;
});
sprite.on('dragstart', (pointer, dragX, dragY) => { });
sprite.on('dragend', (pointer, dragX, dragY) => { });
sprite.on('drop', (pointer, dropZone) => { });
Drag thresholds:
this.input.dragDistanceThreshold = 16;
this.input.dragTimeThreshold = 200;
Three Levels of Pointer Events
Pointer events fire at three levels (see reference for details):
- Game Object:
gameObject.on('pointerdown', (pointer, localX, localY, event) => {})
- Scene per-object:
this.input.on('gameobjectdown', (pointer, gameObject, event) => {})
- Scene global:
this.input.on('pointerdown', (pointer, currentlyOver) => {})
Input Debug, topOnly, and Multi-touch Config
this.input.enableDebug(gameObject);
this.input.enableDebug(gameObject, 0xff00ff);
this.input.removeDebug(gameObject);
this.input.topOnly = false;
this.input.setTopOnly(false);
Multi-touch: set config.input.activePointers to reserve pointer slots at startup, or call this.input.addPointer(num) at runtime. Access via this.input.pointer1 through pointer10.
Gamepad Input
Enable gamepads in the game config:
const config = {
input: {
gamepad: true
}
};
Access via this.input.gamepad. Gamepads are available as pad1 through pad4:
this.input.gamepad.once('connected', (pad) => {
console.log('Gamepad connected:', pad.id);
});
if (this.input.gamepad.total > 0) {
const pad = this.input.gamepad.pad1;
}
Polling gamepad state in update():
update() {
const pad = this.input.gamepad.pad1;
if (!pad) return;
if (pad.up) { }
if (pad.down) { }
if (pad.left) { }
if (pad.right) { }
if (pad.A) { }
if (pad.B) { }
if (pad.X) { }
if (pad.Y) { }
if (pad.L1 > 0) { }
if (pad.L2 > 0) { }
if (pad.R1 > 0) { }
if (pad.R2 > 0) { }
const lx = pad.leftStick.x;
const ly = pad.leftStick.y;
const rx = pad.rightStick.x;
const ry = pad.rightStick.y;
pad.getAxisValue(0);
pad.getButtonValue(0);
pad.isButtonDown(0);
pad.setAxisThreshold(0.1);
}
Gamepad events:
this.input.gamepad.on('connected', (pad, event) => { });
this.input.gamepad.on('disconnected', (pad, event) => { });
this.input.gamepad.on('down', (pad, button, value) => { });
this.input.gamepad.on('up', (pad, button, value) => { });
pad.on('down', (index, value, button) => { });
pad.on('up', (index, value, button) => { });
Vibration (experimental, hardware/browser dependent):
if (pad.vibration) {
pad.vibration.playEffect('dual-rumble', {
duration: 200,
strongMagnitude: 1.0,
weakMagnitude: 0.5
});
}
Key Combos
Listen for a sequence of keys:
this.input.keyboard.createCombo('PHASER');
this.input.keyboard.createCombo(
[ 38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 13 ],
{ resetOnMatch: true }
);
this.input.keyboard.on('keycombomatch', (keyCombo, event) => {
console.log('Combo matched!');
});
createCombo(keys, config):
keys: a string (each character is a key) or an array of key codes / Key objects
config.resetOnWrongKey (boolean, default true) - reset progress if wrong key is pressed
config.maxKeyDelay (number, default 0) - max ms between key presses; 0 = no limit
config.resetOnMatch (boolean, default false) - reset combo after a successful match
config.deleteOnMatch (boolean, default false) - remove combo after first match
For detailed configuration options, API reference tables, and source file maps, see the reference guide.