Super Jumper:一个2DOpenGL ES游戏

在移动领域有一些非常流行的游戏类型可供选择,本内容所选择实现的游戏将更加休闲,这个游戏是一个类似AbductionDoodle Jump的跳跃类游戏。同Mr. Nom一样,仍从定义游戏机制开始。

1. 核心游戏机制

建议你在自己的Android设备上安装Abduction或在网上看一下它的视频。通过这个例子来提炼我们所要实现的游戏的核心游戏机制,这个游戏称为Super Jumper。下面是一些细节:

  • 游戏的主角不断向上跳跃,从一个平台跳到另一平台。游戏世界在垂直方向跨了多个画面。
  • 通过左右倾斜屏幕来控制水平移动。
  • 当游戏主角离开水平屏幕边界时,它将从屏幕相反的一边重新进入。
  • 平台可以静止或者水平移动。
  • 有些平台在主角撞上时会随机地变得粉碎。
  • 在向上的路上,主角可以收集物品来获得分数。
  • 除了钱币,在一些平台上会有弹簧,它们可以使主角跳得更高。
  • 邪恶的力量充满了游戏世界,它们水平移动。当碰上它们时,主角将死亡并且游戏结束。
  • 当主角掉落到屏幕的底部边缘时,游戏也将结束。
  • 在关卡的顶部会有一些目标,当主角碰上目标,新的关卡将开始。

虽然这份清单比Mr. Nom要长,但这并不意味着它将复杂很多。图1显示的是核心原理的初始模型。本章将直接使用Paint.NET创建实物模型。下面介绍背景故事。

Super Jumper:一个2DOpenGL ES游戏_第1张图片

初始的游戏机制实物模型,显示了主角、平台、钱币、邪恶势力和关卡顶部的目标

2. 背景故事和艺术风格

先来讲述游戏的创意和它独特的故事。

游戏的主角Bob得了慢性跳跃症。他每次接触到地面都必须进行跳跃。更糟的是,他深爱的公主(不给她取名字了)被会飞行的松鼠杀手组成的邪恶军队绑架,关在天上的一个城堡中。在这种情况下,Bob的病症反而对拯救公主有所帮助。他开始寻觅心爱的人,与邪恶的松鼠军队战斗。

这个经典的视频游戏故事非常适合于八位图像类型的游戏,如早期在NES上的Super Mario Brothers。图1中的实物模型显示了含有所有游戏元素的最终图像。当然Bob、钱币、松鼠和粉碎的平台都是动画的,同时在游戏中也根据视觉类型使用音乐和声音效果。

3. 画面和切换

现在可以定义画面和画面间的切换了。这里仍将沿用在Mr. Nom中使用过的原则:

  • 有一个带标识的主画面;有PLAY、HIGHSCORES和HELP菜单项;并有一个按键来控制声音的开启和关闭。
  • 有一个游戏画面来询问玩家是否准备好控制运行、暂停、结束游戏和下一关卡的状态。比起Mr. Nom,这里只是新增了画面的下一关卡状态,它将在Bob碰到城堡时触发。这时新的关卡生成,并且Bob将重新从世界的底部开始并保持他的分数。
  • 有一个高分画面用来显示玩家最高的5个分数。
  • 有一个帮助画面来说明游戏机制和玩家的目标,此处省略了玩家如何进行控制的描述。20世纪80年代和90年代早期的孩子们在游戏没有说明如何进行游戏的时候就能够处理这么复杂的控制。

这与Mr. Nom差不多相同。图2显示了所有画面和变换。注意,游戏画面除了暂停按钮并没有任何按钮或子画面,当游戏询问是否准备好时,玩家只要触摸画面即可。

 Super Jumper:一个2DOpenGL ES游戏_第2张图片

2  Super Jumper的所有画面和切换

解决了这些问题,现在需要考虑游戏世界的大小和度量单位,以及映射图像资源的方式。

4. 定义游戏世界

经典的鸡和蛋的问题再次出现了。从第8章中可以知道世界单位(如米)和像素是存在对应关系的。我们在世界空间中按物理规律定义了对象,其边界形状和位置的度量单位是米,速度的度量单位是米/秒。但对象的图像表示使用像素定义,因此不得不做一些映射。解决这个问题的方法是首先针对图像资源定义目标分辨率。与Mr. Nom一样,此处使用的目标分辨率是320×480像素(纵横比是1.5)。之所以使用这个目标分辨率,是因为这是最低的可行的分辨率,但是如果游戏专门针对平板电脑,则可以使用800×1 280像素这样的分辨率,或者这两种分辨率之间的一种分辨率,例如480×800像素(典型的Android手机)。不管目标分辨率如何,其原理是相同的。

