mcpField
I
input-keyboard-mouse-touch

input-keyboard-mouse-touch

github.com/phaserjs开发40.6k971 次安装10 周前更新
代码仓库

关于 input-keyboard-mouse-touch

Phaser 场景输入系统的技能指南,面向开发 Phaser 游戏的开发者。内容涵盖键盘、指针、触控和游戏手柄,并说明事件处理与逐帧轮询方式。

主要功能

  1. 01键盘事件与状态轮询
  2. 02鼠标点击、悬停与滚轮
  3. 03交互对象拖放与命中区域
  4. 04多点触控与指针管理
  5. 05游戏手柄事件和状态读取

适用场景

  1. 01实现角色键盘移动
  2. 02处理对象点击和拖放
  3. 03为触屏游戏配置多指输入
  4. 04读取游戏手柄按键状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/phaserjs/phaser --skill input-keyboard-mouse-touch

安装后在 Claude Code 中输入/input-keyboard-mouse-touch确认已加载。

附带文件references/REFERENCE.md

详细介绍

输入:键盘、鼠标、触摸和游戏手柄

Phaser 提供了一套统一的输入系统,可通过任意 Scene 中的 this.input 访问。它支持键盘轮询和事件、鼠标/指针与游戏对象交互(点击、悬停、拖拽)、多点触控、鼠标滚轮以及游戏手柄输入。输入可以通过事件监听器处理,也可以每帧轮询状态。

关键源码路径: 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/ 相关技能: ../sprites-and-images/SKILL.md、../events-system/SKILL.md、../scenes/SKILL.md

快速开始(基础键盘 + 指针输入)

class MyScene extends Phaser.Scene {
    create() {
        // Keyboard: create cursor keys (up, down, left, right, space, shift)
        this.cursors = this.input.keyboard.createCursorKeys();

        // Keyboard: listen for a specific key event
        this.input.keyboard.on('keydown-SPACE', (event) => {
            console.log('Space pressed');
        });

        // Pointer: listen for click/tap anywhere on the game canvas
        this.input.on('pointerdown', (pointer) => {
            console.log('Clicked at', pointer.x, pointer.y);
        });

        // Pointer: make a Game Object interactive and clickable
        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() {
        // Poll cursor keys each frame
        if (this.cursors.left.isDown) {
            // move left
        }
        if (this.cursors.right.isDown) {
            // move right
        }
        if (Phaser.Input.Keyboard.JustDown(this.cursors.space)) {
            // fire once per press
        }
    }
}

核心概念

输入插件(this.input)

可通过任意 Scene 中的 this.input 访问。它是一个 EventEmitter,负责处理该 Scene 的所有输入。

关键属性:

  • this.input.enabled(布尔值)— 切换该 Scene 的输入处理
  • this.input.topOnly(布尔值,默认 true)— 仅发出指针下方最顶层游戏对象的事件
  • this.input.keyboard — KeyboardPlugin 实例
  • this.input.gamepad — GamepadPlugin 实例
  • this.input.mouse — MouseManager 引用
  • this.input.activePointer — 最近活跃的 Pointer
  • this.input.mousePointer — 鼠标 Pointer(pointers[0],不同于 pointer1,后者是第一个触摸点)
  • this.input.pointer1 到 this.input.pointer10 — 各个指针引用
  • this.input.dragDistanceThreshold(数字,默认 0)— 拖拽开始前指针必须移动的像素数
  • this.input.dragTimeThreshold(数字,默认 0)— 拖拽开始前指针必须按住的毫秒数
  • this.input.pollRate(数字,默认 -1)— 指针轮询频率;0 = 每帧,-1 = 仅在移动时

关键方法:

  • addPointer(quantity) — 为多点触控添加额外指针(默认是 2;最多 10 个)
  • setHitArea(gameObjects, hitArea, hitAreaCallback) — 在游戏对象上设置自定义命中区域
  • 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) — 使用纹理帧尺寸
  • setDraggable(gameObjects, value) — 启用或禁用拖拽
  • makePixelPerfect(alphaTolerance) — 返回一个用于像素级精确命中检测的回调
指针

