游戏引擎作为游戏制作的基础是保证游戏质量的最重要部分,目前主要包括两方面:背景绘制和动画播放。
为了使新员工对目前开发的手机游戏能够有深入的了解,此文档详细描述了目前使用的背景绘制的原理和具体实现方法。
GD Game Design
BG Background
[1] Cao Heng, Gameloft, “Training Minutes For Game Tools”, V0.002.
游戏场景中的背景要展现出一副巨大的图像,它是游戏设计者表现游戏环境和氛围的重要手段。无论对于PC游戏还是其他平台的游戏,巨大的背景都会带来容量问题,而容量对于手机游戏更是十分宝贵的。
为了使用尽量少的图绘制出尽量多的背景,背景图像由无数的等大小的图形组成,这些图形通常使用正方形。而这些图形的种类是有限的,只是每一个都可能被使用了很多次。就好像墙上的瓷砖一样,可以通过有限的种类拼出无限的组合。那么组成背景的图形就被称为Tile,而使用Tile 拼成的背景就被称为 Tiled Background。
最后,我们把拼接一副背景所用的所有 Tile 保存在一张图像中,就构成了我们在游戏中使用的图像资源,我们通常称之为 Tileset。并且,我们会把 Tileset 中的每一个 Tile 进行编号,顺序是从左到右,从上到下,如下所示。
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
通过编号,我们可以把组成巨大背景的每一个 Tile 用该 Tile 在 Tileset中的索引值(Index)表示,如下所示。
4 |
5 |
7 |
18 |
8 |
4 |
5 |
7 |
18 |
8 |
4 |
5 |
7 |
18 |
8 |
9 |
0 |
1 |
11 |
13 |
9 |
0 |
1 |
11 |
13 |
9 |
0 |
1 |
11 |
13 |
10 |
6 |
17 |
2 |
12 |
10 |
6 |
17 |
2 |
12 |
10 |
6 |
17 |
2 |
12 |
3 |
16 |
15 |
19 |
14 |
3 |
16 |
15 |
19 |
14 |
3 |
16 |
15 |
19 |
14 |
4 |
5 |
7 |
18 |
8 |
4 |
5 |
7 |
18 |
8 |
4 |
5 |
7 |
18 |
8 |
9 |
0 |
1 |
11 |
13 |
9 |
0 |
1 |
11 |
13 |
9 |
0 |
1 |
11 |
13 |
10 |
6 |
17 |
2 |
12 |
10 |
6 |
17 |
2 |
12 |
10 |
6 |
17 |
2 |
12 |
3 |
16 |
15 |
19 |
14 |
3 |
16 |
15 |
19 |
14 |
3 |
16 |
15 |
19 |
14 |
4 |
5 |
7 |
18 |
8 |
4 |
5 |
7 |
18 |
8 |
4 |
5 |
7 |
18 |
8 |
9 |
0 |
1 |
11 |
13 |
9 |
0 |
1 |
11 |
13 |
9 |
0 |
1 |
11 |
13 |
我们将这些索引值以从左到右,从上到下的顺序保存起来,就得到了一个连续的索引值的数组。这样,我们只要拥有 Tileset 的图像和索引数组就可以得到一副完整的背景了。
通过上一节的介绍,我们可以知道,巨大的背景可通过一张很小的 Tileset 图和索引数组来绘制出来。
在手机功能允许的范围内,程序还可以对 Tile 进行水平翻转(X Flip)和垂直翻转(Y Flip),这样就可以在背景中使用经过翻转的 Tile ,用于丰富背景图的内容。
为了通用性和可读性,在编辑阶段我们将 Background 的索引数组和相关数据保存为 XML 格式,这样不仅可以在 IE 等软件中浏览,更可以在 Notepad, UltraEditor, HexEditor 等工具中进行编辑。
下面是一个 XML 文件的例子,该格式为公司内部定义。
<Map width="160" height="176">
16,16,16,16,-1,16,16,16,16,16,6,16,16,16,16,16,16,16,16,16,6,16,16,16,16,16,
16,16,16,16,6,16,16,16,17,1073741841,16,16,16,16,6,16,2,2,0,1073741824,2,2,2,2,6,16,7,7,5,1073741829,7,7,7,7,6,16,19,11,8,1,11,11,11,19,6,10,19,19,12,1073741836,15,15,19,19,13,14,536870930,536870930,536870930,536870930,536870930,536870930,536870930,536870930,9,18,18,18,18,18,18,18,18,18,4,4,1073741828,1073741828,4,4,1073741828,1073741828,4,4
0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,3,3, 0,0,1,1,0,0,0,0,3,3,0,0,1,1,0,0,0,0,3,3,4,0,1,1,0,0,0,4,3,3,4,4,1,1,0,0,4,4,3,3,2,2,2,2,2,2,2,2,3,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3
在例子中,我们可以看到紫色的为背景图的大小(单位为象素),绿色依次为 Tileset 图像文件的路径,Tile 的宽度,Tile 的高度,背景上每行的 Tile 数量,背景上每列的 Tile 数量,棕色即为索引数组。在索引数组中,每一个 Tile 的索引用一个4 Bytes的整数表示,如果索引值小于0(即二进制中的最高位为1),表示该 Tile 为空(即没有 Tile),为了便于记录,我们将二进制中的次高位和次次高位是否为1,分别用于表示每个 Tile 是否需要进行 X Flip 或 Y Flip。如例子中的-1就表示该位置没有 Tile,1073741828 (0x40000004)表示该 Tile 进行了 X Flip。
在上面的XML文件中,有两层背景,第一层用于游戏中绘制背景,可称为显示层(Visual Layer),第二层不用于绘制背景,只以同样的形式记录每个 Tile 的物理属性,可称为物理层(Physical Layer)。
为了给背景上不同的区域划分物理属性,比如有些区域人物可以通过,有些区域不能通过。我们使用另外一套 Tileset 拼出与显示层同样大小的物理层,我们的目的只是得到物理层的索引数组,用这套索引数组表示每一个 Tile 对应位置的物理属性,而不是将物理层画到屏幕中。
目前我们所使用的背景编辑工具为 ezPlayfieldEditor.exe ,它是Gameloft-Beijing内部使用的开发工具,由Java语言编写,使用 J2SDK1.4.2编译完成,因此在J2SDK 1.4.2的环境下才可以正常运行。
它可以创建、读取、保存XML格式的背景数据,由于项目的不断创新,该工具还在不断的修改和完善中。
XML文件并非游戏使用的最终资源,我们必须将XML文件转换为二进制文件,以便于程序使用和减少容量。所使用的工具为 Convert.exe ,与 ezPlayfieldEditor 一样,在公司内部开发和使用。
二进制文件的数据结构与XML文件基本相同,同样包括每张背景的宽、高和Tile 的宽、高等信息。而对使用的 Tileset 图像文件进行了统一的编号,在二进制文件中只记录背景所用 Tileset 的编号。
由于各个项目都会对所用的二进制资源进行容量上的优化,所以具体格式不尽相同,这里就不举例介绍了。大家可以集思广益,不断创新,以达到容量最小,使用最便利的目的。
显示算法就是通过二进制文件和Tileset图在屏幕上的绘制背景的算法。对于手机游戏来说,显示算法的速度是最重要得。
当屏幕(红色框)要绘制背景上某一区域时,我们将涉及到的Tile(黄色区域)逐块绘制到对应的位置,就是逐块绘制法。逐块绘制法是显示背景的最基本的方法。
0,0 X
Y |
当我们已知屏幕原点(左上角)在背景中的位置时,只要知道 Tile 的宽高,背景的宽高,就可知道每个涉及到的 Tile 相对屏幕的位置,用两层循环就可以绘制出来。
但是这种方法有一个致命的缺点,就是每帧都需要将所有涉及的 Tile 重新绘制到屏幕上,极大的影响了游戏的流畅度。为了提高速度,于是有了下面的缓冲绘制法。
当屏幕在背景中移动时,实际上所涉及的 Tile 根本没有变化,或者只有一小部分发生了改变。所以我们可以创建一个背景图像缓冲(buffer),保存当前屏幕的背景图像,减少每帧得画图次数,可以大大提高速度。
当我们创建的图像缓冲和黄色区域大小相同时,如果背景涉及的Tile没有变化,我们只需将缓冲图像画到屏幕的适当位置上。如果背景涉及的 Tile 发生了变化,如变为绿色区域,我们只需更新变化的部分Tile到背景缓冲,再将缓冲画到屏幕上即可。
0,0 X
Y |
为了保留缓冲图像中已有部分,我们采用滚动更新的方法。假设,当屏幕向右下移动时,缓冲中最上和最左的Tile已无用(即I、II、III区),我们便将新的最下的Tile 保存在最上(I、II区),将新的最右的Tile保存在最左(I、III区),得到新的缓冲图像。最后,我们将新的背景缓冲分4区,按照实际位置画到屏幕上即可,对应关系如图。
当然,按照屏幕在背景中的位置,我们的缓冲分区会出现四种情况。
l 分1个区,刚好背景没有被缓冲切开
l 分2个区,背景只在X轴方向被缓冲切开
l 分2个区,背景只在Y轴方向被缓冲切开
l 分4个区,背景在X、Y轴方向都被缓冲切开(如例子)
I II
III IV |
IV III
II I |
背景缓冲 |
实际背景 |
背景和动画的图像是相同的,可以有透明效果。为了得到更加丰富的游戏背景,我们可以将两层甚至多层背景重叠,使背景的组合更多,更加复杂。
多层背景与单层背景的原理完全相同,也使用一个背景图像缓冲,区别在于更新缓冲时,需要更新多次缓冲,每一个Tile的位置会被多个Tile按顺序更新。
l 背景缓冲的大小
背景缓冲图像的大小要大于屏幕最多可涉及的Tile的区域。
例如:
屏幕大小是128 X 128,Tile是 8 X 8,缓冲图像至少是
136 X 136 = (128 + 8) X (128 + 8)
屏幕大小是120 X 130,Tile是 8 X 8,缓冲图像至少是
128 X 144 = (120 + 8) X ((130+7)/8*8 + 8)
屏幕大小是 screenW X screenH,Tile是 tileW X tileH,缓冲图像至少是
(screenW+(tileW-1))/tileW*tileW+tileW X (screenH+(tileH-1))/tileH*tileH+tileH
l 背景缓冲的内存消耗
背景缓冲虽然可以提高游戏的速度,但是它所占用的内存也是十分巨大的。
在一般手机中,一张图片的大小为 长X宽X 2。
如背景缓冲的大小为 136 X 136,那么它所占用的内存为 36992 Bytes。
因为,我们可以认为使用背景缓冲是用内存换取速度的一种手段。
在游戏开发过程中,经常会利用内存换取速度,或者速度换取内存。
l 背景缓冲的滚动
当游戏的背景只用到了X轴滚动或者Y轴滚动时,我们的代码就可以进行优化,去掉不需要的滚动代码,可以减少一些容量的负担。
l 多层背景的限制
由于多层背景在更新缓冲时比单层背景要更多的绘图操作,因此更新缓冲的速度相对较慢。对于性能较差和背景滚动频繁的项目不宜使用这种方法。
另外,多层背景必然使用多个Tileset,而Tileset也会消耗大量内存,因此对于内存紧张的项目也不宜使用该方法。