接下来在像素和世界空间中的米之间建立对应关系。图1中的实物模型显示了不同对象使用的画面空间的大小以及它们间的相对比例。对于2D游戏,此处推荐将32像素映射为1米。那么现在在实物模型上覆盖一个网格,网格的尺寸是320×380像素,每个单元格大小是32×32像素。在世界空间中,单元格映射为1×1米的大小。图3显示的是实物模型和网格。

3有点投机取巧,图中各图像的放置与单元格对齐。在实际的游戏中,对象将被放置在不对齐的位置上。

从图3中可以得到什么信息呢?首先,可以直接估计出游戏世界中每个对象的宽度和高度,其单位是米。下面是游戏中对象的边界矩形的取值:

Super Jumper:一个2DOpenGL ES游戏_第3张图片

覆盖网格的实物模型。每个单元格是32×32像素,对应游戏世界的1×1

  • Bob是0.8×0.8米;它并不完全占用一个单元格。
  • 平台是2×0.5米,水平占据两个单元格,垂直占据半个单元格。
  • 钱币是0.8×0.5米。它几乎垂直占据一个单元格,水平占据半个单元格。
  • 弹簧是0.5×0.5米,在两个方向上各占据半个单元格。实际上弹簧垂直方向比水平方向要稍长一点。它的边界形状是正方形,因此碰撞测试不是十分严格。
  • 松鼠是1×0.8米。
  • 城堡是0.8×0.8米。

由这些尺寸可以得到用于碰撞检测的对象的边界矩形的尺寸。它们的大小可以根据游戏对这些值的使用方式进行调整。

从图3还可以得到视锥体的尺寸。它显示了10×15米的游戏世界。

游戏中没有定义的只剩下对象的速度和加速度,它们高度依赖于开发人员想要的游戏感觉,通常需要做一些实验来取得正确的值。下面是我们经过一些实验后得到:

  • 重力加速度向量是(0, -13)米/秒2,它比现实中和炮示例中使用的重力加速度要稍大。
  • Bob的初始跳跃速度向量是(0,11)米/秒。注意,跳跃速度只影响在y轴上的移动。水平移动则由加速度的当前读数定义。
  • 当碰到弹簧时,Bob的跳跃速度是他正常跳跃速度的1.5倍,即(0,16.5)米/秒。同样,这个值也是纯粹通过实验得到。
  • Bob的水平移动速度是20米/秒。注意,这是一个无方向的速率,不是向量。稍后将讲解它是如何与加速计共同工作的。
  • 松鼠不断地左右来回巡逻,它们的移动速度恒定为3米/秒。表示成向量则是,当松鼠向左移动时是(-3,0)米/秒,当它向右移动时是(3,0)米/秒。

Bob将如何在水平方向移动呢?先前定义的运动速率是Bob最大的水平速率。根据玩家手机的倾斜程度,Bob的水平移动速率将在0(不倾斜)20/(完全倾斜到一边)之间。

由于游戏是运行在纵向模式的,因此将使用加速计在x轴方向的值。当手机没有发生倾斜时,x轴的加速度读数是0/2。当完全向左倾斜至手机处于横向时,x轴的加速计读数是-10/2。当完全向右倾斜时,x轴的加速计读数为10/2。把加速计读数除以最大绝对速度(10)使其规范化,然后乘以Bob最大的水平速率,就得到了Bob的速度。这样当手机完全倾斜到一边时,Bob将以20/秒从左向右运动,并且速度随手机倾斜变小而变小。当手机完全倾斜时,Bob的速度能达到两倍于平时的速度。

根据加速计在x轴上的当前读数在每帧中更新水平运动速度,并将其与Bob的垂直速度进行组合。就像在早前的炮弹示例一样,垂直速度来自于重力加速度和Bob当前的垂直速度。

游戏世界的一个重要的方面是只能看到它的一部分。由于当Bob出了屏幕底部边缘时将死亡,这样照相机也将在游戏机制中扮演角色。虽然我们使用照相机进行渲染并且当Bob跳起时也向上移动它,但在世界的模拟类中并不使用它,而记录到目前为止Bob的最高y坐标。如果他低于这个值与视锥体一半高度的差值,那么就认为它离开了屏幕。由于需要根据视锥的高度来决定Bob的生死,因此并不能够使模型(世界模拟类)和视图之间完全独立,对此是可以容忍的。

下面看看游戏所需要的资源。

5. 创建资源

这个新游戏有两类图像资源:UI元素和实际的游戏(或世界)元素。下面从UI元素开始。

