移动触控

移动游戏行业的未来无疑是 Web,许多开发者在游戏开发过程中会选择“移动优先”的方法——在现代世界中,这通常也包括实现触控操作。在本教程中,我们将了解在 HTML 游戏中实现移动端控制是多么容易,并享受在支持触摸的移动设备上玩游戏的乐趣。

注意:游戏 Captain Rogers: Battle at Andromeda 是用 Phaser 构建的,控件的管理也是基于 Phaser 的,但也可以用纯 JavaScript 来实现。使用 Phaser 的好处是它提供了辅助变量和函数,使开发更轻松快捷,但最终选择哪种方法完全取决于您。

纯 JavaScript 方法

我们可以自己实现触摸事件——设置事件监听器并将相关函数分配给它们会非常直接。

js
const el = document.querySelector("canvas");
el.addEventListener("touchstart", handleStart);
el.addEventListener("touchmove", handleMove);
el.addEventListener("touchend", handleEnd);
el.addEventListener("touchcancel", handleCancel);

这样,触摸移动设备屏幕上的游戏 <canvas> 就会触发事件,从而我们可以以任何我们想要的方式来操作游戏(例如,移动飞船)。事件如下:

  • touchstart 当用户将手指放在屏幕上时触发。
  • touchmove 当用户在触摸屏幕时移动手指时触发。
  • touchend 当用户停止触摸屏幕时触发。
  • touchcancel 当触摸被取消时触发,例如当用户将手指移出屏幕时。

注意:触摸事件参考文章提供了更多示例和信息。

纯 JavaScript 演示

让我们在 GitHub 上提供的 一个小演示中实现移动端支持,这样我们就可以通过触摸移动设备上的屏幕来移动玩家的飞船。

我们将使用两个事件:touchstarttouchmove,这两个事件都由一个函数处理。为什么?touchHandler 函数将为飞船的位置分配适当的变量,以便我们可以同时用于这两种情况:当玩家触摸屏幕但没有移动它时 (touchstart),以及当手指在屏幕上移动时 (touchmove)。

js
document.addEventListener("touchstart", touchHandler);
document.addEventListener("touchmove", touchHandler);

touchHandler 函数如下所示:

js
function touchHandler(e) {
  if (e.touches) {
    playerX = e.touches[0].pageX - canvas.offsetLeft - playerWidth / 2;
    playerY = e.touches[0].pageY - canvas.offsetTop - playerHeight / 2;
    output.textContent = `Touch:\nx: ${playerX}, y: ${playerY}`;
    e.preventDefault();
  }
}

如果发生触摸(touches 对象不为空),那么我们将在该对象中获得所需的所有信息。我们可以获取第一个触摸(e.touches[0],我们的示例不支持多点触控),提取 pageXpageY 变量,并通过减去 Canvas 的偏移量(Canvas 与屏幕边缘的距离)以及玩家宽度和高度的一半来设置屏幕上玩家飞船的位置。

Touch controls for the player's ship, with visible output of the x and y position.

为了查看它是否正常工作,我们可以使用 output 元素输出 xy 位置。preventDefault() 函数是必需的,以防止浏览器进行移动——如果没有它,您将获得默认行为,Canvas 会在页面上拖动,这将显示浏览器滚动条,看起来很杂乱。

Phaser 中的触摸事件

我们不必自己做这些;像 Phaser 这样的框架提供了用于管理触摸事件的系统——请参阅 管理触摸事件

指针理论

一个 指针代表触摸屏上的单个手指。Phaser 默认启动两个指针,因此两个手指可以同时执行一个操作。Captain Rogers 是一个简单的游戏——它可以由两个手指控制,左手指移动飞船,右手指控制飞船的枪。没有多点触控或手势——一切都由单个指针输入处理。

您可以通过使用 `this.game.input.addPointer` 来为游戏添加更多指针,最多可以同时管理十个指针。最近使用的指针可在 `this.game.input.activePointer` 对象中找到——屏幕上最近活动的那个手指。

如果您需要访问特定指针,所有指针都可以在 `this.game.input.pointer1`、`this.game.input.pointer2` 等处找到。它们是动态分配的,因此如果您用三个手指触摸屏幕,则 pointer1pointer2pointer3 将处于活动状态。例如,移除第二个手指不会影响其他两个,再次设置它将使用第一个可用的属性,因此 pointer2 将再次被使用。

您可以通过 `this.game.input.x` 和 `this.game.input.y` 变量快速获取最近活动指针的坐标。

输入事件

