十三、适应不同的设备

这一章讲述了如何让游戏适应不同的设备。到目前为止,您一直在开发只能在有键盘和鼠标的设备上工作的示例,比如笔记本电脑或台式机。JavaScript 程序的一个好处是它们也可以在智能手机和平板电脑上运行,这是一个正在蓬勃发展的市场。让你的游戏运行在这样的平台上会为你的游戏带来很多额外的玩家。为了在智能手机或平板电脑上玩游戏,你需要处理触摸输入,就像处理键盘和鼠标输入一样。

另一件你需要注意的事情是你想要制作游戏的设备上的各种屏幕尺寸。本章向您展示如何创建自动适应任何屏幕大小的游戏,无论是巨大的 24 英寸桌面显示器还是微小的智能手机屏幕。

允许画布改变大小

为了允许自动调整屏幕尺寸,你需要做的第一件事是放置canvas元素,使得画布自动缩放到页面的大小。一旦完成,你可以检索画布的大小,将其与游戏屏幕的实际大小进行比较,并执行缩放操作。在 Painter 中,这是您在 HTML 页面上放置canvas元素的方式:

<div id="gameArea">

    <canvas id="mycanvas" width="800" height="480"> </canvas>

</div>

如您所见,您定义了一个名为gameAreadiv元素。在这个div元素中是一个单独的canvas元素。为了让canvas元素自动缩放,你需要将它的宽度和高度设置为浏览器窗口宽度和高度的 100%。这样,当浏览器窗口改变大小时,canvas元素也会随之改变大小。此外,你要尽可能使你显示的页面整洁(没有空白)。为了做到这一点,您使用了样式表。样式表是定义网页元素外观的好方法。不同的 HTML 页面可以使用相同的样式表来确保统一的设计。这是示例游戏的样式表:

html, body {
    margin: 0;
}
#gameArea {
    position: absolute;
}

#mycanvas {
    position: absolute;
    width: 100%;
    height: 100%;
}

深入讨论样式表的可能性不在本书的范围内,但是如果你想了解更多,你可以阅读 Simon Collision 的CSS Web 开发入门 (Apress,2006)或者 Jon Ducket 的 HTML 和 CSS(Wiley,2011)。

在这个特殊的例子中,您要做几件事情。首先,定义htmlbody元素没有边距。这样,如果你愿意,画布可以完全填满屏幕。然后,定义被称为gameAreamycanvas的东西在页面上的绝对位置。这样,不管 HTML 页面中还有什么其他元素,这些元素总是被放置在它们想要的位置。最后,您指出mycanvas的宽度和高度是屏幕的 100%。结果,画布现在填满了浏览器的整个显示,并自动缩放到不同的分辨率。

注意让画布填满整个浏览器并不一定是所有设置的最佳解决方案。在某些情况下,用户可能很难在不意外点击浏览器之外的情况下与浏览器窗口边缘的元素进行交互。在这种情况下,您可以考虑增加边距。

设置本地游戏大小

既然画布会自动缩放到浏览器窗口的大小,您就不能再将画布分辨率用作原生游戏分辨率。如果你这样做了,你将不得不在调整画布大小时重新计算所有游戏对象的位置。更好的方法是定义一个本地游戏大小。然后,在绘制对象时,缩放它们的位置和大小,使其与实际的画布大小相匹配。

第一步是能够指出本地游戏的大小应该是多少。当您调用Game.start方法时,您可以这样做。 你扩展了这个方法,让它接受两个额外的参数来定义游戏的宽度和高度,然后从 HTML 页面调用它:

Game.start('gameArea', 'mycanvas', 1440, 1080);

Jewel Jam 游戏的原生分辨率较大(1440 × 1080),所以如果你想让游戏在所有智能手机和平板电脑上正常运行,缩放肯定是必要的。您还可以传递包含画布的div元素的名称。原因是你改变了这个div的边距,这样游戏就可以很好地显示在屏幕中间,以防屏幕比例与游戏大小比例不同(见图 13-1 )。

9781430265382_Fig13-01.jpg

