论述如何基于3种开发工具制作《Monkey Jump》

来源:互联网 发布:网络推广专员要求 编辑:程序博客网 时间:2024/06/05 07:54

论述如何基于3种开发工具制作《Monkey Jump》(一)

发布时间:2012-02-10 18:04:09 Tags:Cocos2d,PhysicsEditor,TexturePacker,《Monkey Jump》

游戏邦注:本文作者为Andreas Loew,他是TexturePacker和PhysicsEditor工具的开发者。

通过本文,你将学习如何制作一款有关猴子一天糟糕经历的游戏。它专注于自己的事情,但很多疯狂物体持续从天而降。(请点击阅读第2、第3部分)

大家可以参考如下画面:

《Monkey Jump》from raywenderlich.com

《Monkey Jump》from raywenderlich.com

到本文结尾,你将完成一款基于物理原理的杰出游戏,知晓TexturePacker和PhysicsEditor如何帮你节省大把开发时间。

要读懂本文,你需对Cocos2D有所了解。

你还需要具有TexturePacker和PhysicsEditor工具。你可以从TexturePacker.com和physicseditor.de下载二者的评估版,但需注意,评估版的功能并不完整。

入门指南

我们将从设计阶段着手。所以首先我将简要概述这款游戏的运作方式。

就如你所看到的,我们的主角是猴子,其运动将由加速计控制——也就是通过倾斜iPhone或iPad的左右方向。当我们触动屏幕时,猴子就会跳跃。

游戏中,道具会从屏幕顶部落下,降落频率会随游戏的进展而提高。我们将在游戏中设置降落指示器,告知玩家下个道具的降落位置。道具持续累积,猴子需停留于道具的顶部,方能保持存活。

游戏包含两类道具:伤害和治愈猴子的道具(游戏邦注:例如香蕉会治愈猴子)。猴子的健康状态会在屏幕左上方以香蕉状呈现。

玩家分数会显现于屏幕右上方,通常是在道具堆的最高点位置。

Design from raywenderlich.com

Design from raywenderlich.com

要着手制作,首先要下载指南源代码,将其压缩至首选位置。你的起始点是0-BaseProject,这包含基本Cocos2d设置和若干整合资产。

我已在若干开发阶段中添加目录,其中包含你需完成的结果。这些目录会简化你的工作,防止你迷失方向或跳过项目的某块内容。

各文件夹都包含独立完整项目,且包含两个子文件夹:

第一个文件夹叫做Assets,包含TexturePacker和PhysicsEditor保存文件之类的内容,目录形式的Xcode项目及Vicki Wenderlich创建的免费游戏图像集。

Assets文件夹进一步分解成子背景和jungle文件夹,前者包含背景图像,后者则是近景物件和动画内容。

第二个文件夹是MonkeyJump,包含Xcode和文件源,文件源位于第二个MonkeyJump文件中,第二个MonkeyJump文件包含如下子文件:

* libs:Cocos2d、Box2d和Cocos Denshion等所有Cocos2d内容

* GBox2D:我的ObjectiveC++ Box2d包装内容

* Resources:音效和配乐

创造子画面表单

现在可以开始着手真正的内容,首先从子画面表单入手。

打开0-BaseProject文件夹,查看资产文件。我已安排好图像内容。我们将把“背景”文件中的子画面添加至首个子画面表单中,再来是将“jungle” 中的子画面内容添加至第二个子画面表单中。

背景表单将运用RGB565(三原色光模式),这是16位元的图像格式,能够减少50%的存储容量。运用RGB565也能够加速渲染过程。包含多数内容的近景表单也将采用RGBA8888,呈现高质量画面。

创建背景子画面表单

我们从制作背景表单入手。首先是启用TexturePacker,访问O-BaseProject\Assets\background folder,将其拖至TexturePacker窗口的右侧。你将看到jungle背景图像出现在窗口中间。

background settings from raywenderlich.com

background settings from raywenderlich.com

要设立纹理参数,首先要选择Data格式,这当然就是Cocos2d。接着选择Texture格式。我们将采用PVR压缩格式.pvr.ccz。

TexturePacker提示我们应打开预乘alpha,然后表示同意,点击“Apply”。

预乘的意思是,保存文件时所有颜色值同透明值相乘。这加速游戏的图像渲染,相乘步骤无需在运行时间中执行。

pvr.ccz的主要优点是,其能够很快加载,通常比PNG消耗更少内存。在TexturePacker右下角标记,内存消耗量如何从8192kB减少至4096kB。

图像质量会受到细微影响:我们可以发现渐变色没那么流畅,出现若干所谓的banding伪影。我们可以通过在FloydSteinberg中融入Dithering(抖动递色)弥补这种情况。

下面两个图像呈现部分jungle内容。左侧没有dithering,右侧有:

RGB565 image with banding artifacts&RGB565 image with dithering 03