除了直接使用指针之外,还可以监听 `this.game.input` 事件,例如 `onDown`、`onUp`、`onTap` 和 `onHold`。

js
this.game.input.onDown.add(itemTouched, this);

function itemTouched(pointer) {
  // Do something
}

当通过触摸屏幕分发 `onDown` 事件时,将执行 `itemTouched()` 函数。pointer 变量将包含激活事件的指针信息。

这种方法使用了通用可用的 `this.game.input` 对象,但您也可以通过使用 `onInputOver`、`onInputOut`、`onInputDown`、`onInputUp`、`onDragStart` 或 `onDragStop` 来检测任何游戏对象(如精灵或按钮)上的操作。

js
this.button.events.onInputOver.add(itemTouched, this);

function itemTouched(button, pointer) {
  // Do something
}

这样,您就可以将事件附加到游戏中的任何对象,例如玩家的飞船,并对用户执行的操作做出反应。

使用 Phaser 的一个额外好处是,您创建的按钮将接受任何类型的输入,无论是移动设备上的触摸还是桌面上的点击——框架会在后台为您处理这些。

实现

添加一个监听用户输入的交互式对象的最简单方法是创建一个按钮。

js
const buttonEnclave = this.add.button(
  10,
  10,
  "logo-enclave",
  this.clickEnclave,
  this,
);

这个按钮是在 `MainMenu` 状态中形成的——它将位于屏幕左上角十像素处,使用 `logo-enclave` 图片,并在被触摸时执行 `clickEnclave()` 函数。这开箱即用地支持移动和桌面设备。主菜单中有几个按钮,包括启动游戏的那个。

对于实际游戏玩法,我们不是创建更多按钮并用它们覆盖狭小的移动屏幕,而是可以使用一些不同的方法:我们将创建响应给定操作的不可见区域。从设计的角度来看,最好是扩大活动区域,而不是用按钮图像覆盖半个屏幕。例如,点击屏幕的右侧将发射武器。

js
this.buttonShoot = this.add.button(
  this.world.width * 0.5,
  0,
  "button-alpha",
  null,
  this,
);
this.buttonShoot.onInputDown.add(this.goShootPressed, this);
this.buttonShoot.onInputUp.add(this.goShootReleased, this);

上面的代码将创建一个新的按钮,使用一个透明图片覆盖屏幕的右半部分。如果您想执行更复杂的操作,可以分别分配 `on input down` 和 `on input up` 的函数,但在此游戏中,触摸屏幕的右侧将向右发射子弹——这就是我们在此案例中需要的所有内容。

可以通过创建四个方向按钮来管理玩家的移动,但我们可以利用触摸屏的优势,通过拖动玩家的飞船来移动。

js
const player = this.game.add.sprite(30, 30, "ship");
player.inputEnabled = true;
player.input.enableDrag();
player.events.onDragStart.add(onDragStart, this);
player.events.onDragStop.add(onDragStop, this);

function onDragStart(sprite, pointer) {
  // Do something when dragging
}

我们可以拖动飞船并在其间执行其他操作,并在拖动停止时做出反应。在 Phaser 中启用拖动功能后,它将开箱即用——您无需手动设置精灵的位置,因此您可以将 `onDragStart()` 函数留空,或者放置一些调试输出以查看其是否正常工作。pointer 元素包含 xy 变量,存储被拖动元素的当前位置。

专用插件

您可以使用专门处理触摸事件、渲染 UI 控件等的插件。以下是一些使用虚拟游戏手柄和操纵杆的插件示例:

对于像虚拟游戏手柄这样的基本插件,您可以下载脚本并将其添加到您的页面中。

html
<script src="js/phaser.min.js"></script>
<!-- https://github.com/ShawnHymel/phaser-plugin-virtual-gamepad -->
<script src="js/phaser-plugin-virtual-gamepad.js"></script>

然后将它们包含在您的脚本中,并使用类似下面的代码片段:

js
// Add the VirtualGamepad plugin to a Phaser 2 game
this.gamepad = this.game.plugins.add(Phaser.Plugin.VirtualGamepad);
// Add a joystick to the game
this.joystick = this.gamepad.addJoystick(100, 420, 1.2, "gamepad");
// Add a button to the game
this.button = this.gamepad.addButton(400, 420, 1.0, "gamepad");

有关更多信息,请查看 非官方 Phaser 插件目录,看看是否有适合您需求的内容。

总结

这涵盖了为移动设备添加触摸控件的内容;在下一篇文章中,我们将了解如何添加键盘和鼠标支持。