Pointer 对象同时封装了鼠标和触摸输入。默认情况下 Phaser 会创建 2 个指针。使用 this.input.addPointer(quantity) 可以添加更多(最多共 10 个)。

关键属性:

  • x、y - 屏幕空间中的位置(从 position.x、position.y 读取)
  • worldX、worldY - 经过最近一次 Camera 转换后的位置
  • downX、downY - 按钮按下时的位置
  • upX、upY - 按钮释放时的位置
  • isDown(布尔值)- 若任意按钮被按住则为 true
  • primaryDown(布尔值)- 若主按钮(左键点击 / 触摸)被按住则为 true
  • button(数字)- 被按下/释放的按钮(0=左键,1=中键,2=右键)
  • buttons(数字)- 当前被按住按钮的位掩码(1=左键,2=右键,4=中键,8=后退,16=前进)
  • wasTouch(布尔值)- 若输入来自触摸则为 true
  • velocity(Vector2)- 平滑后的指针移动速度
  • angle(数字)- 移动角度,单位为弧度
  • distance(数字)- 平滑后的每帧移动距离
  • movementX、movementY - 指针锁定时的相对移动量
  • deltaX、deltaY、deltaZ - 鼠标滚轮滚动量
  • locked(布尔值)- 指针锁定是否处于激活状态
  • camera - 该 Pointer 最后交互的 Camera

关键方法:

  • leftButtonDown()、rightButtonDown()、middleButtonDown()、backButtonDown()、forwardButtonDown() - 检查特定按钮
  • leftButtonReleased()、rightButtonReleased()、middleButtonReleased() - 检查最近的释放
  • getDistance() - 按下位置与当前位置/释放位置之间的距离
  • getDistanceX()、getDistanceY() - 水平/垂直距离
  • getDuration() - 按下与当前时间或释放时间之间的毫秒数
  • getAngle() - 按下位置与当前位置/释放位置之间的角度
  • updateWorldPoint(camera) - 针对给定 camera 重新计算 worldX/worldY
  • positionToCamera(camera, output) - 通过 camera 转换指针位置
交互式游戏对象(setInteractive)

调用 gameObject.setInteractive() 可为游戏对象启用输入。默认情况下使用纹理帧作为命中区域。

// Default hit area from texture
sprite.setInteractive();

// Custom shape hit areas (Rectangle, Circle, Ellipse, Triangle, Polygon)
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);

// Pixel-perfect hit testing (expensive — use sparingly)
sprite.setInteractive({ pixelPerfect: true, alphaTolerance: 1 });
sprite.setInteractive(this.input.makePixelPerfect());
// With alpha tolerance (default 1):
sprite.setInteractive(this.input.makePixelPerfect(150));

// Config object with multiple options
sprite.setInteractive({
    draggable: true,
    dropZone: false,
    useHandCursor: true,
    cursor: 'pointer',
    pixelPerfect: true,
    alphaTolerance: 1
});

// Containers must specify a shape or call setSize first
container.setSize(200, 200);
container.setInteractive();

常见模式

键盘输入(cursors、addKey、isDown、JustDown)

方向键 返回一个包含 up、down、left、right、space、shift Key 对象的对象:

this.cursors = this.input.keyboard.createCursorKeys();

// In update():
if (this.cursors.up.isDown) { /* held */ }
if (this.cursors.space.isDown) { /* held */ }

addKey 为任意按键创建一个 Key 对象:

// By string name
const keyW = this.input.keyboard.addKey('W');
// By key code
const keySpace = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
// With options: addKey(key, enableCapture, emitOnRepeat)
const keyA = this.input.keyboard.addKey('A', true, false);

addKeys 一次性创建多个按键:

// Comma-separated string returns { W, S, A, D } Key objects
const keys = this.input.keyboard.addKeys('W,S,A,D');
if (keys.W.isDown) { /* ... */ }

// Object form with custom names
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 in update() - check every frame
if (keyW.isDown) { /* key is currently held */ }
if (keyW.isUp) { /* key is currently released */ }