RGB565 image with banding artifacts&RGB565 image with dithering 03

然后,将Data文件设置成Xcode项目Resources目录中的background-hd.plist。系统会自动将Texture文件移至background-hd.pvr.ccz。

不要担心路径会以绝对模式呈现。TexturePacker会在文件保存后立即创建自己的相对路径。这意味着,只要不改变资产&资源同.tps file保存文件的相对位置,我们就能够随意移动项目。

-hd扩展名非常重要,因为TexturePacker能够保存视网膜屏幕,降低图像分辨率服务陈旧设备。要做到这点,只需查看AutoSD选项。这能够创造服务视网膜屏幕的background-hd.plist和background-hd.pvr.ccz文件,以及background.plist和background.pvr.ccz内容,满足低图像分辨率需求。

Max. width和Max. height以红色呈现,这意味着TexturePacker无法在纹理尺寸规格里呈现子画面,因为边距填充的默认值是2。

向下滚动左窗格,将边距填充和模型填充设置成0。当前所有数值都应以黑色呈现。

background settings 02 from raywenderlich.com

background settings 02 from raywenderlich.com

最后,通过点击Save,将表单以background.tps格式保存于Assets文件夹中。这令Resources文件生成4个文件:background-hd.plist、 background-hd.pvr.ccz、background.plist和background.pvr.ccz。

创建Jungle子画面表单

现在通过点击工具栏中的New按键创建近景内容的新表单。将资产文件中的完整jungle文件夹拖至空白的右窗格中。

TexturePacker如何在右窗格中建造完整目录结构?(游戏邦注:你可以通过点击“jungle”左侧的箭头按键扩展树形视图)。

Folder Hierarchy from raywenderlich.com

Folder Hierarchy from raywenderlich.com

我们采用Smart Folders功能,在此每次我们于文件夹中添加子画面时,TexturePacker都会重新扫描目录。若你想要添加新资产或给既有资产重命名,只需将其放入有关文件机制的文件夹中,然后当你重新登陆TexturePacker时,系统就会自动更新表单。

注意:若你需要立即更新众多子画面表单,可以切换至Terminal,通过调用TexturePacker *.tps更新命令行中的所有.tps文件。

你甚至可以将TexturePacker整合进自己的Xcode创建过程中。这有若干优点。

首先,你不再需要担心子画面表单——只需将子画面添加至自己的资产文件夹中。

其次,若你采用版本控制,子画面表单就会以二进制blob模式呈现。这将快速扩展你的工作副本尺寸,特别是在控制版本中采用Git或Mercurial软件。关于此的简单操作方式是,不要在版本控制中添加子画面表单,而是抽空制作这些内容。

关于选项,我们采用和背景表单相似的设置方式。确保图像采用RGBA8888格式,以高质量状态呈现。

foreground settings from raywenderlich.com

foreground settings from raywenderlich.com

同时要将边距填充的默认值设置成2。这将减少子画面的边界伪影情况。有时OpenGL会融入相邻子画面的像数。边距填充通过在子画面周围添加透明边距避免此问题。

注意:若你想要无缝隙地将子画面用作并列图层,你就得避开或减少边界伪影(游戏邦注:这将透明边界换成彩色模式)。若你没有这么做,并列图层中的横线就会变成透明状态。

将项目Resources文件夹中的jungle-hd.plist设置成Data文件,将同个文件夹中的jungle-hd.pvr.ccz设置成Texture文件。查看AutoSD旁的方框。

最后,将表单以“jungle.tps”格式保存于资产文件中,然后点击Publish。这令Resources文件夹生成4个文件:jungle-hd.plist、jungle-hd.pvr.ccz、jungle.plist和jungle.pvr.ccz。查看它们是否存在那里。

Sprite Sheets from raywenderlich.com

Sprite Sheets from raywenderlich.com

手动制作物理模型

下面我们将通过PhysicsEditor创建物理模型。

启动PhysicsEditor,从基本装置着手。我们需要将Exporter设置成Box2d Generic (PLIST)。为此,我添加一个载入程序,你可以将此运用至自己的Cocos2d项目。若你不满意PhysicsEditor所支持的格式,可以创建自己的自定义数据输出装置。

将PTM-Ratio设置成170。这告知Box2d,170像数=1公尺(39.37英寸),公尺是Box2d的内部测量单位。我选择170像数是因为这刚好是猴子的高度,在模拟情境中猴子的高度将变成1公尺。

基于不同分辨率创建不同模型将令物件生成不同集合,从而带来不同物理行为(游戏邦注:这是我们所不需要的)。这里的理念是基于各种设备运行相同模拟情境,然后只调整图像元素。

physicseditor setup from raywenderlich.com

physicseditor setup from raywenderlich.com

设定固定装置

我们现在将要手动创建自己的首个模型。将jungle/floor/grassfront.png模型拖至PhysicsEditor的左窗格。当你这么做时,模型也会同时出现于中间窗格,这是核心编辑区域。

