mcpField
P
phaser-input

phaser-input

github.com/yakoub-ai开发29195 次安装9 周前更新
代码仓库

关于 phaser-input

phaser-input 是介绍 Phaser 4 输入系统的技能,涵盖键盘、指针、触控和游戏手柄操作。它提供常见 API 用法,并附有移动端虚拟摇杆参考实现。

主要功能

  1. 01键盘绑定与按键状态
  2. 02指针事件和拖放
  3. 03触控与多点触控
  4. 04手柄按键与模拟摇杆
  5. 05虚拟摇杆参考实现

适用场景

  1. 01为游戏玩法映射键盘操作
  2. 02添加可点击和可拖动对象
  3. 03处理多点触控交互
  4. 04构建移动端移动控制

安装

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

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

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

附带文件references/input-api.mdreferences/virtual-joystick.md

详细介绍

Phaser 4 输入

Phaser 4 的输入系统支持键盘、鼠标/指针、触摸和游戏手柄。在场景中,所有输入都通过 this.input 访问。

键盘输入

方向键

一次调用即可创建方向键集合,用于跟踪方向键、空格键和 Shift 键:

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 键

使用 addKeys 按名称绑定任意按键:

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 */ }
}
单个按键

使用 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、JustDown 与 JustUp
方法行为
key.isDown按住按键时,每帧都为 true
Phaser.Input.Keyboard.JustDown(key)按键首次按下时,仅在那一帧为 true
Phaser.Input.Keyboard.JustUp(key)按键释放时,仅在那一帧为 true
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();
  }
}
按键组合(Konami 密码、作弊码)
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();
  });
}
禁用浏览器默认按键行为

防止浏览器拦截方向键(滚动页面)和其他按键:

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,
  ]);
}

指针/鼠标输入

全局指针事件

在 this.input 上监听游戏画布任意位置的事件:

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
  });
}

摄像机移动后,请使用 ptr.worldX/ptr.worldY,它们提供的是世界坐标,而非屏幕坐标。

让精灵可交互

在监听特定对象的指针事件之前,先调用 setInteractive():

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();
  });
}
拖放

启用游戏对象的拖动功能:

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();
  });
}
输入区域

使用 Zone 作为不可见的交互区域(适用于 UI 点击区域):

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

防止右键时显示浏览器上下文菜单:

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);
    }
  });
}
阻止事件传播

防止点击游戏对象时同时触发全局 pointerdown 处理程序:

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

触摸输入

触摸事件与指针 API 共用相同接口——在触摸设备上会自动触发 pointerdown、pointermove 和 pointerup。

多点触控

添加额外的指针槽位(默认总共 2 个;需要支持更多手指时可扩展):

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
}
滑动检测

Phaser 没有内置的滑动 API。请手动跟踪起始位置:

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');
      }
    }
  });
}
阻止浏览器手势
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 }
}

游戏手柄输入

Phaser 4 默认包含 Gamepad 插件。通过 this.input.gamepad 访问它。

连接
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`);
  });
}
读取按键
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 */ }
}
摇杆

每个轴的取值范围为 -1 到 1。请设置死区以防止漂移:

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);
  }
}

虚拟摇杆(移动设备)

Phaser 4 没有内置虚拟摇杆。对于移动游戏,请实现基于指针的摇杆,或使用参考实现。

请参阅 references/virtual-joystick.md,其中包含完整且可用于生产环境的 TypeScript 类,可绘制底座圆和摇杆帽,并提供归一化的 direction.x/direction.y 值。

该参考文件记录了两个常见错误:(1)首次触点锁定——拖动期间底座必须保持锁定;如果用户拖动时底座会“跳动”,说明有人在 onPointerMove 中更新了 baseX/baseY。(2)跨场景监听器丢失——如果摇杆在一个场景中有效、在另一个场景中无效,请通过 scene.launch() 在持久化的 InputScene 中仅实例化一次,而不要在每个游戏场景中分别实例化。

使用方式:

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);
  }
}

更多资源

参考文件
  • references/input-api.md —— InputPlugin、KeyboardPlugin、Key、Pointer、Gamepad 及所有输入事件的完整 API 参考
  • references/virtual-joystick.md —— 适用于移动游戏的完整 TypeScript 虚拟摇杆实现

评论

支持 Markdown