[翻译]Akihabara 指南,第四部分:地图的卷动

原文:http://bostongamejams.com/akihabara-tutorials/akihabara-tutorial-part-4-scrolling-map/
这部分有不少视频是在 youtubo 上的,你说我应该把它移到国内哪个视频网站上呢?

Akihabara 指南,第四部分:地图的卷动

Darius Kazemi 编写于 2010 年 06 月 27 日

这是系列指南的第四部分,我们将向你演示如何使用基于 HTML5 和 JavaScript 的 Akihabara 框架来编写 8 向射击游戏。Akihabara 是一个利用 HTML5 功能帮助创建游戏的 Javascript 库。使用 HTML5 编写游戏最棒的事情是你可以在任何平台、任何支持 HTML5 的浏览器运行它。这包括 Chrome,Firefox,Safari 和 iPhone/iPad,WebOS 设备上的 WebKit 浏览器,或者其他移动平台。

在这一部分,我们将创建一个比第三部分中的地图大得多的地图,然后将演示如何建立摄影机,以便于玩家移动时卷动地图。

开始前的重要提示

为了让这个指南起作用,你应当下载我们整理过的第三部分的代码,并从此处开始学习:为了使得代码更加一致,我们对变量命名进行了一些修改。同时,你应当重命名工作目录下的 playerSprite.png 为 player_sprite.png。

成品

本次课程结束后,你会得到类似这样的一个游戏。按下 Z 越过标题屏幕,然后使用方向键移动。注意摄影机是如何跟随玩家对象移动的。

大地图

好吧,卷动地图首先需要的是让地图尺寸大于屏幕的尺寸。做到这点是相当的容易。只需要添加一些数据到 loadMap 函数,让它的宽度和高度翻倍。

<br />
	function loadMap() {<br />
	  return help.asciiArtToMap([<br />
	&quot;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxx            xx        xxxx                                    xx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxx            xx        xxxxxxxxxxxxxxxxxxx                     xx&quot;,<br />
	&quot;xx                          xx        xxxxxxxxxxxxxxxxxxx                     xx&quot;,<br />
	&quot;xx                          xx                                                xx&quot;,<br />
	&quot;xx                          xx                                                xx&quot;,<br />
	&quot;xx                          xx                                                xx&quot;,<br />
	&quot;xx                          xx                                                xx&quot;,<br />
	&quot;xx                          xx                                                xx&quot;,<br />
	&quot;xx          xxxxxxxx   xxxxxxxxxxxxxxxxxxx                     xxxxxxxxxxxxxxxxx&quot;,<br />
	&quot;xx          xxxxxxxx   xxxxxxxxxxxxxxxxxxx                     xxxxxxxxxxxxxxxxx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xxxxxxxx                              xxxx                                    xx&quot;,<br />
	&quot;xxxxxxxx                              xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx            xxxxxxxxxxxxxxxxxx      xxxx                                    xx&quot;,<br />
	&quot;xx            xxxxxxxxxxxxxxxxxx      xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                xx                  xx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxx                xx                  xx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxx                xx                  xx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxx                xx                  xx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxx                xx                  xx&quot;,<br />
	&quot;xx                                    xxxx            xxxxxxxx                xx&quot;,<br />
	&quot;xx                                    xxxx            xxxxxxxx                xx&quot;,<br />
	&quot;xx                                    xxxx                xx                  xx&quot;,<br />
	&quot;xx                                    xxxx                xx                  xx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxx            xx        xxxx                xx                  xx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxx            xx        xxxx                xx                  xx&quot;,<br />
	&quot;xx                          xx        xxxx                xx                  xx&quot;,<br />
	&quot;xx                          xx        xxxx                                    xx&quot;,<br />
	&quot;xx                          xx        xxxx                                    xx&quot;,<br />
	&quot;xx                          xx        xxxx                                    xx&quot;,<br />
	&quot;xx                          xx        xxxx                                    xx&quot;,<br />
	&quot;xx                          xx        xxxx                                    xx&quot;,<br />
	&quot;xx          xxxxxxxx   xxxxxxxxxxxxxxxxxxx                                    xx&quot;,<br />
	&quot;xx          xxxxxxxx   xxxxxxxxxxxxxxxxxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xxxxxxxx                                                                      xx&quot;,<br />
	&quot;xxxxxxxx                                                                      xx&quot;,<br />
	&quot;xx                                                                            xx&quot;,<br />
	&quot;xx            xxxxxxxxxxxxxxxxxx      xxxx                                    xx&quot;,<br />
	&quot;xx            xxxxxxxxxxxxxxxxxx      xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xx                                    xxxx                                    xx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;,<br />
	&quot;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;,<br />
	    ], [ [null, ' '], [0, 'x'] ])<br />
	}<br />

初始化摄影机

接下来,我们实现摄影机本身。我们将在地图的 blit 函数中,在调用 gbox.blit 前实现。

<br />
	// 将玩家对象至于摄影机中央。map.w 和 map.h 数据告诉摄影机什么时候碰撞到地图边缘,以便停止卷动。<br />
	gbox.centerCamera(gbox.getObject('player', 'player_id'), {w: map.w, h: map.h});<br />

gbox.centerCamera 函数接受两个参数:想要摄影机跟随的对象,以及不希望摄影机卷动出去的盒子的边界。为了避免看到地图之外的区域,盒子的边界设置为整个地图的宽和高。

注意:添加了这个以后,你可能在 firefox 中遇到玩家精灵不能正确显示的 bug,在我们用自己编写的代码修复替换并修补这个 bug 之前,请在 Safari 或者 Chrome 中测试。

gbox.getObject 函数返回了指定对象的句柄。它的两个参数分别是对象的组和 ID。这是在其他对象中引用对象最好的办法。

活了!