在蓝色小圈圈中标上叉叉:这是模型的定位点。我们将维持此模型的定位点位置,也就是右下角的位置。稍后我将向你展示如何改变此定位点。

首先运用顶部工具条中的多边形工具。只要你点击此工具,小型红色三角形就会出现。你可以通过自己的鼠标拖曳此三角形。

控制键让你得以调整三角形的形状。在线条旁进行双击操作,就能够添加额外顶点。双击顶点可以进行移除操作。

你无需考虑多边形方向或凸面、凹面模型这类的元素。这些都可以通过PhysicsEditor进行处理。

现在我们将给地面创建四边形模型。地面应该盖过土壤,但不要盖过草坪,呈现如下样式:

editing shapes from raywenderlich.com

editing shapes from raywenderlich.com

设定参数

地面属于静态模型,因此其参数非常基础。

密度(Density)影响主体/固定装置的重量。地面不会移动,所以密度数值无关紧要。返还(Restitution)是指碰撞模型会从另一物体弹回。我们希望将地面的数值设置成0。

摩擦力(Friction)是这里唯一重要的数值,这决定子画面在平面的滑动数量。所以将此设置成0.7。

下个要设置的道具是触碰位置。PhysicsEditor令你得以轻松处理这些参数——也就是说,无需进行16进制运算。首先,你需要在相关文本字段中设定名称,这样二进制数值就能获得有效名称:

* 平面——包括地面和墙面

* 猴子

* good_objects——不会伤害猴子的物件

* bad_objects——会伤害猴子的物件

忽略其他二进制数字——我们不需要这些内容。

setting the parameters from raywenderlich.com

setting the parameters from raywenderlich.com

两个物体只有在这样的情况下才会发生碰撞:B物体的二进制码范畴(category)被用来充当A物体的掩码(mask),反之亦然。我们将给平面二进制码勾选Category字段,因为此模型就是个平面,然后给所有的二进制码勾Mask字段。这能够促使各物件同平面的碰触。

将项目以“shapes.pes” 格式保存于资产文件夹中。

基于Tracer创建模型

手动创建模型通常总是缺乏趣味。所以不妨让PhysicsEditor替我们效劳。

将下列物体抛至左窗口:背包、香蕉、香蕉皮、食堂、帽子、菠萝和雕像。

autotrace from raywenderlich.com

autotrace from raywenderlich.com

选择背包,点击工具栏中的魔杖图标。这将打开新一个窗口tracer。tracer会呈现以平面模式所追踪模型的当前形状。

tracer in action from raywenderlich.com

tracer in action from raywenderlich.com

最重要的设置内容是Tolerance。此数值告知tracer应该如何匹配模型。这直接影响多边形定点的数量。例如,若将Tolerance设置成20,我们将获得5个顶点的多边形,这已不适合模型;将此数值设置成1就会获得39顶点的匹配多边形。

tracer from raywenderlich.com

tracer from raywenderlich.com

数值为5的Tolerance值属于能够被接受的匹配度,这约有15个顶点。完全没有问题!点击OK回到主屏幕。你可以持续调整模型,但不要过于吹毛求疵。

Not enough vertexes & Too many vertexes & Good trace

Not enough vertexes & Too many vertexes & Good trace

现在就来设置背包的参数。将密度设置成5.00,将Restitution设置成0.10,将Friction设置成0.5。你定会想要把握这些参数,以更清楚知晓它们的运作情况。

在此碰触截面中,勾选bad_objects旁的Cat. box,通过勾选所有Mask 方框,允许内容同其他所有物件碰触。

同时将定位点(游戏邦注:中间有叉叉的蓝色圈圈)拖至背包模型的中间。或者,你可以通过把右边工具条的Relative定位点值设成0.5和0.5,从而将定位点设置于模型正中心。

setting up the backpack from raywenderlich.com

setting up the backpack from raywenderlich.com

现在你知道如何创建模型,或手动,或运用tracer,那就接着自己动手制作剩余内容。运用如下图表所呈现的参数。别忘记将各模型的定位点拖至下图像所指示的位置中。

table from raywenderlich.com

table from raywenderlich.com

items from raywenderlich.com

items from raywenderlich.com

最后元素:猴子

我们所要创建的模型还有一个,这是最复杂的元素。猴子包含若干动画阶段(总共14个):

monkey phases from raywenderlich.com

monkey phases from raywenderlich.com

我首先想到的就是给各动画画面添加触碰多边形,从而完美配合各动画阶段。但这是个糟糕的主意。

首先,频繁更换固定装置会消费众多CPU能量。但更主要的问题是,随着新模型的出现,多数有关猴子的元素将发生改变。

这是因为Box2d基于多边形的面积和密度计算体积。若你改变多边形,猴子将得到或失去重量!这会导致物理模拟内容的呈现变得不规律。