图 13-1 。你必须确保游戏屏幕总是很好地显示在浏览器窗口的中间!

Game.start方法中,你将游戏的大小存储在一个成员变量_size中。您还定义了一个只读属性size 来访问成员变量。通过这种方式,你向Game类的用户表明,他们不应该在游戏运行时改变游戏的原生尺寸。您将游戏区域和画布的标识符传递给Canvas2D.initialize方法,因为您必须在属于Canvas2D的方法中完成缩放和定位精灵的跑腿工作。这就是完整的Game.start方法:

Game_Singleton.prototype.start = function (divName, canvasName, x, y) {
    this._size = new Vector2(x, y);
    Canvas2D.initialize(divName, canvasName);
    this.loadAssets();
    this.assetLoadingLoop();
};

Canvas2D中,你存储了一个对div元素的引用,这样你就可以在以后使用它来改变边距。检索该元素的方式与检索canvas元素的方式相同:

this._canvas = document.getElementById(canvasName);
this._div = document.getElementById(divName);

您需要计算每次调整浏览器窗口大小时要应用的缩放因子。您可以通过附加事件处理函数来实现这一点,如下所示:

window.onresize = Canvas2D_Singleton.prototype.resize;

现在,每当调整窗口大小时,就会调用resize方法 。最后,您显式调用resize方法:

this.resize();

通过调用这个方法,在游戏开始时根据浏览器窗口大小计算出合适的比例。

调整游戏大小

每当调整窗口大小时,就调用resize方法。当这种情况发生时,你需要计算两件事:

  • 绘制精灵所需的比例
  • 游戏区域的边距,以便在浏览器窗口的中间很好地绘制游戏

在开始计算这些东西之前,将canvasdiv元素存储在两个局部变量中。这样做可以节省一些编写工作,因为您需要在这个方法中经常访问这些元素:

var gameCanvas = Canvas2D._canvas;
var gameArea = Canvas2D._div;

现在你来计算一下原生游戏大小的比例:

var widthToHeight = Game.size.x / Game.size.y;

下一步是计算浏览器窗口比率。首先通过访问innerWidthinnerHeight变量来获取浏览器窗口的大小。一旦你有了这些,你就可以计算浏览器窗口比例:

var newWidth = window.innerWidth;
var newHeight = window.innerHeight;
var newWidthToHeight = newWidth / newHeight;

你不希望游戏屏幕看起来被压扁或拉长,所以你必须确保当窗口大小改变时,比例不会改变。当新的比例大于原生游戏尺寸比例时,这意味着浏览器窗口太宽。因此,您需要重新计算宽度以修正比率,如下所示:

if (newWidthToHeight > widthToHeight) {
    newWidth = newHeight * widthToHeight;
}

另一种情况是新比率较小,意味着浏览器窗口太高。在这种情况下,您需要重新计算高度:

newHeight = newWidth / widthToHeight;

现在你已经计算出了正确的宽度和高度,你首先改变gameArea div使其具有这个高度和宽度。只需通过编辑元素的样式,即可实现如下操作:

gameArea.style.width = newWidth + 'px';
gameArea.style.height = newHeight + 'px';

为了在屏幕中间显示gameArea元素,您还需要定义边距,如下:

gameArea.style.marginTop = (window.innerHeight - newHeight) / 2 + 'px';
gameArea.style.marginLeft = (window.innerWidth - newWidth) / 2 + 'px';
gameArea.style.marginBottom = (window.innerHeight - newHeight) / 2 + 'px';
gameArea.style.marginRight = (window.innerWidth - newWidth) / 2 + 'px';

将新的宽度和高度与实际的宽度和高度之差除以 2,并将这些值设置为边距。例如,如果期望的游戏屏幕宽度是 800,但是窗口实际上是 900 像素宽,那么您可以在每边创建 50 像素的边距,以便将元素绘制在屏幕的中间。最后,更改画布的大小,使其具有所需的宽度和高度(以及正确的比例):

gameCanvas.width = newWidth;
gameCanvas.height = newHeight;

绘制精灵的比例现在可以很容易地计算出来了。您可以定义一个属性来完成这项工作:

Object.defineProperty(Canvas2D_Singleton.prototype, "scale",
    {
        get: function () {
            return new Vector2(this._canvas.width / Game.size.x,
                this._canvas.height / Game.size.y);
        }
    });

当您绘制图像时,您只需在执行绘制操作之前应用此比例。这是通过向Canvas2D.drawImage方法添加几行代码来实现的——一行用于检索标尺,一行用于应用标尺:

var canvasScale = this.scale;
this._canvasContext.scale(canvasScale.x, canvasScale.y);

Canvas2D.drawText方法中做同样的事情。完整的代码,请看属于本章的 JewelJam1 例子。

重新设计鼠标输入处理

做完所有这些之后,您已经在divcanvas元素周围移动了相当多的距离。在画师游戏中,你假设画布总是画在屏幕的左上角。在这里,情况不再如此。如果你想检测鼠标在屏幕上的点击,当计算鼠标在游戏屏幕上的本地位置时,你需要考虑画布在屏幕上的位置。此外,因为您应用了缩放,所以您还需要相应地缩放鼠标位置。

这是重新审视输入处理类MouseKeyboard设计方式的好机会。你从未正确实现的一件事是处理键盘或鼠标按钮的上下相对于键盘或鼠标按钮按下。此外,在Mouse类中,你只考虑了左键点击;而在Keyboard类中,同一时间只能按下一个键。您可以通过 JavaScript 的原型系统使用面向对象编程的力量来设计更好的解决方案。

第一步是创建一个简单的类,可以存储按钮的状态(不管是按键还是鼠标按钮)。我们姑且称这个类为ButtonStateButtonState类非常简单,只有两个(boolean)成员变量:一个指示按钮是否按下,另一个指示按钮是否被按下。下面是完整的类:

function ButtonState() {
    this.down = false;
    this.pressed = false;
}

现在您使用Mouse类中的 ButtonState实例来表示鼠标左键、中键和右键的状态。这是Mouse的新构造者的样子:

function Mouse_Singleton() {
    this._position = Vector2.zero;
    this._left = new ButtonState();
    this._middle = new ButtonState();
    this._right = new ButtonState();
    document.onmousemove = handleMouseMove;
    document.onmousedown = handleMouseDown;
    document.onmouseup = handleMouseUp;
}

如您所见,按钮的成员变量位于与每个位置相关联的成员变量旁边。您可以添加只读属性来访问这些变量。例如,如果您想检查鼠标中键是否按下,您可以使用下面一行简单的代码来完成:

if (Mouse.middle.down)
    // do something

在事件处理函数中,您正在更改成员变量的值。在handleMouseMove事件处理函数中,您必须计算鼠标位置。这也是您确保鼠标位置根据应用于画布的缩放和偏移进行缩放和移动的地方。下面是完整的handleMouseMove功能:

function handleMouseMove(evt) {
    var canvasScale = Canvas2D.scale;
    var canvasOffset = Canvas2D.offset;
    var mx = (evt.pageX - canvasOffset.x) / canvasScale.x;
    var my = (evt.pageY - canvasOffset.y) / canvasScale.y;
    Mouse._position = new Vector2(mx, my);
}

现在,无论何时移动鼠标,都会正确计算鼠标位置。接下来您需要做的是处理鼠标按钮按下和按下事件。鼠标按钮只有在当前按下时才会被按下,而不是在之前的游戏循环迭代中。对于鼠标左键,您可以这样表达:

if (evt.which === 1) {
    if (!Mouse._left.down)
        Mouse._left.pressed = true;
    Mouse._left.down = true;
}

evt.which值表示您是在处理鼠标左键(1)、中键(2)还是右键(3)。请看一下 JewelJam1 示例,了解完整的handleMouseDown事件处理函数。在handleMouseUp事件处理程序中,您再次将down变量设置为false。下面是完整的函数:

function handleMouseUp(evt) {
    handleMouseMove(evt);

    if (evt.which === 1)
        Mouse._left.down = false;
    else if (evt.which === 2)
        Mouse._middle.down = false;
    else if (evt.which === 3)
        Mouse._right.down = false;
}

