[翻译]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 函数,让它的宽度和高度翻倍。

	function loadMap() {
	  return help.asciiArtToMap([
	"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
	"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xxxxxxxxxxxxxxxx            xx        xxxx                                    xx",
	"xxxxxxxxxxxxxxxx            xx        xxxxxxxxxxxxxxxxxxx                     xx",
	"xx                          xx        xxxxxxxxxxxxxxxxxxx                     xx",
	"xx                          xx                                                xx",
	"xx                          xx                                                xx",
	"xx                          xx                                                xx",
	"xx                          xx                                                xx",
	"xx                          xx                                                xx",
	"xx          xxxxxxxx   xxxxxxxxxxxxxxxxxxx                     xxxxxxxxxxxxxxxxx",
	"xx          xxxxxxxx   xxxxxxxxxxxxxxxxxxx                     xxxxxxxxxxxxxxxxx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xxxxxxxx                              xxxx                                    xx",
	"xxxxxxxx                              xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx            xxxxxxxxxxxxxxxxxx      xxxx                                    xx",
	"xx            xxxxxxxxxxxxxxxxxx      xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                xx                  xx",
	"xxxxxxxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxx                xx                  xx",
	"xxxxxxxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxx                xx                  xx",
	"xxxxxxxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxx                xx                  xx",
	"xxxxxxxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxx                xx                  xx",
	"xx                                    xxxx            xxxxxxxx                xx",
	"xx                                    xxxx            xxxxxxxx                xx",
	"xx                                    xxxx                xx                  xx",
	"xx                                    xxxx                xx                  xx",
	"xxxxxxxxxxxxxxxx            xx        xxxx                xx                  xx",
	"xxxxxxxxxxxxxxxx            xx        xxxx                xx                  xx",
	"xx                          xx        xxxx                xx                  xx",
	"xx                          xx        xxxx                                    xx",
	"xx                          xx        xxxx                                    xx",
	"xx                          xx        xxxx                                    xx",
	"xx                          xx        xxxx                                    xx",
	"xx                          xx        xxxx                                    xx",
	"xx          xxxxxxxx   xxxxxxxxxxxxxxxxxxx                                    xx",
	"xx          xxxxxxxx   xxxxxxxxxxxxxxxxxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xxxxxxxx                                                                      xx",
	"xxxxxxxx                                                                      xx",
	"xx                                                                            xx",
	"xx            xxxxxxxxxxxxxxxxxx      xxxx                                    xx",
	"xx            xxxxxxxxxxxxxxxxxx      xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xx                                    xxxx                                    xx",
	"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
	"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
	    ], [ [null, ' '], [0, 'x'] ])
	}

初始化摄影机

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

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

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 函数一样,我们向其传递玩家对象和整个地图的尺寸信息。将这部分代码放在 标签前。

	function followCamera(obj,viewdata) {
	}


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

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

	xbuf = 96;
	ybuf = 96;
	xcam = gbox.getCamera().x;
	ycam = gbox.getCamera().y;

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

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


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

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

	if ((obj.x - xcam) > (gbox._screenw - xbuf)) gbox.setCameraX( xcam + (obj.x - xcam)-(gbox._screenw - xbuf),viewdata);

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

	if ((obj.x - xcam) < (xbuf)) gbox.setCameraX(xcam + (obj.x - xcam) - (xbuf),viewdata);

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

	if ((obj.y - ycam) > (gbox._screenh - ybuf)) gbox.setCameraY( ycam + (obj.y - ycam)-(gbox._screenh - ybuf),viewdata);
	if ((obj.y - ycam) < (ybuf)) gbox.setCameraY(ycam + (obj.y - ycam) - (ybuf),viewdata);

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

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

添加新的摄影机

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

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

Lights, something, action!

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

Akihabara 指南目录

Leave a Reply

Your email address will not be published.