解决方案是,只给猴子制作一个模型,尽量确保其匹配度。首先,将一个猴子模型拖至PhysicsEditor中。你可以通过双击模型名称进行重命名。

add monkey from raywenderlich.com

add monkey from raywenderlich.com

现在点击“+” 按键给猴子添加其他动画阶段。文件会在组合框中呈现,你可以进行转换。

通过点击魔杖图标启动tracer。现在你所看到的是模型已不适合猴子。这正是我们所期望的。

tracer from raywenderlich.com

tracer from raywenderlich.com

tracer现在处于动画追踪模式。你可以在两种画面模式中切换:交叉(intersection)和合并(union)。intersection只基于所有子画面都覆盖的内容生成多边形,而union则运用任一子画面所覆盖的元素。应该选择intersection。

滑块让你能够浏览各动画阶段,查看模型如何配合各不同阶段。

我们将把tracer创造的模型当作粗略模式。这是因为我们需要将猴子的身体分成若干部分。这里是我们所要创建的内容:

monkey shape 01 from raywenderlich.com

monkey shape 01 from raywenderlich.com

有时多边形模型无法顺畅滑过其他多边形,它们相互羁绊。这也是我们基于圆形模式设计猴子的原因所在。

另一原因是运用圆圈会带来更快速的碰撞检测。检验圆点是否处于圆圈之内所涉及的数学运算要比测试圆点是否处于多边形内简单得多。所以我们会节省CPU能量。完成其他组件后,我们将删除tracer模型。

首先创建猴子的头部。采用圆圈模型,将其变得和追踪版本一样大。头部应和其他部位区别对待,因为我们希望当物体从空中降落,击中猴子的头部时,猴子会受伤。

要将头部同其他部位区分开,就将Id设置成“head”。头部的category是猴子,其同good_objects、bad_objects和平面触碰,所以确保将这些元素设置成mask(掩码)。将密度设置成2.0,restitution设置成0.1,摩擦值(friction)设置成0.5。

接着基于两个圆圈制作猴子的身体,一个覆盖躯干,一个覆盖腿部。将Id设置为“body”。身体的category是猴子,它同good_objects、bad_objects和平面碰触。将身体的密度设置成2.0,restitution设置成0.1,摩擦值设置成0.5。

我们可以通过选择Filename组合框中的各种图像循环穿梭于各个动画阶段。查看圆圈是否适合各阶段。这无需是完美的搭配,你可以随时返回PhysicsEditor,调整接触组合。

最后,删除基于tracer创建的多边形。

游戏中,我们需要查看左右是否有物体促成此伸展动画。要做到这点,我们需在猴子左右两侧放置两个感应器。

感应器是无需同其他身体部位互动的固定装置,它们只会汇报触碰情况。要将固定装置变成感应器,就需要设置isSensor。

将左传感器的Id设置成“push_left”,右传感器的设置成“push_right”。category是猴子,它们只和bad_objects触碰——我们不希望猴子将平面和香蕉推开。

monkey shape from raywenderlich.com

monkey shape from raywenderlich.com

最后就是:保存,然后点击Publish。将文件以shapes.plist格式存储于项目的Resources文件夹中。

上面就是有关PhysicsEditor制作的全部内容,下面我们将转而谈论Xcode,着手游戏编码。

Xcode项目:综述

我利用附带Cocos2d元素的标准Cocos2d+Box2d模版制作首个项目,移除所有演示内容。我还添加声音资源和若干类(class),通过运用Cocos2d中的Box2d,你的工作将变得简单许多。

有关此项目,需注意的一点是,所有文件的扩展名应是.mm,而非.m。这是因为Box2d基于C++,所以我们必须在开发中采用Objective-C++。

现在我们就来深入谈论此初始项目:

GBox2D

首先先来谈论几点有关GBox2D的内容。GBox2D是Box2d的Objective-C转换内容,主要服务我即将问世的项目《TurtleTrigger》。

Gbox2D包含两个主要class:

* GB2Engine:此class覆盖Box2d的虚拟世界。其运作虚拟内容,更新所有子画面的位置和循环模式,能够循环访问游戏空间的所有物件。GB2Engine以单class模式执行,极大方便访问操作。

* GB2Node:此class型结合CCNode和B2Body。这是物理模拟内容和Cococs2d图像表现的粘合剂。它还包含简单管理物理物件的选择器。,同时植入代理服务器,以访问内部节点的数据。

* GB2Sprite:此class源自GB2Node,主要以CCSprite作为自己的内部物件。

* GB2DebugDrawLayer:这是覆盖调试绘图的Cocos2d图层。你可以像添加正常图层一样将其添加至自己的项目中。添加后,此软件将绘制物理模型的轮廓。这一软件的优点在于能够察觉内容是否运作于视网膜显示目标,然后相应地扩充内容。