我们的摄影机现在可以工作了。你的代码应当看起来像这样工作。但是我们的摄像机移动的时候,有些东西似乎不太对劲。看看这个(youtubo 视频,翻墙吧!):

即使是我们的玩家对象一个极小的移动,摄影机都会跟着一起动。这让游戏看起来很愚蠢。你可能会由于游戏中的移动而引起眩晕。而我们解决这个问题的办法就是给摄影机增加死区。

摄影机的“死区”

死区是屏幕中间的一个区域,在这个区域内,摄影机不会跟随玩家。当玩家在死区内移动时,摄影机不会移动。当玩家越过了死区的边缘后,摄影机会跟随玩家移动。这意味着在屏幕中央微小的移动(一些重要区域的局部操作)不会让屏幕移动。这感觉起来好一些。下面的视频展示了《Super Mario Bros. 3.》中的死区(youtubo 视频,翻墙吧!)。

《Super Mario Bros. 3》中的死区是非常窄的一个竖条,几乎不被察觉,但它确实存在于此。死区通常会非常微小——直到阅读了 Steve Swink 超级棒的书 Game Feel 以后,我们才留意到它。

死区的实现

我们将创建一个新的摄影机函数,跟 centerCamera 函数一样,我们向其传递玩家对象和整个地图的尺寸信息。将这部分代码放在 标签前。

<br />
	function followCamera(obj,viewdata) {<br />
	}<br />


这个图片阐明了摄影机、死区、地图和玩家的位置关系。

死区的数学原理是很简单的,先让你有个简单的了解。首先是定义 xbuf 和 ybuf,用于指定死区的缓冲区域的大小,就像上图所示那样。我们也需要知道摄影机当前的 x 和 y 的位置。我们在函数内定义这些:

<br />
	xbuf = 96;<br />
	ybuf = 96;<br />
	xcam = gbox.getCamera().x;<br />
	ycam = gbox.getCamera().y;<br />

我们设置死区的宽高都是 96 像素,并且调用 gbox.getCamera 函数获取摄影机对象用于计算。

由于在玩家出了死区后需要移动摄影机,我们需要编写代码来检测这种情况。


这展示了当对象离开死区时重新计算摄影机位置的情况。

让我们从玩家向右移动开始。就像你看到的上面的图那样,玩家相对于死区向右移动时,(obj.x – xcam)表示屏幕左边到玩家对象的距离,这是大于屏幕左边到死区最右边的距离(gbox._screenw – xbuf)的。如果前一个距离大于后一个,就需要向右移动摄影机,距离是它们之间的差值,这样玩家精灵总是跟死区的最右边保持一定距离。代码看起来是这样的,向死区的右边移动,然后给 xcam 变量增加一个距离,并将摄影机定位到这个新数值上:

<br />
	if ((obj.x - xcam) &gt; (gbox._screenw - xbuf)) gbox.setCameraX( xcam + (obj.x - xcam)-(gbox._screenw - xbuf),viewdata);<br />

同样的,我们希望知道什么时候玩家对象到屏幕左边的距离(obj.x-xcam)小于死区左边(xbuf)到屏幕左边的距离。就像上面一样,我们计算这个差值,不过这次是从 xcam 减出来的负数,并向左移动摄影机。

<br />
	if ((obj.x - xcam) &lt; (xbuf)) gbox.setCameraX(xcam + (obj.x - xcam) - (xbuf),viewdata);<br />

幸运的是,我们可以替换“x”为“y”并且替换“w”为“h”就得到了 y 轴的相同算法,不同变量的代码。

<br />
	if ((obj.y - ycam) &gt; (gbox._screenh - ybuf)) gbox.setCameraY( ycam + (obj.y - ycam)-(gbox._screenh - ybuf),viewdata);<br />
	if ((obj.y - ycam) &lt; (ybuf)) gbox.setCameraY(ycam + (obj.y - ycam) - (ybuf),viewdata);<br />

好了,就是这样。完整的 followCamera() 代码看起来是这样的:

<br />
	function followCamera(obj,viewdata) {<br />
	  xbuf = 96;<br />
	  ybuf = 96;<br />
	  xcam = gbox.getCamera().x;<br />
	  ycam = gbox.getCamera().y;</p>
<p>	  if ((obj.x - xcam) &gt; (gbox._screenw - xbuf)) gbox.setCameraX(xcam + (obj.x - xcam) - (gbox._screenw - xbuf), viewdata);<br />
	  if ((obj.x - xcam) &lt; (xbuf))                 gbox.setCameraX(xcam + (obj.x - xcam) - xbuf,                   viewdata);<br />
	  if ((obj.y - ycam) &gt; (gbox._screenh - ybuf)) gbox.setCameraY(ycam + (obj.y - ycam) - (gbox._screenh - ybuf), viewdata);<br />
	  if ((obj.y - ycam) &lt; (ybuf))                 gbox.setCameraY(ycam + (obj.y - ycam) - ybuf,                   viewdata);<br />
	}<br />

添加新的摄影机

最后,我们只要用新的摄影机函数替换 gbox.centerCamera 函数。回到 addMap 函数中,用 followCamera 替换 gbox.centerCamera,就像这样:

<br />
	// 将玩家对象至于摄影机中央。map.w 和 map.h 数据告诉摄影机什么时候碰撞到地图边缘,以便停止卷动。<br />
	followCamera(gbox.getObject('player', 'player_id'), { w: map.w, h: map.h });<br />

Lights, something, action!

代码最终完成后应当像这样。这里有个视频演示了期望的行为,在屏幕的中央有一个死区,这样微小的移动不会造成摄影机移动(youtubo 视频,翻墙吧!)。

Akihabara 指南目录

Leave a Reply

Your email address will not be published. Required fields are marked *