// JustDown - returns true only once per press, resets after check
if (Phaser.Input.Keyboard.JustDown(keyW)) { /* fire once */ }
if (Phaser.Input.Keyboard.JustUp(keyW)) { /* released once */ }

// checkDown with duration - throttled polling
if (this.input.keyboard.checkDown(keySpace, 250)) {
    // true at most once every 250ms while held
}

// Event-driven: listen for specific key
this.input.keyboard.on('keydown-SPACE', (event) => { /* ... */ });
this.input.keyboard.on('keyup-SPACE', (event) => { /* ... */ });

// Event-driven: listen for any key
this.input.keyboard.on('keydown', (event) => {
    console.log(event.key); // native DOM KeyboardEvent
});

// Event on a Key object itself
const spaceBar = this.input.keyboard.addKey('SPACE');
spaceBar.on('down', (key, event) => { /* Key object + native event */ });
spaceBar.on('up', (key, event) => { /* ... */ });

Key 对象属性:

  • isDown / isUp(布尔值)
  • keyCode(数字)
  • altKey、ctrlKey、shiftKey、metaKey(布尔值)- 按下时的修饰键状态
  • duration(数字)- 上一次按下-松开周期中按住的毫秒数
  • timeDown、timeUp(数字)- 时间戳
  • repeats(数字)- 按住期间的重复次数
  • emitOnRepeat(布尔值)- 若为 true,则每次重复时触发 'down' 事件
  • enabled(布尔值)- 该按键是否可被处理

阻止浏览器默认行为:

// Capture specific keys to prevent browser scrolling etc.
this.input.keyboard.addCapture('SPACE,UP,DOWN,LEFT,RIGHT');
this.input.keyboard.addCapture([ 32, 37, 38, 39, 40 ]);
this.input.keyboard.removeCapture('SPACE');
// Note: captures are global across all Scenes

常见 KeyCode: 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)。可通过 Phaser.Input.Keyboard.KeyCodes.SPACE 等方式访问。

鼠标/指针点击与悬停

场景级指针事件(在画布任意位置触发):

this.input.on('pointerdown', (pointer, currentlyOver) => {
    // pointer: Pointer object, currentlyOver: array of interactive Game Objects under pointer
});
this.input.on('pointerup', (pointer, currentlyOver) => { /* ... */ });
this.input.on('pointermove', (pointer, currentlyOver) => { /* ... */ });
this.input.on('wheel', (pointer, currentlyOver, deltaX, deltaY, deltaZ) => { /* ... */ });

游戏对象指针事件(需要 setInteractive):

sprite.setInteractive();

// pointerdown on this specific object
sprite.on('pointerdown', (pointer, localX, localY, event) => {
    // localX/localY are relative to the Game Object's top-left
    // event.stopPropagation() prevents the event from going further
});

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) => { /* ... */ });

右键处理:

this.input.on('pointerdown', (pointer) => {
    if (pointer.rightButtonDown()) {
        // right-click
    }
});

// Disable context menu
this.input.mouse.disableContextMenu();

指针锁定(FPS 风格的鼠标捕获):

// Request lock on click
this.input.on('pointerdown', () => {
    this.input.mouse.requestPointerLock();
});

this.input.on('pointerlockchange', (event, locked) => {
    // locked: boolean
});

// Read relative movement while locked
// pointer.movementX, pointer.movementY
拖放
const sprite = this.add.sprite(400, 300, 'item');
sprite.setInteractive();
this.input.setDraggable(sprite);

// Or use config:
// sprite.setInteractive({ draggable: true });

// Drag events on the Scene input
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();
});

// Drop zones
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) => { /* ... */ });

游戏对象级拖拽事件同样可用:

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) => { /* ... */ });

拖拽阈值:

this.input.dragDistanceThreshold = 16; // must move 16px before drag starts
this.input.dragTimeThreshold = 200;    // must hold 200ms before drag starts
指针事件的三个层级

指针事件在三个层级触发(详情见参考):

  1. 游戏对象:gameObject.on('pointerdown', (pointer, localX, localY, event) => {})
  2. 场景按对象:this.input.on('gameobjectdown', (pointer, gameObject, event) => {})
  3. 场景全局:this.input.on('pointerdown', (pointer, currentlyOver) => {})