UI元素

首先注意到的是UI元素(按钮、logo)与像素到世界单位的映射无关。如同在Mr. Nom中一样,将它们设计成适合的目标分辨率——本例中是320×480像素。通过图2可以确定有哪些UI元素。

首先创建的UI元素是不同画面所需要的按钮。图4显示的是游戏的所有按钮。

建议在网格中创建所有的图形资源,网格单元格的大小是32×32像素或64×64像素。图4中按钮所在网格的单元格大小是64×64像素。顶部那行的按钮用于主菜单画面,指示是否开启声音。左下的箭头按钮应用于需要导航到下一画面的画面,右下的按钮用于运行中的游戏画面,它允许用户暂停游戏。

你可能想知道为什么没有指向右方的箭头。这是因为通过使用负的宽和/或高,精灵批处理器可以很容易将所绘制的对象翻转。在一些图像资源中使用这一技巧可以节省一些内存。

接下来的是主菜单画面所需要的元素,它们是一个logo、菜单项和背景。图5显示了这些元素。

 Super Jumper:一个2DOpenGL ES游戏_第4张图片

不同的按键,大小均为64×64像素图5背景图片、主菜单项和logo

背景图片不仅使用在主菜单画面,也使用在所有的画面上。它与目标分辨率大小一样,都是320×480像素。主菜单项记录是300×110像素。在图5显示的背景是黑色的,这是因为白底白字是看不清的。在实际的图片中,背景使用的是透明像素。logo274×142像素,在其4个角上使用了透明像素。

再接下来是帮助画面的图片。此处使用了一个320×480像素的全屏图片,而不是使用一组元素组合。这样做将减少绘制代码的数量,因此不会增加程序的大小。图2中显示了所有的帮助画面,唯一组合这些图片的是箭头按钮。

对于高分画面,将重用主菜单项中显示“HIGHSCORES”的图片部分。实际的分数将使用本章稍后将介绍的特殊技术进行渲染。画面剩下的部分再次使用背景图片和按钮组成。

游戏画面还有一些文本UI元素,即READY?标签、用于暂停状态的菜单项(RESUMEQUIT)GAME OVER标签。图6显示了它的全貌。

 

6  READY?RESUMEQUITGAME OVER标签

使用点阵字体处理文本

如何渲染游戏画面中其他的文本元素呢?我们将使用与在Mr. Nom中相同的技术来渲染分数。现在不只是有数字,还有字符。这里使用被称为点阵字体的图片图集,其中每个子图片表示一个字符(0a)。图7显示的是游戏中使用的点阵字体。

 Super Jumper:一个2DOpenGL ES游戏_第5张图片

点阵字体

7中的黑色背景和网格并不包含在实际的图片中。点阵字体是在游戏画面中渲染文本的一种非常古老的技术,通常包括ASCII字符范围的图片。这样的一个字符图片被称为一个图像字符(glyph)。而ASCIIUnicode的前身之一。如图8所示,ASCII字符集有128个字符。

 Super Jumper:一个2DOpenGL ES游戏_第6张图片

8  ASCII字符及其十进制、十六进制和八进制表示

在这128个字符中,96个是可打印字符(从第32到第127),游戏的点阵字体中只包含可打印字符。点阵字体的第一行是第32个字符到第47个字符,第二行是第48个字符到第63个字符,以此类推。ASCII只适用于存储和显示标准拉丁字母。扩展ASCII码格式使用值128255来编码西文中的其他通用字符,例如ö或é。更多的字符集(如汉字或阿拉伯文)使用Unicode表示,而且无法使用ASCII码进行编码。对于本章的游戏,标准ASCII字符集已经足够了。

那么如何使用点阵字体渲染文本呢?这其实是非常容易的。首先,创建96个纹理区域,每个区域映射到点阵字体中的一个图像字符。可以使用如下方式把这些纹理区域保存在一个数组中:

[java] view plain copy print ?
  1. TextureRegion[]glyphs = newTextureRegion[96];  
TextureRegion[]glyphs = newTextureRegion[96];

Java字符串使用16Unicode编码。游戏中使用的点阵字体的ASCII字符的取值与ASCII码和Unicode码的值一样。采用如下方法在Java字符串中获取字符区域的一个字符:

[java] view plain copy print ?
  1. intindex = string.charAt(i) – 32;  
intindex = string.charAt(i) – 32;

它把一个直接索引方法放入纹理区域数组,只需要把字符串中当前字符减去空白字符(32)即可。如果索引值小于0或大小95,则得到一个不在点阵字体中的Unicode字符。通常忽略这样的字符。