* GB2Contact:当察觉存在冲突时,此结构将转变成物件的参数。每次接触都会调用进行碰触的两个相关物件。

* GB2WorldContactListener:这是C++ class,软件在物理模拟情况中反应碰撞。

若你有兴趣,可以自己查阅相关知识。但若你不清楚它们的实际操作内容,也不要担心,在下面的内容中,你将把握这些工具的运用方式。

基于Gbox2D的触碰检测

GBox2D使得碰撞检测变成小菜一碟!这是因为你无需制作严肃的逻辑陈述或系列选择结构,从中探索各种可能的碰撞组合。

相反,GBox2D只是运用触碰类型的名称,通过类型名称衍生出的名称访问选择器。若你觉得这听起来有些抽象,查看如下例子:

假设你有一只猴子和一根香蕉,它们是class分别属于Monkey和Banana的物件。若两个物体都开始碰撞,GBox2D将访问如下选择器:

[banana beginContactWithMonkey:collisionA];
[monkey beginContactWithBanana:collisionB];

若触碰已完成,物件就不再接触:

[banana endContactWithMonkey:collisionA];
[monkey endContactWithBanana:collisionB];

A触碰和B触碰的参数包含触碰信息,例如触碰涉及哪些物件和固定装置。我们将通过此信息,查看猴子是否被击中头部或身体。

AppDelegate

我所改变的存在于原始Box2d项目的AppDelegate元素如下:

首先,我将画面缓冲器的默认像数设置成RGBA8。这意味着,游戏会得到完整的24位元色彩深度。我关闭深度缓冲器,因为我们不再需要它。

EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
pixelFormat:kEAGLColorFormatRGBA8
depthFormat:0
];

下个需要设置的重要元素是,预乘alpha。这是因为我们运用通过TexturePacker制作而成的预乘PVR图像。若我们未设置预乘alpha,我们的图像将会出现深色的边界。

[CCTexture2D PVRImagesHavePremultipliedAlpha:YES];

由于我们采用随机生成器选择将降落的物件,所以我们需要制作内容。最佳方式就是采用time()。若你忘记制作随机生成器,你依然会得到随机数据(游戏邦注:但它们会和游戏开始时的内容一模一样)。

srand (time (NULL));

完成初始化操作后,就轮到GameLayer内容:

[[CCDirector sharedDirector] runWithScene: [GameLayer scene]];

GameLayer

GameLayer是源自CCLayer的简单class。它由空白的init函数构成。

-(id) init
{
if( (self=[super init]))
{
}
return self;
}

将CCLayer包装成CCScene,然后移交给Director的静态选择器:

+(CCScene *) scene
{
// ‘scene’ is an autorelease object.
CCScene *scene = [CCScene node];

// ‘layer’ is an autorelease object.
GameLayer *layer = [GameLayer node];

// add layer as a child to scene
[scene addChild: layer];

// return the scene
return scene;
}

声音资源

我从http://incompetech.com/获得主题音乐tafi-maradi-loop.caf。

由于我想要循环播放音乐,因此我亲自简化内容,选择没声道的主题。

物件的音效来自http://soundbible.com。有些内容基于cfxr制作而成。

所有音效和音乐文件都被转化成caf格式。

编辑和运行项目后,你将看到黑色屏幕。现在就在此屏幕中添加些许内容。

Xcode的基本设置

此部分旨在设定基本游戏图层和背景,设定物理引擎。

在深入编码内容前,还有一项工作需要完成:添加我们在PhysicsEditor中创建的资源。是否还记得?

* background.plist

* background.pvr.ccz

* background-hd.plist

* background-hd.pvr.ccz

* jungle.plist

* jungle.pvr.ccz

* jungle-hd.plist

* jungle-hd.pvr.ccz

* shapes.plist

双击Xcode中的Resources文件夹,选择Add Files To “MonekyJump”,然后选择上述文件,这样你就能够将上述文件添加至Xcode项目中的Resources文件夹中。

现在轮到编码内容。首先,首先我们需要创建一个Floor class ,以代表游戏中的平面。然后通过iOS\Cocoa Touch\Objective-C类模版创建新文件,这样你就能够将Floor.h and Floor.mm文件添加至你的项目中。给类Floor命名,将其变成GB2Sprite的子类。内容创建完后,不要忘记将Floor.m的扩展名改成.mm。

Floor.h包含如下内容:

#pragma once

#import “cocos2d.h”
#import “GB2Sprite.h”

@interface Floor : GB2Sprite
{
}

+(Floor*) floorSprite;

@end

Floor.mm包含如下内容:

#import “Floor.h”

@implementation Floor

+(Floor*) floorSprite
{
return [[[self alloc] initWithStaticBody:@”grassfront” spriteFrameName:@”floor/grassfront.png”] autorelease];
}

@end