输入调试、topOnly 与多点触控配置
// Debug visualize hit areas
this.input.enableDebug(gameObject);
this.input.enableDebug(gameObject, 0xff00ff);
this.input.removeDebug(gameObject);

// Let all objects under pointer receive events (not just top-most)
this.input.topOnly = false;
this.input.setTopOnly(false);

多点触控:在启动时将 config.input.activePointers 设置为预留指针槽位,或在运行时调用 this.input.addPointer(num)。通过 this.input.pointer1 到 pointer10 访问。

游戏手柄输入

在游戏配置中启用手柄:

const config = {
    input: {
        gamepad: true
    }
};

通过 this.input.gamepad 访问。手柄可通过 pad1 到 pad4 使用:

// Wait for connection
this.input.gamepad.once('connected', (pad) => {
    console.log('Gamepad connected:', pad.id);
});

// If already connected, check total
if (this.input.gamepad.total > 0) {
    const pad = this.input.gamepad.pad1;
}

在 update() 中轮询手柄状态:

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

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

    // Face buttons (boolean) - Xbox naming convention
    if (pad.A) { /* bottom button (Xbox A / PS X) */ }
    if (pad.B) { /* right button (Xbox B / PS Circle) */ }
    if (pad.X) { /* left button (Xbox X / PS Square) */ }
    if (pad.Y) { /* top button (Xbox Y / PS Triangle) */ }

    // Shoulder buttons (float 0-1)
    if (pad.L1 > 0) { /* left shoulder top (LB) */ }
    if (pad.L2 > 0) { /* left shoulder bottom / trigger (LT) */ }
    if (pad.R1 > 0) { /* right shoulder top (RB) */ }
    if (pad.R2 > 0) { /* right shoulder bottom / trigger (RT) */ }

    // Analog sticks (Vector2, values -1 to 1)
    const lx = pad.leftStick.x;  // left stick horizontal
    const ly = pad.leftStick.y;  // left stick vertical
    const rx = pad.rightStick.x;
    const ry = pad.rightStick.y;

    // Raw axis/button access
    pad.getAxisValue(0);    // float
    pad.getButtonValue(0);  // float 0-1
    pad.isButtonDown(0);    // boolean
    pad.setAxisThreshold(0.1); // ignore values below threshold
}

手柄事件:

// Plugin-level events (any gamepad)
this.input.gamepad.on('connected', (pad, event) => { /* ... */ });
this.input.gamepad.on('disconnected', (pad, event) => { /* ... */ });
this.input.gamepad.on('down', (pad, button, value) => { /* any button on any pad */ });
this.input.gamepad.on('up', (pad, button, value) => { /* ... */ });

// Gamepad-instance events
pad.on('down', (index, value, button) => { /* button on this specific pad */ });
pad.on('up', (index, value, button) => { /* ... */ });

振动(实验性,依赖硬件/浏览器):

if (pad.vibration) {
    pad.vibration.playEffect('dual-rumble', {
        duration: 200,
        strongMagnitude: 1.0,
        weakMagnitude: 0.5
    });
}
按键组合

监听一系列按键:

// String-based combo
this.input.keyboard.createCombo('PHASER');

// Array of key codes (Konami code)
this.input.keyboard.createCombo(
    [ 38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 13 ],
    { resetOnMatch: true }
);

// Listen for match
this.input.keyboard.on('keycombomatch', (keyCombo, event) => {
    console.log('Combo matched!');
});

createCombo(keys, config):

  • keys:一个字符串(每个字符是一个按键)或一个按键代码 / Key 对象数组
  • config.resetOnWrongKey(布尔值,默认 true) - 如果按错键则重置进度
  • config.maxKeyDelay(数字,默认 0) - 按键之间的最大毫秒数;0 = 无限制
  • config.resetOnMatch(布尔值,默认 false) - 成功匹配后重置组合
  • config.deleteOnMatch(布尔值,默认 false) - 首次匹配后移除组合

有关详细配置选项、API 参考表和源文件映射,请参阅参考指南。

评论

支持 Markdown