为了能够在一行中渲染多个字符,需要知道字符间应该留多少空间。图9-7中的点阵字体被称为固定宽度字体,这意味着每个图像字符的宽度是一样的。这些点阵字体字符的大小是16×20像素。当向前逐个渲染字符串中的字符时,只需要增加20像素。绘制位置在字符间移动的像素数被称为步进(advance)。本点阵字体的步进是固定的,但通常也可根据绘制的字符而改变。更复杂的步进形式是使用当前将要绘制的字符和下一个字符来进行计算。这个技术被称为字距调整(kerning),如果想获得更多的信息,可以在网上搜索相关内容。本章为了简单起见,只使用固定宽度点阵字体。

如何生成ASCII点阵字体呢?网上有很多工具可以用来生成点阵字体。本书使用的是名为Codehead’s Bitmap Font生成器的免费工具。只要加载字体文件并指定字体的高度,该生成器就能从中生成ASCII字符集的图片。这里不讨论该工具的众多选项,但建议你自己去试用这个工具。

游戏中剩下的字符串都将使用这种技术绘制。稍后将会看到一个具体实现的点阵字体类。下面继续讨论游戏资源。

加上点阵字体,现在就有了所需要的所有图形UI元素。使用SpriteBatcher进行渲染,该批处理器使用照相机设置一个视锥体,这个视锥体能够直接映射到目标分辨率上。通过这种方式能够得到所有坐标的像素坐标。

6. 实现Super Jumper

实现Super Jumper是非常容易的。它可以重用前面章节中的完整框架,并且可以在更高层次上遵从Mr. Nom中的体系。这意味着需要每个画面都有一个类,并且每个类都将实现画面中的逻辑和表示。除此之外,还需要利用一个合适的清单文件进行标准的项目配置,将所有的资源都在assets/目录下,并为应用程序创建一个图标等。下面从Assets类开始。

7. 是否需要优化

现在来检测新游戏。游戏中唯一需要处理速度的地方是游戏画面。在GameScreen类中设置了FPSCounter实例,并在GameScreen.render()方法的最后调用FPSCounter.logFrame()方法。下面是在HeroDroidNexus One中的结果:

[java] view plain copy print ?
  1. Hero (1.5):  
  2.   
  3. 01-02 20:58:06.417:DEBUG/FPSCounter(8251): fps: 57  
  4.   
  5. 01-02 20:58:07.427:DEBUG/FPSCounter(8251): fps: 57  
  6.   
  7. 01-02 20:58:08.447:DEBUG/FPSCounter(8251): fps: 57  
  8.   
  9. 01-02 20:58:09.447:DEBUG/FPSCounter(8251): fps: 56  
  10.   
  11. Droid (2.1.1):  
  12.   
  13. 01-02 21:03:59.643:DEBUG/FPSCounter(1676): fps: 61  
  14.   
  15. 01-02 21:04:00.659:DEBUG/FPSCounter(1676): fps: 59  
  16.   
  17. 01-02 21:04:01.659:DEBUG/FPSCounter(1676): fps: 60  
  18.   
  19. 01-02 21:04:02.666:DEBUG/FPSCounter(1676): fps: 60  
  20.   
  21. Nexus One (2.2.1):  
  22.   
  23. 01-02 20:54:05.263:DEBUG/FPSCounter(1393): fps: 61  
  24.   
  25. 01-02 20:54:06.273:DEBUG/FPSCounter(1393): fps: 61  
  26.   
  27. 01-02 20:54:07.273:DEBUG/FPSCounter(1393): fps: 60  
  28.   
  29. 01-02 20:54:08.283:DEBUG/FPSCounter(1393): fps: 61  
Hero (1.5):

01-02 20:58:06.417:DEBUG/FPSCounter(8251): fps: 57

01-02 20:58:07.427:DEBUG/FPSCounter(8251): fps: 57

01-02 20:58:08.447:DEBUG/FPSCounter(8251): fps: 57

01-02 20:58:09.447:DEBUG/FPSCounter(8251): fps: 56

Droid (2.1.1):

01-02 21:03:59.643:DEBUG/FPSCounter(1676): fps: 61

01-02 21:04:00.659:DEBUG/FPSCounter(1676): fps: 59

01-02 21:04:01.659:DEBUG/FPSCounter(1676): fps: 60

01-02 21:04:02.666:DEBUG/FPSCounter(1676): fps: 60

Nexus One (2.2.1):

01-02 20:54:05.263:DEBUG/FPSCounter(1393): fps: 61

01-02 20:54:06.273:DEBUG/FPSCounter(1393): fps: 61