这里唯一值得注意的代码行是initWithStaticBody选择器的调用。这让我们的物件变成静态模式——未被物理引擎移动的物件,通过shapes.plist文件的“grassfront”模型将身体模型初始化。

它还运用源自jungle.plist的floor/grassfront.png名称子画面图像。

为什么我们从GB2Sprite中导出此类,而不是直接采用GB2Sprite?答案在于GBox2D的碰撞处理,其运用类的名称调用碰触物件的适合选择器。由于我们想要知晓物件何时同平面碰触,平面物件的类名称就需要区别于其他GB2Sprite物件。

下步要进行的就是更新GameLayer。添加实体变量约束GameLayer.h中的必需物件:

@interface GameLayer : CCLayer
{
CCSprite *background;                   // weak reference
CCSprite *floorBackground;              // weak reference
CCSpriteBatchNode* objectLayer;         // weak reference

我们将把物件存储成弱引用(游戏邦注:就是不保证不被垃圾回收器回收的对象)——也就是其未提高各物件的留存期限。

你无需担心CCSprites被删除。它们会被添加成CCLayer衍生内容,这样其留存值至少是1。否则我们将被困于留存周期中,无法释放这些物件所包含的内存。

然后在GameLayer.mm的init选择器中填充内容。首先是加载子画面表单。

[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@”jungle.plist”];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@”background.plist”];

接着将物理模型加载至GB2ShapeCache中:

[[GB2ShapeCache sharedShapeCache] addShapesWithFile:@”shapes.plist”];

然后,设置图层。我们将把游戏分成下列图层:

* 背景图层:jungle图像

* 平面图层:包含茂密小草的单个子画面

* 物件图层:包含所有道具和猴子

* Hud图层存在分数和能量指示器。

layers from raywenderlich.com

layers from raywenderlich.com

添加代码,在init中制作基本背景和平面背景:

// Setup background layer
background = [CCSprite spriteWithSpriteFrameName:@"jungle.png"];
[self addChild:background z:0];
background.anchorPoint = ccp(0,0);
background.position = ccp(0,0);

// Setup floor background
floorBackground = [CCSprite spriteWithSpriteFrameName:@"floor/grassbehind.png"];
[self addChild:floorBackground z:1];
floorBackground.anchorPoint = ccp(0,0);
floorBackground.position = ccp(0,0);

然后添加物件图层。这将变成一批子画面的节点,旨在加速物件的渲染:

objectLayer = [CCSpriteBatchNode batchNodeWithFile:@"jungle.pvr.ccz" capacity:150];
[self addChild:objectLayer z:10];

最后是调试图层:

// add the debug draw layer, uncomment this if something strange happens ;
[self addChild:[[GB2DebugDrawLayer alloc] init] z:30];

若你想要放弃调试图画,只需在第二行中添加注释。若启用此内容,物理模型将被绘制于子画面周围,让你能够呈现碰触的发生地点,以及所有模型是否协调一致。

接着,将平面物件变成物件图层的衍生内容。在GameLayer.mm顶部植入Floor.h:

#import “Floor.h”

然后在init底部添加平面物件:

[objectLayer addChild:[[Floor floorSprite] ccNode] z:20];

我们无需在物理空间中再添加任何物件——其他内容都已包含于Gbox2D之中。

然后在iPhone模拟器中编辑和运行项目。所呈现的内容应大致如下:

iPhone simulator from raywenderlich.com

iPhone simulator from raywenderlich.com

好极了——现在我们可以着手在游戏中添加动作元素。

降落物件

这部分内容的两个相关目标是:让物件从天而降,添加音效。

所有降落物件的基本类将被命做Object。其将处理音效及若干基本碰触探测。我们随后将谈及从Object类中导出其他子类。

首先,通过iOS\Cocoa Touch\Objective-C类模版创建新文件。将类命作Object,将其变成GB2Sprite的子类(游戏邦注:记得要将Object.m的扩展名改成.mm)。

Object是个简单类,源自GB2Sprite。这意味着它本身具有物理和图像
性能。

为简化我们的工作,我借鉴物理子画面及子画面表单图像元素的命名方式命名音效文件。这让我们得以在必要时候通过物件的名称创建正确的模型和音效。

为顺利达成此目标,我们需要名为objName的属性——objName被输进initWithObject选择器,成为类的的一部分。

RandomObject是创建随机物件的工厂模式,会在内容完成后呈现适当的物件名称。

将此代码粘帖至Object.h:

#pragma once

#import “cocos2d.h”
#import “GB2Sprite.h”

@interface Object : GB2Sprite
{
NSString *objName; // type of the object
}

@property (retain, nonatomic) NSString *objName;

-(id) initWithObject:(NSString*)objName;
+(Object*) randomObject;

@end

现在就切换到Object.mm内容。我们首先要应对若干必要的输入内容,综合objName的属性。

#import “Object.h”
#import “GB2Contact.h”
#import “SimpleAudioEngine.h”
#import “GMath.h”