您可以看到在这里也调用了handleMouseMove函数。这样做是为了确保当您按下鼠标按钮时,鼠标位置可用。如果您省略了这一行,玩家在没有移动鼠标的情况下开始游戏并按下了鼠标按钮,游戏将试图在没有位置信息的情况下处理鼠标按钮的按下。这就是为什么你在handleMouseDownhandleMouseUp函数中都调用了handleMouseMove函数(尽管后者可能不是必需的)。

最后,在每个游戏循环迭代结束时,Mouse对象被重置。这里你唯一需要做的事情就是将pressed变量再次设置为【T2:】

Mouse_Singleton.prototype.reset = function () {
    this._left.pressed = false;
    this._middle.pressed = false;
    this._right.pressed = false;
};

通过在每次游戏循环迭代后将pressed变量设置为false,可以确保鼠标或按键只被处理一次。

数组

你也可以重新设计键盘输入处理,现在你已经有了这个ButtonState类——但是在你这么做之前,让我们引入你需要的另一个概念,叫做数组。数组基本上是一个编号列表。看看下面的例子:

var emptyArray = [];
var intArray = [4, 8, 15, 16, 23, 42];

这里你可以看到数组变量的两个声明和初始化。第一个声明是一个空数组(没有元素)。第二个变量intArray,引用一个长度为 6 的数组。您可以通过索引来访问数组中的元素,其中数组中的第一个元素的索引为 0:

var v = intArray[0]; // contains the value 4
var v2 = intArray[4]; // contains the value 23

您使用方括号来访问数组中的元素。您也可以使用相同的方括号来修改数组中的值:

intArray[1] = 13; // intArray now is [4, 13, 15, 16, 23, 42]

还可以向数组中添加一个元素:

intArray.push(-3); // intArray now is [4, 13, 15, 16, 23, 42, -3]

最后,每个数组都有一个length变量,您可以访问它来检索长度:

var l = intArray.length; // contains the value 7

您可以结合使用数组和for循环来做有趣的事情。这里有一个例子:

for (var i = 0; i < intArray.length; i++) {
    intArray[i] += 10;
}

这将遍历数组中的所有元素,每个元素加 10。所以,这段代码执行后,intArray指的是[14, 23, 25, 26, 33, 52, 7]

除了以您刚才看到的方式初始化数组之外,还有另一种创建数组的方式,如下所示:

var anotherArray = new Array(3);

此示例创建一个大小为 3 的数组,然后您可以填充该数组:

anotherArray[0] = "hello";

您甚至可以用 JavaScript 创建多维数组。例如:

var tictactoe = new Array(3);
tictactoe[0] = ['x', 'o', ' '];
tictactoe[1] = [' ', 'x', 'o'];
tictactoe[2] = [' ', 'o', 'x'];

所以,数组的元素也可以是数组。这些类型的网格结构在表示像国际象棋这样的游戏中的游戏场时特别有用。因为数组是作为引用存储的(就像类的实例一样),所以使用这样的二维数组表示网格在计算上不是很高效。用一维数组表示网格有一个简单的方法,如下:

var rows = 10, cols = 15;
var myGrid = new Array(rows * cols);

现在可以按如下方式访问第i行和第j列的元素:

var elem = myGrid[i * cols + j];

图 13-2 显示了表达式语法图的另一部分,带有指定数组的语法。下一章将更详细地讨论在游戏中使用数组来表示结构和网格。

9781430265382_Fig13-02.jpg

图 13-2 。表达式的部分语法图

使用数组处理键盘输入

让我们看看如何使用数组更有效地处理按键。因为现在有了ButtonState类,所以也可以用它来处理键盘输入,并为每个按键状态存储一个ButtonState实例。所以,在Keyboard_Singleton的构造函数中,创建一个包含所有可能的关键状态的数组,如下:

this._keyStates = [];
for (var i = 0; i < 256; ++i)
    this._keyStates.push(new ButtonState());

现在,只要检测到按键按下,您就可以设置按键的按下状态,如下所示:

function handleKeyDown(evt) {
    var code = evt.keyCode;
    if (code < 0 || code > 255)
        return;
    if (!Keyboard._keyStates[code].down)
        Keyboard._keyStates[code].pressed = true;
    Keyboard._keyStates[code].down = true;
}

如您所见,您可以像处理鼠标按钮一样处理按键。添加到Keyboard_Singleton中的以下两种方法使得检测按键是否被按下变得非常容易:

Keyboard_Singleton.prototype.pressed = function (key) {
    return this._keyStates[key].pressed;
};
Keyboard_Singleton.prototype.down = function (key) {
    return this._keyStates[key].down;
};

JewelJam 游戏中,你不需要键盘,因为所有的互动都是通过鼠标或触摸屏进行的。后面的例子再次使用了键盘。这也意味着您基本上可以忽略任何发生的键盘事件。如果你没有在游戏循环方法中加入任何键盘操作,游戏会完全忽略玩家按下的任何键。

触摸屏输入

因为智能手机和平板电脑也有网络浏览器,所以可以在这些设备上运行 JavaScript 应用。实际上,JavaScript 是目前唯一一种无需重写代码就能开发出运行在台式机、笔记本电脑、手机和平板电脑上的应用的跨平台方法。例如,您可以将最终的画师示例放在服务器上,然后在平板电脑上访问网页。但是,你不能玩这个游戏,因为你没有处理过触摸输入。在本节中,您将为游戏添加触摸输入功能。幸运的是,这在 JavaScript 中非常容易做到!

在开始编写 JavaScript 代码之前,您需要注意 HTML 页面中的一些事情。原因是平板电脑和智能手机设备定义了网页上的默认触摸行为,例如滚动或缩放网页的能力。这些互动会干扰玩游戏,所以你要把它们关掉。这可以在 HTML 文件的标题中完成,方法是在标题中添加以下标记:

<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

HTML 中的 视区 定义了视图中的页面部分。例如,当您导航到一个页面时,它会自动放大一部分,这是通过修改视口的大小来实现的。在meta标签中,你可以定义视口的属性。这个例子告诉浏览器三件事:视窗的宽度与设备的宽度相同,第一次查看页面时页面的缩放比例为 1,用户不能缩放页面。结果,所有改变 HTML 页面的定位或缩放的触摸交互都被关闭。

现在,您可以开始编写 JavaScript 代码来处理触摸事件。JavaScript 中触摸输入的工作方式是,每次用户触摸屏幕时,都会生成一个touchstart事件 ,并为该触摸分配一个唯一的标识符。当用户用另一个手指触摸屏幕时,另一个带有新的唯一标识符的touchstart事件发生。当用户在屏幕上移动手指时,会产生一个touchmove事件,并且可以从该事件中获得正在移动的手指的唯一标识符。类似地,当用户停止触摸屏幕时,生成一个touchend事件,并且可以再次从该事件中检索已经停止触摸屏幕的手指的标识符。

为了处理触摸,创建一个类Touch_Singleton和相关的单个实例Touch,类似于您创建KeyboardMouse对象的方式。在类的构造函数中,创建一个数组,在其中存储每次触摸和相关信息。此外,您还可以存储每次触摸是否是按压。触摸按压意味着在先前的游戏循环迭代中手指没有触摸屏幕,但是在当前的迭代中手指触摸了屏幕。此外,您必须附加事件处理函数。下面是完整的构造函数方法:

function Touch_Singleton() {
    this._touches = [];
    this._touchPresses = [];
    document.addEventListener('touchstart', handleTouchStart, false);
    document.addEventListener('touchend', handleTouchEnd, false);
    document.addEventListener('touchcancel', handleTouchEnd, false);
    document.addEventListener('touchleave', handleTouchEnd, false);
    document.body.addEventListener('touchmove', handleTouchMove, false);
}

对于浏览器来说,处理触摸相对较新(相对于键盘和鼠标输入)。因此,并非所有浏览器都使用相同的术语。这就是为什么您需要为不同种类的touchend事件添加事件监听器。然而,最终,您处理三种类型的事件:touchstarttouchmovetouchend、。它们都有自己的事件处理函数。

