移动端触摸控制

未来手游一定是Web的天下,许多开发在游戏开发过程中首先选择手游 — 既然如此,触摸控制是不可少的。我们将在本教程中了解怎样简单地在移动端H5游戏中实现触摸控制 ,只要移动端支持触摸,你就可以尽情的玩。

说明:游戏 Captain Rogers: Battle at Andromeda 是基于Phaser 和Phaser-based管理控制,但它也可以用纯JavaScript实现。使用Phaser的好处它提供了辅助变量和方法可以直接调用,有助于快速的开发游戏,这需要根据项目实际情况选择。

纯 JavaScript 方式实现

我们可以实现自己的触摸事件 — 给document添加事件监听,并传入自定义功能的方法,非常简单:

var el = document.getElementsByTagName("canvas")[0];
el.addEventListener("touchstart", handleStart);
el.addEventListener("touchmove", handleMove);
el.addEventListener("touchend", handleEnd);
el.addEventListener("touchcancel", handleCancel);

这样, 在移动设备上屏幕上触摸游戏的 <canvas> 将触发这些事件,因为我们就可以随意操控游戏(如:移动太空船)。 事件如下所示:

  • touchstart 当用户手指放在屏幕上触发。
  • touchmove 当他们在屏幕上移动手指时触发。
  • touchend 当用户在屏幕上停止移动时触发。
  • touchcancel 触摸被取消是触发,例如当用户将他们的手指移动到屏幕之外时。

说明: 这篇 touch events 参考文章提供了更多的实例和介绍。

纯JavaScript示例

这个实现了移动端触摸的little demo代码已经放到了GibHub上,我们下载这个示例就可以实现在移动端屏幕上移动飞船。

我们将两种事件:touchstart touchmove 放到一个方法里处理. 为什么呢? touchHandler 方法定义的飞船位置变量适合下面两种情况下: 当玩家触摸时,但不移动它(touchstart)和当手指在屏幕上开始移动 (touchmove):

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

touchHandler 方法的代码如下:

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.innerHTML = "Touch: "+ " x: " + playerX + ", y: " + playerY;
        e.preventDefault();
    }
}

If the touch occurs (touches object is not empty), then we will have all the info we need in that object. We can get the first touch (e.touches[0], our example is not multitouch-enabled), extract the pageX and pageY variables and set the player's ship position on the screen by subtracting the Canvas offset (distance from the Canvas and the edge of the screen) and half the player's width and height.

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

To see if it's working correctly we can output the x and y positions using the output element. The preventDefault() function is needed to prevent the browser from moving — without it you'd have the default behaviour, and the Canvas would be dragged around the page, which would show the browser scroll bars and look messy.

Touch events in Phaser

We don't have to do this on our own; frameworks like Phaser offer systems for managing touch events for us — see managing the touch events.

Pointer theory

A pointer represents a single finger on the touch screen. Phaser starts two pointers by default, so two fingers can perform an action at once. Captain Rogers is a simple game — it can be controlled by two fingers, the left one moving the ship and the right one controlling the ship's gun. There's no multitouch or gestures — everything is handled by single pointer inputs.

You can add more pointers to the game by using; this.game.input.addPointer up to ten pointers can be managed simultaneously. The most recently used pointer is available in the this.game.input.activePointer object — the most recent finger active on the screen.

If you need to access a specific pointer, they are all available at, this.game.input.pointer1this.game.input.pointer2, etc. They are assigned dynamically, so if you put three fingers on the screen, then, pointer1pointer2, and pointer3 will be active. Removing the second finger, for example, won't affect the other two, and setting it back again will use the first available property, so pointer2 will be used again.

You can quickly get the coordinates of the most recently active pointer via the this.game.input.x and this.game.input.y variables.

Input events

Instead of using the pointers directly it is also possible to listen for this.game.input events, like onDown, onUp, onTap and onHold:

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

function itemTouched(pointer) {
    // do something
}

The itemTouched() function will be executed when the onDown event is dispatched by touching the screen. The pointer variable will contain the information about the pointer that activated the event.

This approach uses the generally available this.game.input object, but you can also detect the actions on any game objects like sprites or buttons by using onInputOver, onInputOut, onInputDown, onInputUp, onDragStart, or onDragStop:

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

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

That way you'll be able to attach an event to any object in the game, like the player's ship, and react to the actions performed by the user.

An additional advantage of using Phaser is that the buttons you create will take any type of input, whether it's a touch on mobile or a click on desktop — the framework sorts this out in the background for you.

Implementation

The easiest way to add an interactive object that will listen for user input is to create a button:

var buttonEnclave = this.add.button(10, 10, 'logo-enclave', this.clickEnclave, this);

This one is formed in the MainMenu state — it will be placed ten pixels from the top left corner of the screen, use the logo-enclave image, and execute the clickEnclave() function when it is touched. This will work on mobile and desktop out of the box. There are a few buttons in the main menu, including the one that will start the game.

For the actual gameplay, instead of creating more buttons and covering the small mobile screen with them, we can use something a little bit different: we'll create invisible areas which respond to the given action. From a design point of view, it is better to make the field of activity bigger without covering half of the screen with button images. For example, tapping on the right side of the screen will fire the weapon:

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

The code above will create a new button using a transparent image that covers the right half of the screen. You can assign functions on input down and input up separately if you'd like to perform more complicated actions, but in this game touching the right side of the screen will simply fire the bullets to the right — this is all we need in this case.

Moving the player could be managed by creating the four directional buttons, but we can take the advantage of touch screens and drag the player's ship around:

var 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
}

We can pull the ship around and do something in the meantime, and react when the drag is stopped. Hauling in Phaser, if enabled, will work out of the box — you don't have to set the position of the sprite yourself manually, so you could leave the onDragStart() function empty, or place some debug output to see if it's working correctly. The pointer element contains the x and y variables storing the current position of the dragged element.

Dedicated plugins

You could go even further and use dedicated plugins like Virtual Joystick — this is a paid, official Phaser plugin, but you can find free and open source alternatives. The initialization of Virtual Joystick looks like this:

this.pad = this.game.plugins.add(Phaser.VirtualJoystick);
this.stick = this.pad.addStick(30, 30, 80, 'generic');

In the create() function of the Game state we're creating a virtual pad and a generic stick that has four directional virtual buttons by default. This is placed 30 pixels from the top and left edges of the screen and is 80 pixels wide.

The stick being pressed can be handled during the gameplay in the update function like so:

if(this.stick.isDown) {
    // move the player
}

We can adjust the player's velocity based on the current angle of the stick and move him appropriately.

摘要

这篇文章主要讲解如何在移动端实现触摸控制; 下一篇文章我们将看到怎样添加键盘和鼠标支持。