01-02 20:54:07.273:DEBUG/FPSCounter(1393): fps: 60

01-02 20:54:08.283:DEBUG/FPSCounter(1393): fps: 61


每秒60帧是相当不错的结果。当然Hero要稍差一点,这是因为它的CPU稍差。可以使用SpatialHashGrid来加速世界的模拟,我们愿意把这个练习留给读者。当然并不需要这么做,因为Hero总是有这样的问题(其他任何的1.5版设备一样)。更糟的是Hero上时不时进行的垃圾回收所产生的暂停,原因先前已经讲过(直接ByteBuffer中的一个bug),但目前没有什么解决办法,只能寄希望于Android1.5版早点被淘汰吧。

在上面的测试中关闭了主菜单中的音效。下面将音效打开后再进行测试:

[java] view plain copy print ?
  1. Hero (1.5):  
  2.   
  3. 01-02 21:01:22.437:DEBUG/FPSCounter(8251): fps: 43  
  4.   
  5. 01-02 21:01:23.457:DEBUG/FPSCounter(8251): fps: 48  
  6.   
  7. 01-02 21:01:24.467:DEBUG/FPSCounter(8251): fps: 49  
  8.   
  9. 01-02 21:01:25.487:DEBUG/FPSCounter(8251): fps: 49  
  10.   
  11. Droid (2.1.1):  
  12.   
  13. 01-02 21:10:49.979:DEBUG/FPSCounter(1676): fps: 54  
  14.   
  15. 01-02 21:10:50.979:DEBUG/FPSCounter(1676): fps: 56  
  16.   
  17. 01-02 21:10:51.987:DEBUG/FPSCounter(1676): fps: 54  
  18.   
  19. 01-02 21:10:52.987: DEBUG/FPSCounter(1676):fps: 56  
  20.   
  21. Nexus One (2.2.1):  
  22.   
  23. 01-02 21:06:06.144:DEBUG/FPSCounter(1470): fps: 61  
  24.   
  25. 01-02 21:06:07.153:DEBUG/FPSCounter(1470): fps: 61  
  26.   
  27. 01-02 21:06:08.173:DEBUG/FPSCounter(1470): fps: 62  
  28.   
  29. 01-02 21:06:09.183:DEBUG/FPSCounter(1470): fps: 61  
Hero (1.5):

01-02 21:01:22.437:DEBUG/FPSCounter(8251): fps: 43

01-02 21:01:23.457:DEBUG/FPSCounter(8251): fps: 48

01-02 21:01:24.467:DEBUG/FPSCounter(8251): fps: 49

01-02 21:01:25.487:DEBUG/FPSCounter(8251): fps: 49

Droid (2.1.1):

01-02 21:10:49.979:DEBUG/FPSCounter(1676): fps: 54

01-02 21:10:50.979:DEBUG/FPSCounter(1676): fps: 56

01-02 21:10:51.987:DEBUG/FPSCounter(1676): fps: 54

01-02 21:10:52.987: DEBUG/FPSCounter(1676):fps: 56

Nexus One (2.2.1):

01-02 21:06:06.144:DEBUG/FPSCounter(1470): fps: 61

01-02 21:06:07.153:DEBUG/FPSCounter(1470): fps: 61

01-02 21:06:08.173:DEBUG/FPSCounter(1470): fps: 62

01-02 21:06:09.183:DEBUG/FPSCounter(1470): fps: 61


当播放背景音乐时,Hero的性能表现差许多,音效也影响了Droid的部分性能,而对于Nexus One则完全没有影响。那么有什么解决办法吗?没有。问题不是出在音效上而是在背景音乐上。解码MP3OGG文件流会占用游戏的CPU周期;游戏就是这样工作的。在测试游戏性能时注意这个因素。

8小结

本文利用OpenGL ES创建了本书的第二个游戏。由于框架优良,游戏非常容易实现。纹理图集和SpriteBatcher的使用使性能得到提高。本文也讨论了如何渲染固定宽度的ASCII点阵字体。游戏机制的良好设计和世界单位与像素单位之间关系的清晰定义使得游戏开发非常容易。设想一下,如果所有的东西都是以像素为单位进行操作,这简直就是个噩梦。所有的计算都进行了分类——这并不利于低性能的Android设备。同时也把游戏逻辑和游戏展现相互独立开。总而言之,Super Jumper是非常成功的。

 

《Android 4游戏编程入门经典》试读电子书免费提供,有需要的留下邮箱,一有空即发送给大家。 别忘啦顶哦! 点击查看更多关于虾米站长网


你可能感兴趣的:(Super Jumper:一个2DOpenGL ES游戏)