当用户开始触摸屏幕时,调用handleTouchStart功能。 在这个函数中,您只需将触摸事件存储在_touches数组中,以便以后需要时可以检索数据。然而,用户可能会同时用多个手指触摸屏幕。因此,该事件包含一系列新的触摸。你可以用变量changedTouches来访问这个数组。对于该数组中的每个元素,将触摸事件数据添加到_touches数组中。因为这些都是新的内容,所以您还需要将true值添加到_touchPresses变量中。这是完整的方法:

function handleTouchStart(evt) {
    evt.preventDefault();

    var touches = evt.changedTouches;
    for (var i = 0; i < touches.length; i++) {
        Touch._touches.push(touches[i]);
        Touch._touchPresses.push(true);

    }
}

注意,这个函数中的第一条指令是evt.preventDefault();。该方法调用确保触摸事件不用于默认行为(如滚动)。您还将该指令放在其他触摸事件处理程序函数中。在handleTouchEnd中,你需要从你的类中的两个数组中移除触摸。为此,您必须搜索整个数组,以找到属于特定 touch ID 的数据。为了使这变得简单一点,您在您的类中添加了一个方法getTouchIndexFromId,它会为您找到数组中的索引。稍后,您可以调用此方法来查找触摸在数组中的存储位置,然后移除该元素。

在该方法中,使用一个for循环遍历数组中的所有元素。当您找到与您正在寻找的触摸数据的标识符相匹配的触摸时,您返回该数据。如果找不到触摸数据,则返回值-1。下面是完整的方法:

Touch_Singleton.prototype.getTouchIndexFromId = function (id) {
    for (var i = 0, l = this._touches.length; i < l; ++i) {
        if (this._touches[i].identifier === id)
            return i;
    }
    return -1;
};

handleTouchEnd函数中, 用一个for循环遍历changedTouches变量。对于每一次触摸,您找到相应的索引并从两个数组(_touches_touchPresses)中移除触摸。从数组中移除一个元素是通过splice方法完成的。该方法采用一个索引和一个数字:索引指示应该从哪里移除元素,第二个参数指示应该移除多少个元素。这里有一个例子:

var myArray = [2, 56, 12, 4];
myArray.splice(0,1); // myArray now contains [56, 12, 4]
myArray.splice(1,2); // myArray now contains [56]

以下是完整的handleTouchEnd功能:

function handleTouchEnd(evt) {
    evt.preventDefault();
    var touches = evt.changedTouches;
    for (var i = 0; i < touches.length; ++i) {
        var id = Touch.getTouchIndexFromId(touches[i].identifier);
        Touch._touches.splice(id, 1);
        Touch._touchPresses.splice(id, 1);

    }
}

最后,在handleTouchMove、、中,您必须为已经改变的触摸更新触摸信息。这意味着你必须替换数组中的一个元素。splice方法接受第三个参数,在这个参数中您可以指定一个替换元素。看看属于这一章的代码,看看handleTouchMove事件处理函数的实现。

使得处理触摸输入更容易

您已经看到了 JavaScript 是如何处理触摸输入的。你可以在Touch_Singleton类中再添加一些方法,让它在游戏中更容易使用。首先是一个简单的方法来检索一个给定索引触摸的位置:

Touch_Singleton.prototype.getPosition = function (index) {
    var canvasScale = Canvas2D.scale;
    var canvasOffset = Canvas2D.offset;
    var mx = (this._touches[index].pageX - canvasOffset.x) / canvasScale.x;
    var my = (this._touches[index].pageY - canvasOffset.y) / canvasScale.y;
    return new Vector2(mx, my);
};

请注意,您必须应用与鼠标位置相同的位置和比例校正。触摸事件中的pageXpageY变量提供了玩家触摸屏幕的坐标。

检查你是否触摸了屏幕的某个区域也很有用。为此,您可以在 JewelJam1 示例中添加一个名为Rectangle的类。这是一个非常简单的类,类似于Vector2,用于表示矩形。还可以添加几个简单的方法来检查两个矩形是否相交(对碰撞检查有用)以及矩形是否包含某个位置。查看Rectangle.js文件,了解如何构造这个类。