@implementation Object

@synthesize objName;

GMath.h包含若干助手功能—–例如,gFloatRand,这是个远程浮点随机数字生成器。

接着就是添加init选择器,示例物理物件。你可以采用物件的名称,因为它们将呈现物理模型。至于子画面窗口标识,你就需要添加文件名称和.png扩展名。在属性中存储objName——在碰触检测中我们将需要通过它呈现音效。

-(id) initWithObject:(NSString*)theObjName
{
self = [super initWithDynamicBody:theObjName
spriteFrameName:[NSString stringWithFormat:@"objects/%@.png", theObjName]];
if(self)
{
self.objName = theObjName;
}
return self;

在dealloc选择器中,我们只需释放objName属性,调用超级dealloc:

-(void) dealloc
{
[objName release];
[super dealloc];
}

下个要添加的内容就是我们的静态工厂模式,这将生成随机物件。我决定在此采用简单的逻辑陈述。原因是,我们稍后需要给香蕉和香蕉皮创建特殊类。这两个物件只有一个路径,而其他3个物件则各有3个路径,这样它们就有望频繁出现。

逻辑结构效果显著。 你可以通过有命名的数组保存CPU周期,但程序一秒会被访问一次,因此我们的方式更胜一筹。

+(Object*) randomObject
{
NSString *objName;
switch(rand() % 18)
{
case 0:
objName = @”banana”;
break;

case 1:
objName = @”bananabunch”;
break;

case 2: case 3: case 5:
objName = @”backpack”;
break;

case 6: case 7: case 8:
objName = @”canteen”;
break;

case 9: case 10: case 11:
objName = @”hat”;
break;

case 12: case 13: case 14:
objName = @”statue”;
break;

default:
objName = @”pineapple”;
break;
}
return [[[self alloc] initWithObject:objName] autorelease];
}

最后,给文件添加结束内容:

@end

现在我们把话题转换至GameLayer.h,直接在#import陈述之后给物件类添加前置声明:

#import “cocos2d.h”
@class Object;

给GameLayer类添加这些新元素:

ccTime nextDrop;    // Will keep the time until the next drop.
ccTime dropDelay;     // The delay between two drops.
Object *nextObject;   // Contains a reference to the next item to drop.

然后切换到GameLayer.mm,在文件开始添加Object.h输入内容。同时输入GMath.h:

#import “Object.h”
#import “GMath.h”

在init选择器末端初始化新变量,通过画面更新内容更新选择器:

nextDrop = 3.0f;  // drop first object after 3s
dropDelay = 2.0f; // drop next object after 1s

[self scheduleUpdate];

最后一行的内容是,协助各窗口调用名为“update” 的选择器。此选择器的参数就是距选择器上次被访问所流逝的时间。在init后添加更新方式:

-(void) update: (ccTime) dt
{
// 1 – drop next item
nextDrop -= dt;
if(nextDrop <= 0)
{
// 2 – do we have the next object?
if(nextObject)
{
// 3 – set the object as active, making it drop
[nextObject setActive:YES];

// 4 – set next drop time
nextDrop = dropDelay;
// reduce delay to the drop after this
// this will increase game difficulty
dropDelay *= 0.98f;

}

// 5 – create new random object
nextObject = [Object randomObject];
// but keep it disabled
[nextObject setActive:NO];

// 6 – set random position
float xPos = gFloatRand(40,440);
float yPos = 400;
[nextObject setPhysicsPosition:b2Vec2FromCC(xPos, yPos)];

// 7 – add it to our object layer
[objectLayer addChild:[nextObject ccNode]];
}
}

让我们逐一查看上述代码。

1. 此截面降低间隔时间,因为更新内容最后才由nextDrop导出。若nextDrop的数值降至0以下,我们就该制作新的降落道具。

2. 若nextDrop寿命耗尽,截面就会查看nextObject是否有储备物件。

3. 若是如此,这里应将其设置成活跃模式。将物件设置成活跃模式让我们能够对物件进行物理引擎控制。

4. 此截面设置下次降落时间同当前降落的间隔,从将降落延迟降低2%,促使游戏因每次的降落道具而变得更具难度。

5. 此截面通过我们在Object中的工厂模式创造新降落物件,将物件设置成非活跃状态,阻止物件继续降落及参与物理模拟情境中。

6. 截面给予物件随机位置。屏幕的宽度是480pt(游戏邦注:Cocos2d采用点数作为基本单位,1 pt相当于“陈旧”设备的1像数,视网膜显示设备的2像数)。代码确保物件的位置处于40-440点数之间。截面还将Y轴的起始位置设置成400,这样物件就会从屏幕顶端开始屏幕之外的运作。b2Vec2FromCC方式被用于在点坐标中制作box2db2Vec2。B2Vec2FromCC将Cocos2d的点数转变成Box2d的以米为单位的数值。

7. 最后,截面在物件图层中添加物件。

编辑,然后运行内容!你应看到类似于如下画面的内容,但当然其中所涉及的道具完全不同。道具看起来有些模糊,因为调试图样依然处于运行中:

画面略显模糊

画面略显模糊

通过在GameLayer.mm中给相关代码行添加注释,退出调试图层:

//        [self addChild:[[GB2DebugDrawLayer alloc] init] z:30];

现在你的游戏会看起来好很多:

清晰画面

清晰画面

查看道具如何跳出屏幕,分布于左右两侧?游戏的目标是让道具堆积起来,所以我们需要在屏幕两侧添加墙体。

要进行此操作,我们只需创建两个新的GB2Node物件。它们会跳出屏幕,分布于左右两侧。

由于GB2Nodes会自己添加至当前物理模拟情境中,所以你无需手动添加这些内容。它们不会以画面形式呈现,所以只要创建这些内容就可以。

将这些代码行添加至GameLayer.mm的init中,就在平面图层之后:

GB2Node *leftWall = [[GB2Node alloc] initWithStaticBody:nil node:nil];
[leftWall addEdgeFrom:b2Vec2FromCC(0, 0) to:b2Vec2FromCC(0, 10000)];

GB2Node *rightWall = [[GB2Node alloc] initWithStaticBody:nil node:nil];
[rightWall addEdgeFrom:b2Vec2FromCC(480, 0) to:b2Vec2FromCC(480, 10000)];

然后编辑和运行内容。查看物件如何在墙面空间范围中呈现?

保存物件

保存物件

这看起来很不错,但依然缺少某些元素。我觉得物件在相互碰触时应该发出一些声音。难道你不这么觉得?

我不希望物件持续发出声音,而只是在它们以一定速度击中某物的情况下。所以我们将查看物件的速度,只在它们以相当快的速度碰触某物时发出声音。

将此代码添加至Object.mm:

-(void) beginContactWithObject:(GB2Contact*)contact
{
b2Vec2 velocity = [self linearVelocity];

// play the sound only when the impact is high
if(velocity.LengthSquared() > 3.0)
{
// play the item hit sound
// pan it depending on the position of the collision
// add some randomness to the pitch
[[SimpleAudioEngine sharedEngine] playEffect:[NSString stringWithFormat:@"%@.caf", objName]
pitch:gFloatRand(0.8,1.2)
pan:(self.ccNode.position.x-240.0f) / 240.0f
gain:1.0 ];

}
}

我们需将上述方式命做beginContactWithObject,这样当两个物体发生碰触时,GBox2D就会自动调用内容。

linearVelocity方式让我们能够把握物件的速度。调用物件Length或LengthSquared则呈现速度的具体数值。比较恒定数值时,我倾向采用LengthSquared,因为它不会要求计算数值的二次根。

我们将通过调用SimpleAudioEngine的playEffect方式制作音效。首个参数就是音频文件的名称。

为简化我们的工作,我给予音效同物件和子画面相同的名称。所以你可以运用我早前存储的objName内容,从中获得正确的音频文件。通过NSString在名称上附上.caf。

通过运用数值是0.8和1.2的gFloatRand调节音高(pitch)。这将呈现音高有所变化的音效。若所有物件都发出相同声音,我们定会觉得有些乏味。

这里我们要运用的最后一个策略是将声音来源嵌入物件的位置。Pan允许的数值范围是-0.1到1.0间。物件的X轴位置将处于0-480间,所以减去240,除以240你就会得到此范围值。

若你希望,在无需重写代码的情况下,物件能够在击中地面时能够发出声音,就添加下述方式,这会将“物件–平面”碰触发送至Object.mm:

-(void) beginContactWithFloor:(GB2Contact*)contact
{
[self beginContactWithObject:contact];
}

然后进行编辑和运作,查看物件如何降落,同时在发生碰触时形成声音。

但我们的游戏目前还有一点令我不是很满意。首个道具在降落后就停留在半空中,但声音引擎已经预置。

只要我们添加主题音乐,这就不成问题,因为音乐会立即将声音引擎初始化。但若你现在想要解决此问题,首先要在GameLayer.mm顶部添加输入陈述:

#import “SimpleAudioEngine.h”

然后在GameLayer的init选择器中添加SimpleAudioEngine共享物件调用:

[SimpleAudioEngine sharedEngine];

上述执行方式给所有降落物件的碰撞带来相同基本音效。若你有雄心壮志,可以根据物件碰撞类型制作不同音效(游戏邦注:也就是说,基于食堂撞击食堂,制作一种音效;根据香蕉击中食堂制作另一种音效……)

另一提高此代码的方式就是通过碰撞速度变化所呈现的音效。

下步操作是什么?

现在已是文章尾声,当前内容保存于3-DroppingObjects文件夹的源代码zip文件中。