原文:http://bostongamejams.com/akihabara-tutorials/akihabara-tutorial-part-2-moving-a-sprite/
Akihabara 指南,第二部分:精灵的移动
Darius Kazemi 编写于 2010 年五月 21 日
这是系列指南的第二部分,我们将向你演示如何使用基于 HTML5 和 JavaScript 的 Akihabara 框架来编写 8 向射击游戏。Akihabara 是一个利用 HTML5 功能帮助创建游戏的 Javascript 库。使用 HTML5 编写游戏最棒的事情是你可以在任何平台、任何支持 HTML5 的浏览器运行它。这包括 Chrome,Firefox,Safari 和 iPhone/iPad,WebOS 设备上的 WebKit 浏览器,或者其他移动平台。
在这个部分中,我们将向你演示如何渲染精灵到屏幕上,并且移动它。这个指南包含精灵的渲染、输入、基本的游戏对象创建和控制。我们将在第一部分创建的基础上进行开发,所以将假设你已经完成了第一部分的指南。
成品
在这里查看本课结束后的成果。按“Z”键跳过标题屏幕,然后使用方向键控制圈点移动。
整体思路
现在,我们将开始编写实际的游戏代码,我们需要回头看看,考虑一下视频游戏的屏幕是怎么样的。
游戏是由相互作用的对象组成:在像《太空侵略者》这样的游戏中,对象包括玩家的飞船、敌人、护盾和子弹。然后,我们设定对象行为的规则。一个敌人的对象可能包含代码,说“向前和向后移动我,当碰到屏幕边缘时消失。在一个随机的时间间隔中,向前进方向上射击。”
对象是游戏的核心组件,但是只有一大堆对象并不是游戏。我们需要将这些对象放在世界中,并提供一个整体的概念:“在玩家的四个方向上放置 10 个敌人。”;“玩家对象死亡三次后游戏结束。”
Akihabara 是一个基于对象的游戏引擎。在指南的这一部分,我们将尝试创建第一个玩家对象,定义它的行为,然后将它放入游戏世界中。在这个部分的指南中,不会有其他相互作用的对象或者游戏的整体概念出现——我们刻意略过这些在以后的指南中会出现的内容。
玩家对象
我们的玩家对象将会包含一些定义。我们会通过指定一个精灵来定义对象的外观(一个 2D 的图形,等一下详细说),并且设置它的控制方式,以及如何移动。
精灵
精灵是图片的梦幻般的称呼,或者说用代码来控制的一组图像。它是关联到游戏对象的图形,并且是这个对象的图形化描述。
我们的精灵将使用的图像是这个蓝色的圆形:playerSprite.png。这是一个 PNG 文件:Akihabara 可以处理透明的 PNG 文件,所以它可以正确渲染含有透明部分的 PNG,所以你可以穿过去看到背景。虽然图像本身是 16 x16 像素的方块,在我们的游戏中它将是一个圆形。下载并保存到 index.html 的同一目录下,跟第一部分中的其他 PNG 文件一样。
为了在游戏中使用这个精灵,我们需要像其他图像一样单独加载它。打开第一部分的代码,添加下面的代码到 loadResources 紧接着加载 logo.png 文件的那行下面。
gbox.addImage( 'logo' , 'logo.png' ); |
gbox.addImage( 'playerSprite' , 'playerSprite.png' ); |
首先,我们调用第一部分已经学过的 gbox.addImage 加载 PNG 文件。
然后调用 gbox.addTiles。许多 2D 游戏引擎支持精灵表(sprite sheet),它是一张大图,含有若干相同尺寸、按格网排列的小图。精灵图被剪切,并通过定义网格的尺寸和每行、每列的格子数量建立瓦片地图(tile map)。游戏引擎可以通过瓦片的编号访问到每个瓦片——2D 的动画可以定义为一格一个接一个的显示瓦片。
gbox.addTiles 函数需要知道图片和其拆分方式。你可以单独指定每个瓦片的高和宽,以及每行希望有多少个瓦片。我们的圆形是 16×16 像素,所以我们设置 tileh 和 tilew (高和宽)为 16。在这个例子中我们仅仅处理一个格,所以我们让它每行只用一个瓦片。gapx 和 gapy 变量定义每个格之间有多少偏移量(或者说需要忽略的“空白”),这里我们设为 0。
过一会我们定义玩家对象时,会使用我们定义的 playerTiles ID 来访问。
玩家对象的结构
定义一个对象的代码是非常冗长的,所以我们首先看一下定义一个对象的大致结构,然后再讨论一些特殊的实现。下面是部分从我们的对象中摘抄出的部分代码,包括了 addPlayer 函数。先参照注释阅读一遍代码,应当是相当清晰的。
在 main 函数后面我们创建了addPlayer 函数用于封装,以保证我们的游戏主要代码清晰。这个函数在内部调用 gbox.addObject 进行了大量的设置。
最开始要做的是定义一些基本的识别变量。首先,为 玩家对象指定了 id,这样可以在其他函数中使用它。然后设置了组 ID,用于渲染的顺序。这让我们实现了“确保在背景组之上绘制玩家组的所有内容”,所以就不会发生玩家消失在背景之后的事情。然后我们设置了对象的 tileset 为‘playerTiles’,这是之前我们 tilemap 的图像的 ID。
剩下的部分分为三个函数:initialize, first 和 blit。
initialize 函数包含的所有代码在对象创建时运行一次以后再也不执行了。这通常包括如最大生命值、初始速度和对象的任何可以进行“设置”的内容。
first 函数在游戏图像绘制前的每一帧执行。游戏帧是一个用于计算和更新对象行为的小的时间片段。我们将对象的所有行为放在这个函数内,例如检查玩家的输入、检查一个对象是否同其他对象碰撞、根据当前的速度更新位置,更新人工智能等等。许多我们认为的“游戏编程”的内容都在这个函数内实现。
blit 函数包括对象的所有渲染代码。如果必须对在屏幕上显示图像做一些处理,就是在这里进行。跟 first 函数一样,在每帧都会调用,只不过是紧接着 first 函数。这是因为我们希望首先更新自己的位置,然后在屏幕上用已经更新过的位置绘制精灵。
下面的三个章节我们将讨论在这三个函数里放些什么内容。
initialize 函数
在这个例子中,我们的函数是空白的:
toys.topview.initialize( this , { |
上面的代码我们告诉游戏使用 toys.topview 类型初始化玩家对象,所有需要的初始化在 toys.topview 中完成。这对于下面的 first 和 blit 是很方便的。
first 函数
在 first 函数中,我们告诉对象在游戏的每一帧,它应当检查方向键是否被按下,并根据按键修改对象的加速度,告诉物理引擎这是对象的加速度,并强制对象更新对象的位置。
toys.topview.controlKeys( this , { left: 'left' , right: 'right' , up: 'up' , down: 'down' }); |
toys.topview.handleAccellerations( this ); |
toys.topview.applyForces( this ); |
toys.topview.controlKeys 函数是一个帮助函数,用于映射每个方向上的加速度到控制键。在这个例子中,我们希望映射到方向键,所以这个函数最终实现了这个功能:是的,左是左,右是右。但是有时,需要编程实现这些功能。
当我们调用 toys.topview.handleAccellerations (是的,这里的函数名拼写有错误),我们是在告诉游戏引擎如果没有在指定方向上的加速度,则减速到 0。如果我们不使用这个函数,我们的玩家对象会在冰面上一样。在这里我们必须实现摩擦力。
toys.topview.applyForces 函数提供了根据最后的位置,以及当前的速度和加减速度确定新的位置。这是实际的移动发生的位置。
blit 函数
在 blit 函数中要做的事情是清除屏幕,然后根据新的位置绘制玩家对象。
gbox.blitFade(gbox.getBufferContext(),{}); |
gbox.blitTile(gbox.getBufferContext(), { |
gbox.blitFade 函数清除屏幕——调用 gbox.getBufferContext 获得当前的屏幕缓冲并传递进去,所以它知道要清理什么内容。
向 gbox.blitTile 函数传递了渲染这个瓦片的对象的信息。就像第一部分解释过的那样,“this”意味着我们正在写代码的那个对象,所以我们最终传递了当前的 tileset,当前的 x/y 坐标,和关于这个瓦片是否被垂直和水平翻转的信息(默认不进行),摄像机的信息,和 alpha 透明值 1(意味着完全不透明)。虽然我们传递了几乎 this.* 的所有数据,但只是传递了默认的值,对于现在来说足够了。
main 函数的一些组成
现在,我们需要对 main 函数设置一些内容,然后就大功告成了!
首先要做的是设置玩家对象的渲染层。在定义玩家对象的时候,我们指定这个对象到‘player’组。这里是 main 函数的首行调用 gbox.setGroups 添加 ‘player’ 组到内部的组存储的数组。
gbox.setGroups([ 'player' , 'game' ]); |
gbox.setGroups 函数告诉游戏用什么顺序渲染物体。物体按照 setGroups 列出的从左到右的顺序进行渲染。通过在最开始添加‘player’组,玩家对象将会首先被渲染,但是在‘game’组的内容将会被渲染在其之上。这样做的好处是‘game’组包括类似“Game Over”的消息将会显示在玩家的精灵上。
现在,我们需要剔除默认的难度选择,以及 ”Let’s begin!” 消息。为了实现这个,我们只要重写这些函数,使其什么也不做。在定义 maingame 之后的代码中,编写这些重写:
maingame = gamecycle.createMaingame( 'game' , 'game' ); |
maingame.gameMenu = function () { return true ; }; |
maingame.gameIntroAnimation = function () { return true ; }; |
在 main 函数的结束,我们调用 gbox.go 去执行游戏,我们需要通过向游戏世界放置玩家对象初始化游戏。
maingame.initializeGame = function () { |
maingame.initializeGame 函数中的代码设置游戏。跟之前的代码类似的方法,这里像《太空入侵者》那样设置玩家和所有的敌人。在这个代码中所要做的所有事情就是调用 addPlayer 函数初始化玩家对象。因为我们没有对玩家对象定义任何的坐标,默认的坐标是 (0,0),相对于屏幕的顶部和左边。
嘿,你已经可以移动了!
现在你可以保存你的 HTML 文件,并在兼容的浏览器中打开;你应该看到类似这样的内容。你会看到来自于第一部分的标题屏幕。按 Z 键跳过标题屏幕,蓝色的圆形会显示在屏幕上。并可以通过键盘上的方向键进行控制。
Akihabara 指南目录
Leave a Reply