您可以使用矩形来定义屏幕的各个部分。containsTouch方法检查作为参数提供给方法的矩形中是否有触摸

Touch_Singleton.prototype.containsTouch = function (rect) {
    for (var i = 0, l = this._touches.length; i < l; ++i) {
        if (rect.contains(this.getPosition(i)))
            return true;
    }
    return false;
};

在方法体中有一个for循环,它为数组中的每次触摸检查它的位置是否在矩形内。您重用了之前定义的getPosition方法。根据结果,该方法返回truefalse。还将以下属性添加到Touch :

Object.defineProperty(Touch_Singleton.prototype, "isTouchDevice",
    {
        get: function () {
            return ('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0);
        }
    });

这是一个非常简单的属性,用于检查您当前是否在使用触摸设备。并非所有的浏览器都以相同的方式检测到这一点,所以这是将其封装在方法或属性中的一个很好的理由。这样,您只需处理一次浏览器之间的这些差异。之后,访问Touch.isTouchDevice属性,就完成了。

向画师添加触摸输入

现在你已经有了这个漂亮的Touch物体,如果不在画师游戏中加入触感,那就太可惜了。属于本章的示例代码有一个带触摸输入的 Painter 版本。查看示例代码,特别是添加了触摸输入的Cannon.js文件。在触控界面中,你可以点击炮管中的彩球来切换到不同的颜色。在大炮外面敲击瞄准大炮。之后松开触球。

为了将触摸输入处理和鼠标输入处理分开,您可以如下重写handleInput方法:

Cannon.prototype.handleInput = function (delta) {
    if (Touch.isTouchDevice)
        this.handleInputTouch(delta);
    else
        this.handleInputMouse(delta);
};

现在你写两个不同的方法来处理触摸和鼠标输入。这就是handleInputTouch方法的样子:

Cannon.prototype.handleInputTouch = function (delta) {
    var rect = this.colorSelectRectangle;
    if (Touch.containsTouchPress(rect)) {
        if (this.currentColor === this.colorRed)
            this.currentColor = this.colorGreen;
        else if (this.currentColor === this.colorGreen)
            this.currentColor = this.colorBlue;
        else
            this.currentColor = this.colorRed;
    } else if (Touch.touching && !Touch.containsTouch(rect)) {
        var opposite = Touch.position.y - this.position.y;
        var adjacent = Touch.position.x - this.position.x;
        this.rotation = Math.atan2(opposite, adjacent);
    }
};

首先,检索代表加农炮部分的矩形,您可以触摸它来选择不同的颜色。添加一个简单的属性来计算这个矩形。然后检查矩形是否包含触摸按压。如果是这种情况,你改变颜色。通过使用一个if指令,你可以很容易地在三种颜色之间循环。

如果玩家正在触摸屏幕的某个地方,但不是在矩形内,你改变大炮的目标指向那个位置。还可以看看Ball类,看看它是如何处理触摸输入的!最后,另一件好事是根据应用是在平板电脑上运行还是在台式计算机上运行来加载不同的精灵:

if (Touch.isTouchDevice)
    sprites.gameover = loadSprite("spr_gameover_tap.png");
else
    sprites.gameover = loadSprite("spr_gameover_click.png");

当游戏在触摸设备上运行时,覆盖图有文本“点击继续”;否则显示“点击继续”。

注意许多现代笔记本电脑都包含触摸屏和键盘,如何自动确定玩家想要使用触摸还是键盘输入并不总是显而易见的。玩家可能希望在一些游戏中使用触摸屏,而在另一些游戏中使用键盘。一个可能的解决方案是同时接受两个输入。另一种解决方案是让玩家通过菜单设置来自己选择输入法。

你学到了什么

在本章中,您学习了:

  • 如何根据不同的设备自动调整游戏屏幕的尺寸
  • 如何根据游戏画面的尺寸自动校正鼠标位置
  • 什么是数组以及如何使用数组
  • 如何使用触摸界面来控制游戏中发生的事情