游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

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

发布时间:2012-02-10 18:04:09 Tags:,,,

游戏邦注:本文作者为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文件中。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

How To Build a Monkey Jump Game Using Cocos2D, PhysicsEditor & TexturePacker Part 1

This is a post by special contributor Andreas Loew, the creator of TexturePacker and PhysicsEditor.

In this tutorial, youll learn how to make a game about a monkey having a rough day. He’s just minding his own business, but these crazy objects keep falling from the sky!

To see what I mean, check out this cool video:

By the end of this tutorial, you’ll have created a cool physics enabled game, and learned how using TexturePacker and PhysicsEditor can save you a ton of development time.

To go through this tutorial, you should have some basic familiarity with Cocos2D. If you are new to Cocos2D, check out some of the other tutorials on this site first.

You will also need a copy of TexturePacker and PhysicsEditor. To to through this tutorial you can use an evaluation version from TexturePacker.com and physicseditor.de, but note that there are some features that are not present in the evaluation versions. If you want to order the full versions, here’s a direct link to order the bundle.

Ready to make that monkey jump? Keep reading to get started!

Getting Started

We will start with the design phase. So let me give you a brief overview of how the game will work.

As you’ve seen, our main character will be a monkey. His movement will be controlled with the accelerometer – that is, by tilting the iPhone or iPad left and right. The monkey will jump when the screen is tapped.

During the game, items will drop from the top of the screen, and the dropping frequency will increase the longer the game continues. We will have a drop indicator to show the player where the next item will fall. Items pile up, and the monkey must stay on top to stay alive.

There will be two sorts of items: those that hurt the monkey, and those that heal him – namely, bananas! The monkey’s health will be displayed in the top-left corner as a bar of bananas.

The score will be displayed in the top-right corner, and will be at the height of the highest item on the stack.

To get started, download the tutorial source code and unzip it into your preferred location. Your starting point will be 0-BaseProject, which contains the basic Cocos2d setup and some integrated assets.

I’ve added directories for several stages of the development process containing the results you should achieve. These directories will make your life easier in case you get lost, or if you simply want to skip one part of the project.

Each folder contains the complete project and is independent from the rest. Inside each folder are two more folders:

The first is called Assets, and contains such things as the TexturePacker and PhysicsEditor save files, a directory holding the Xcode project, and a free game art pack created by Vicki Wenderlich.

The Assets folder is organized further into the subfolders background, containing the background graphics, and jungle, containing the foreground objects and animation.

The second folder is called MonkeyJump. It contains the Xcode project and the sources, which are contained in a second MonkeyJump folder which has the following subfolders:

* libs: Cocos2d, Box2d, Cocos Denshion… all the Cocos2d stuff

* GBox2D: my ObjectiveC++ Box2d wrapper

* Resources: sounds and music

Creating Sprite Sheets

Time to start the real work, beginning with sprite sheets.

Open the 0-BaseProject folder and have a look at the Assets folder. I’ve already arranged the images for us. We’re going to add the sprites from the “background” folder into one sprite sheet, and the sprites from the “jungle” folder into as second.

The background sheet will use RGB565, a 16-bit image format that reduces the amount of memory used by 50%. Using RGB565 will also speed up rending. The foreground sheet, with most of the content, will use RGBA8888 for full quality. For more information on pixel formats with TexturePacker, check out this tutorial.

Creating the Background Sprite Sheet

Let’s start by creating the background sheet. Start TexturePacker, navigate to the O-BaseProject\Assets\background folder, and drag it into the right hand side of the TexturePacker window. You’ll see the jungle background image show up in the center of the window.

To set up the parameters for the texture, first choose the Data format, which is of course Cocos2d. Next choose the Texture format. We will use zlib compressed PVR format .pvr.ccz.

TexturePacker warns us that we should turn on pre-multiplied alpha. Let’s agree to that and click “Apply.”

Pre-multiply means that all color values get multiplied by their transparency value when saving the file. This speeds up graphics rendering in the game, since the multiplication step does not need to be performed at runtime.

The main advantage of pvr.ccz is that it can be loaded quite fast, and it usually consumes less memory than PNG. Note in the bottom-right corner of TexturePacker how the memory consumption is reduced from 8192kB to 4096kB.

The quality of the image will suffer a bit: we can see that the gradients are now not as smooth and have some artifacts called banding. We can compensate for this by setting Dithering to FloydSteinberg.

The following two images show a part of the jungle. The left one has no dithering, while on the right one, dithering has been enabled:

Next, set the Data file to background-hd.plist in the Resources directory of the Xcode project. This will automatically set the Texture file to background-hd.pvr.ccz.

Don’t worry that the paths are displayed as absolute paths. TexturePacker creates a relative path to the document file as soon as it is saved. This means we can move our project around as long as we don’t change the relative position of the assets and resources to the saved .tps file.

The -hd extension is important, because TexturePacker can save retina display and reduced resolution images for older devices. To enable this, simply check the AutoSD option. This will create background-hd.plist and background-hd.pvr.ccz files for retina display, and background.plist and background.pvr.ccz for reduced resolution.

Max. width and Max. height appear in red, which means TexturePacker can’t layout the sprite within the texture’s dimensions. This is because padding is set to 2 by default.

Scroll down on the left pane and set both border padding and shape padding to 0. Now all numbers should appear in black.

Finally, let’s save our sheet as background.tps in the Assets folder by first clicking Save and then Publish. This creates four files in the Resources folder: background-hd.plist, background-hd.pvr.ccz, background.plist and background.pvr.ccz. (Open the Resources folder to make sure the files are there.)

Creating the Jungle Sprite Sheet

Now create a new sheet for the foreground by pressing the New button on the toolbar. Drag the complete jungle folder from the Assets folder into the empty right pane.

See how TexturePacker builds the complete directory structure on the right pane? (You can expand the tree view by pressing the arrow button to the left of the word “jungle”.)

We’re using a feature called Smart Folders, where TexturePacker rescans the directory every time we add sprites to the folder. If you want to add a new asset, or rename or change an existing asset, just drop it into the folder on your file system, and when you reenter TexturePacker it will automatically update the sheet.

Note: If you need to update many sprite sheets at once, you can switch to Terminal and update all .tps files from the command line by calling TexturePacker *.tps.

You can even integrate TexturePacker with your Xcode build process. This has several advantages.

First, you no longer need to worry about sprite sheets – just add the sprites to your Assets folder, and you’re done.

Second, if you use version control, sprite sheets will hang around as binary blobs. This will grow your working copy size pretty fast, especially when using Git or Mercurial for version control. The easy way around this is to simply not add the sprite sheets to version control, and instead create them on the fly.

For more information on integrating TexturePacker with Xcode 4, check out this tutorial.

For the options, we’ll use a nearly identical setup as for the background sheet (see screenshot below). Just make sure to leave the image format at RGBA8888 for full quality.

Also be sure to leave the paddings set to the default of 2. This will reduce artifacts on the sprite borders. Sometimes OpenGL drags in pixels from neighboring sprites. Padding avoids this problem by adding transparent borders around the sprites.

Note: If you want to use sprites as tiles with seamless connections, you must use either select extrude or reduce border artifacts, which replaces the transparent borders with colored ones. If you do not do this, you might get transparent lines between the tiles.

Set jungle-hd.plist in the Resources folder of the project as the Data file, and jungle-hd.pvr.ccz in the same folder as the Texture file. Check the box next to AutoSD.

Finally, save the sheet as “jungle.tps” in the Assets folder and click Publish. This creates four files in the Resources folder: jungle-hd.plist, jungle-hd.pvr.ccz, jungle.plist, and jungle.pvr.ccz. Check and see if they are there.

Creating Physics Shapes Manually

Next we’ll set up the physics shapes with PhysicsEditor!

Launch PhysicsEditor and start with the basic setup. We need to set the Exporter to Box2d Generic (PLIST). For this I added a loader which you can use in your Cocos2d projects. If you are not satisfied with the formats supported by PhysicsEditor, you can simply create your own custom data exporter.

Set the PTM-Ratio to 170. This tells Box2d that 170 pixels are equal to 1 meter (39.37 inches), the internal measurement unit of Box2d. I chose 170 pixels because this is the monkey’s height, and he is going to be 1 meter tall in the simulation.

We’re going to create all the physics shapes for the high-res sprites only. This is okay, since we want the physics parameters to be identical on retina display and old displays.

Creating different shapes for different screen resolutions would create different masses for each object, resulting in different physics behavior – which we don’t want! The idea is to run the same simulation on all devices and only adjust the visuals.

Setting Up the Fixture

We’re now going to create our first shape by hand. Drag the jungle/floor/grassfront.png shape onto the left pane of PhysicsEditor. When you do this, the shape will also appear in the center pane, which is the main editor area.

Note the small blue circle with the cross inside: that is the anchor point of the shape. We’re going to leave the anchor point for this shape where it is, on the bottom right corner. Later on, I’ll show you how to change the anchor point.

First use the polygon tool from the top tool bar. As soon as you click on it, a small red triangle appears. You can drag this triangle with your mouse.

The handles allow you to change the triangle’s shape. Double-clicking somewhere near a line will add an additional vertex. Double-click the vertex to remove it.

You do not have to care about things like polygon orientation or convex or concave shapes. This is all handled by PhysicsEditor.

Now create a rectangular shape for the floor. It should cover the soil but not the grass, and look something like this:

Setting Up the Parameters

Since the floor is going to be a static shape, its parameters are going to be very basic.

Density affects the weight of a body/fixture. Since the floor will not move, the value of the density is of no importance. Restitution is the degree a colliding shape will rebound from another object. For the floor, we want to keep this at 0.

Friction is the only important value here, as it determines how much the sprites will slide on the floor. Set this to 0.7.

The next items to set up are the collision bits. PhysicsEditor lets you handle these parameters quite easily – that is, without doing hex math. First, give the bits useful names by setting up the names in the text fields associated with the bits:

* floor – both the floor and the walls

* monkey – what else?

* good_objects – objects that don’t hurt the monkey

* bad_objects – objectw that hurt the monkey

Ignore the other bits – we don’t need them.

Two objects can collide only when Object B’s bit category is of a type that has been selected as a mask for Object A, and vice-versa. We will tick the Category field for the floor bit since this shape is a floor, and then the Mask field for all the bits. This enables collision between all objects and the floor.

Save the project as “shapes.pes” inside the Assets folder.

A Little Magic: Creating Shapes with the Tracer

Creating shapes by hand isn’t always fun. So let’s use the magic wand tool and let PhysicsEditor do the work for us!

Drop the following objects onto the left pane: backpack, banana, bananabunch, canteen, hat, pineapple, and statue.

Select the backpack, and click the magic wand icon on the tool bar. This will open a new window, the tracer. The tracer shows the current shape with an overlay of the traced shape.

The most important setting is the Tolerance. This value tells the tracer how exactly the polygon should match the shape. It directly influences the number of vertices the polygon will have. For example, setting the Tolerance to 20 will result in a 5-vertex polygon that no longer fits the shape; setting it to 1 creates a perfectly-matching polygon with 39 vertices.

A Tolerance value of 5 gives us an acceptable match, with about 15 vertices (the exact amount sometimes varies with the starting point of the tracer). That’s fine! Click OK to get back to the main screen. If you want you can fine-tune the shape, but don’t be too finicky!

Now set the backpack’s parameters. Set the Density to 5.00, the Restitution to 0.10 (so that the backpack bounces a little bit) and the Friction to 0.5. You might want to play around with these parameters later to get a better feel for how they work.

In the collisions section, tick the Cat. box next to bad_objects, and enable collision with all other objects by making sure all the Mask boxes are ticked.

Also drag the anchor point (the blue circle with the cross inside) to the center of the backpack shape. Alternatively, you can set the anchor point to the exact center of the shape by setting the Relative anchor point values in the right sidebar to 0.5 and 0.5.

Now that you know how to create shapes, either by hand or using the tracer, go ahead and do the remaining ones yourself! Use the parameters indicted in the table below. And don’t forget to drag the anchor point for each shape to the position indicated in the images below.

Last But Not Least: the Monkey

There’s one shape we have left to create, and that’s because it’s the most complex. Our monkey consists of quite a few animation phases (fourteen total):

The first idea that comes to mind is to add a collision polygon for each animation frame to make a perfect match for each phase of the animation. But this would be a bad idea.

First of all, the frequent exchange of fixtures would consume a good deal of CPU power. But the bigger problem is that with each new shape, the mass of the monkey would change!

This is because Box2d calculates mass based on a polygon’s area and density. If you change the polygon, the monkey will gain or lose weight! And this will lead to the physics simulation behaving erratically.

The solution is to make only one shape for the monkey, which we will try to make as good a fit as possible. To begin, drag one of the monkey shapes onto PhysicsEditor. You can rename the shape by double-clicking its name.

Now click the “+” button to add the other animation phases for the monkey. The files will show up in the combo box and you can switch between them.

Start the tracer by clicking on the magic wand icon. What you see now is that the shape does not fit the monkey anymore. This is exactly what we want.

The tracer is now in animation tracing mode. You can switch between two frame modes: intersection and union. Intersection creates the polygon only from the parts that are covered by all sprites, whereas union uses the parts that are covered by any sprite. Choose intersection.

The slider allows you to browse through the animation phases and see how the shape fits the different phases.

We will take the shape the tracer gave us just as an estimate. This is because we must split up monkey’s body into several parts. Here is what we have to build:

Sometimes polygon shapes don’t slide smoothly over other polygons and stick to each other. This is why we’re building the monkey from circle shapes.

The other reason is that using circles results in faster collision detection. The math to check if a point is inside a circle is much simpler than that for testing for a point inside a polygon. So we’ll save CPU power. We’ll delete the tracer shape after the other parts are set up.

First build the head of the monkey. Use a circle shape and make it as big as the traced version. The head must be treated differently from the other body parts, since we want the monkey to get hurt when objects land on his head from above.

To distinguish the head from the other parts, set the Id to “head.” The head’s category is monkey, and it collides with good_objects, bad_objects and floor, so make sure these are selected as masks. Set the density to 2.0, the restitution to 0.1, and the friction to 0.5.

Next build the body of the monkey from two circles, one covering the torso and one the legs. Set the Id to “body.” The body’s category is monkey, and it collides with good_objects, bad_objects and floor. Set the body’s density to 2.0, its restitution to 0.1, and its friction to 0.5.

Cycle through the different animation phases by selecting the different images from the Filename combo box. Check if the circles fit all phases. It doesn’t have to be a perfect fit, and you can come back to PhysicsEditor any time you want to tweak the collision set.

Finally, delete the polygon created with the tracer.

During the game we need to check if there are objects to the left or right to trigger the push animation. To do this, place two sensors on the left and right sides of the monkey.

Sensors are fixtures that don’t interact with other bodies and simply report collisions. To make a fixture a sensor, set isSensor.

Set the Id to “push_left” for the left sensor and “push_right” for the right one. The category is monkey, and they collide with bad_objects only – we don’t want the monkey push the floor and the bananas away!

One last thing to do: save and click Publish! When asked for a location, store the file in the Resources folder of the project as shapes.plist.

That’s it for now in PhysicsEditor. Next we move to Xcode and start coding our game!

The Xcode Project: Overview

I created the starter project using the standard Cocos2d+Box2d template that comes with Cocos2d and removing all the demo stuff. I also added the sound resources and some classes which will make your life easier when working with Box2d inside Cocos2d.

One important thing to know about this project is that all files must have the .mm extension instead of .m. This is because Box2d is C++ based, so we must use Objective-C++ for development.

Now let’s discuss the starter project in more detail:

GBox2D

First let me give you some information about GBox2D. GBox2D is an Objective-C wrapper for Box2d that I developed for my upcoming game TurtleTrigger.

These are the main classes in Gbox2D:

* GB2Engine: This class wraps the Box2d world simulation. It runs the simulation, updates all sprite positions and rotations and can iterate through all objects in the world. It is implemented as a singleton class to make access as easy as possible.

* GB2Node: This class combines a CCNode and a B2Body. It’s the glue between the physics simulation and the graphics representation inside Cococs2d. It also contains selectors for simple management of the physics object and implements a proxy to access the inner node’s data.

* GB2Sprite: This class is derived from GB2Node and specializes in using CCSprite as the inner object.

* GB2DebugDrawLayer: This is a Cocos2d layer that wraps the debug drawing. It can be added to your project like a normal layer. When added it will draw the physics shape outlines. The nice thing about it is that it detects when running on a retina display target and scales the content accordingly.

* GB2Contact: This structure will be passed as parameter to an object when a collision is detected. Both objects involved in the collision will be called for each single point of contact.

* GB2WorldContactListener: This is a C++ class that reacts to collisions in the physics simulation.

If you’re curious, go ahead and scan through these classes to get an idea of what they do. Don’t worry if you don’t understand what they’re doing – you’ll learn how to use it in the next few sections.

Collision Detection with Gbox2D

GBox2D makes collision detection a piece of cake! This is because you do not need to create one huge switch-case statement or a series of if-else cascades to detect the various possible collision combinations.

Instead, GBox2D simply uses the names of the colliding classes and calls selectors with names derived from the class names! If this sounds too abstract, take a look at the following example:

Let’s assume you have a monkey that is an object of class Monkey, and a banana that is an object of class Banana. If both objects begin to collide, the following selectors will be called by GBox2D:

If the collision is released, because the objects do not touch anymore:

The collisionA and collisionB parameters contain collision information, e.g., which objects and which fixtures took part in the collision. We will use this information to see if the monkey was hit on his head or body.

AppDelegate

Things I changed from the AppDelegate contained in the original Box2d project are as follows:

First I set the default pixel format of the frame buffer to RGBA8. That means that the game gets the full 24-bit color depth. I disabled the depth buffer, since we don’t need it.

The next important thing to set is pre-multiplied alpha. This is because we use pre-multiplied PVR images created with TexturePacker. If we don’t set pre-multiplied alpha, our images will have dark borders.

Since we’re going to use the random generator to select objects to drop, we have to seed it. The best way to do this is using time(). If you forget to seed the random generator, you will still get random numbers – but they will be the same with every start of the game.

When the initialization is done, the GameLayer scene is started:

GameLayer

The GameLayer is a simple class derived from CCLayer. In this state it consists of an empty init function…

…and a static selector that wraps the CCLayer into a CCScene to hand over to the Director:

Audio Resources

I got the theme music, tafi-maradi-loop.caf, from http://incompetech.com/

Since I knew I wanted to loop the music, I made things easier on myself and chose a theme without a vocal track.

The sounds effects for the objects were obtained from http://soundbible.com (thanks to Mike Koenig). Some of them were created using cfxr.

All sounds and the music files were converted to caf format. See Ray’s Audio 101 tutorial for more information.

If you compile and run the project, you will simply see a black screen. So let’s add some content to that screen now!

Basic Setup in Xcode

The goals for this section of the tutorial are to set up the basic game layers and backgrounds, and to set up the physics engine.

Before we dive into the code, there’s one more thing to do: add the resources we created in PhysicsEditor. Remember these?

* 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

Add the above files to the Resources folder in your Xcode project by Control-clicking on the Resources folder inside Xcode, selecting Add Files To “MonekyJump” and then selecting the files listed above from the project Resourcess folder.

Now for the code. First, we need to create a Floor class to represent the floor for the game. Add Floor.h and Floor.mm files to your project by creating a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class Floor, and make it a subclass of GB2Sprite. And don’t forget to change the extension for Floor.m to .mm once it has been created.

Floor.h simply contains this:

And Floor.mm this:

The only remarkable line of code here is the call to the initWithStaticBody selector. This makes our object a static object – one that isn’t moved by the physics engine. It initializes the body’s shape using a shape from the shapes.plist file with the name “grassfront.”

It also uses a sprite image with the name floor/grassfront.png that is taken from jungle.plist.

Why did we derive this class from GB2Sprite instead of simply using a GB2Sprite directly? The answer is GBox2D’s collision handling, which uses the name of the class to call appropriate selectors on the colliding objects. Since we want to know when something collides with the floor, the class name for the floor object must be distinguishable from other GB2Sprite objects.

The next thing to do is update the GameLayer. Add some instance variables to hold the required objects in GameLayer.h:

We will store the objects as weak references – that is, without increasing the retain count for each object.

You do not need to worry about the CCSprites being deleted. They will be added as children of the CCLayer and thus have a retain count of at least 1. This is done because otherwise we will be caught in a retain cycle and not able to free the memory allocated by these objects.

Now fill GameLayer.mm‘s init selector with content. First, load the sprite sheets:

Next, load the physics shapes into the GB2ShapeCache:

Then, set up the layers. We’ll divide the game into the following layers:

* Background layer: The jungle image

* Floor background: A single sprite with the tall grass

* Object layer: Contains all the items and the monkey

* Debug draw layer Activated as needed

* Hud layer Has the score and live energy indicators (to be added later)

Add the code to create the basic background and floor background layers to init:

Then, add the object layer. This will be a sprite batch node to speed up rendering of the objects:

And finally, the debug draw layer:

If you want to disable the debug drawing, simply comment out the second line. If enabled, the physics shapes will be drawn over the sprites, allowing you to see where collisions happen and if all shapes are properly aligned.

Next, add the floor object as the child of the object layer. Include Floor.h at the top of GameLayer.mm:

Then add the floor object at the end of init:

There isn’t anything more we have to do to add the objects to the physics world – everything else is covered inside Gbox2D!

Compile and run the project in the iPhone simulator. (By the way, the project in its current state is available in the folder called 2-BasicLayerSetup.) You should see something similar to this:

Nice – now let’s add some action to the game!

Dropping Objects

We have two related goals for this section of the tutorial: make our objects drop from the sky and add our sound effects.

Our base class for all the dropping objects will be called Object. It will handle the sound and some basic collision detection. We will derive other sub-classes later on in the tutorial from the Object class.

First, create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class Object, and make it a subclass of GB2Sprite. (And remember to change the extension for Object.m to .mm)

Object is a simple class, derived from GB2Sprite. This means that it comes with physics and graphical capabilities built in.

To make our lives easier, I’ve named the sound files in the same way as the physics sprites and the images from the sprite sheet. This allows us to simply use the object’s name to create the right shape and sound when needed. You’re welcome!

In order for this to work, we need a property named objName – objName is passed into the initWithObject selector and stored as part of the class.

RandomObject is a factory method that creates a random object and hands over the right object name upon creation.

Paste this code into Object.h:

Let’s now go to Object.mm. Start with some needed imports and with synthesizing the objName property.

GMath.h contains some helper functions – for example, gFloatRand, a ranged floating point random number generation.

Next, add the init selector and instantiate the physics object. You can use the object’s name as it is to instantiate the physics shape. For the sprite frame name, you’ll need to add the folder’s name (which is object) and the .png extension. Store the objName in the property – we’ll need it during collision detection to play the sound effect.

In the dealloc selector, simply release the objName property and call super dealloc:

The next thing to add is our static factory method, which will simply create a random object. I decided to use a simple switch-case statement for this. The reason is that we’ll need to create special classes for banana and banana bunch later on. These two objects get only one case entry, while the other objects get three each, so that there’s a higher probability they appear more often.

Switch case constructs are quite efficient (usually implemented by the compiler using a jump table). You might save some CPU cycles by using an array with the names instead, but since the routine will be called once in a second, our way is fine.

Finally, add the closing end to the file:

Now switch to GameLayer.h and add a forward declaration for the object class, directly after the #import statement:

Add these new members to the GameLayer class:

Switch to GameLayer.mm and add an import of Object.h to the imports at the start of the file. Also import GMath.h:

Initialize the new variables at the end of the init selector, and schedule an update selector with every frame update:

The last line will call a selector called “update” for every frame. The parameter to this selector is the time elapsed since the selector was last called. Add the update method right after init:

Let’s go through the above code section by section.

1. This section simply reduces the time interval since update was last called from nextDrop. If nextDrop falls below 0, it’s time to create a new item to drop.

2. If the nextDrop timer runs out, this section checks if there is already an object stored in nextObject.

3. If so, it’s set to to active in here. Setting the object to active gives the physics engine control over the object.

4. This section sets the time until the next drop to the current drop delay, and reduces the drop delay by 2%, making the game a bit more difficult with each dropped item.

5. This section creates a new object to drop using our factory method in Object – randomObject – and sets the object to inactive, which keeps the object from dropping and participating in the physics simulation.

6. This section gives the object a random position. The screen is 480pt wide (Cocos2d uses points as the base unit, with 1 pt equivalent to 1 pixel on “old” devices and 2 pixels on retina display devices). The code ensures that the object’s position is somewhere between 40 and 440 points. The section also sets the y-coordinate to 400 for the starting position so that the object will start offscreen from the top of the screen. The b2Vec2FromCC method is used to create a box2db2Vec2 from the point coordinates. B2Vec2FromCC transforms Cocos2d’s points to Box2d’s meters-based values.

7. Finally, this section adds the object to the object layer.

Compile and run! You should see something similar to the following but of course, with different items. The items look a bit blurry since debug drawing is still enabled:

Disable the debug draw layer as follows by commenting out the relevant line in GameLayer.mm:

Now your game should look much nicer:

Notice how the items can tumble out-of-screen to the left and right? The goal of the game is to let the items pile up, so we need to add a wall on each side of the screen.

To do this, simply create two new GB2Node objects. They will be out of the screen to the left and right.

Since GB2Nodes add themselves to the current physics simulation, you don’t need to add them manually. They are not represented graphically, so creating them will suffice.

Add these lines to the init in GameLayer.mm, right after the floor layer:

Compile and run. See how the objects are now kept inside the screen by our walls?

This looks nice, but there is still something missing. I think the objects should make some noise when colliding with each other. Don’t you agree?

I don’t want the objects to make sounds all the time, just when they hit each other at a decent speed. So we’ll check the object’s velocity, and play a sound only when it collides at a fast enough speed.

Add this code to Object.mm:

The above method must be named beginContactWithObject so that it will be automatically called by GBox2D each time two objects collide.

The linearVelocity method gives us the velocity of the object. Calling Length or LengthSquared on the object delivers the velocity’s value. I prefer using LengthSquared when comparing with a constant value, since it doesn’t require calculating the square root of the value.

We’ll play the sound with a call to SimpleAudioEngine’s playEffect method. The first parameter is the name of the audio file.

Remember that to make our lives easier I gave the sound effects the same name as the objects and sprites. So you can use the objName we stored earlier to get the right sound file. Use NSString to append .caf to the name.

Add some variation to the pitch by using gFloatRand with 0.8 and 1.2. This will play the sound with some pitch variation. It would be boring if every object made the same sound all the time.

The last trick to apply is to pan the sound’s source to the position of the object. Pan allows values between -1.0 and 1.0. The object’s x position (in points) will be somewhere between 0 and 480, so subtracting 240 and dividing by 240 will deliver that range.

If you want objects to make a sound when they hit the floor without rewriting a lot of the code, add the following method which forwards the object-floor collision to the object-object collision to Object.mm:

Compile, run and see how the objects drop and make a sound upon collision.

Ah, but there is one more thing I don’t like about our game right now. The first item drops and then pauses in mid-air while the sound engine is initialized.

This won’t be a problem once we add the theme music, since the music will initialize the sound engine right away. But if you want to fix this now, first add an import statement to the top of GameLayer.mm:

Then, add a call to SimpleAudioEngine’s shared object inside GameLayer’s init selector:

The above implementation plays the same basic sound (with pitch variations) for all falling object collisions. If you’re ambitious, you could play different sounds depending upon the types of objects colliding. That is, play one sound when a canteen hits a canteen, and another when a banana hits a canteen…

Another way to improve this code would be to vary the gain of the effect with the speed of the collision.

Where to Go From Here?

If you don’t have it already, here is all of the source code for this tutorial series.

You’ve now reached the end of Part One of the MonkeyJump tutorial! The project in its current form is available in the source code zip in the folder called 3-DroppingObjects.

Stay tuned for Part Two, where we’ll add our hero (the monkey), make him move and interact with objects – even make him teleport! Plus injury and death, life-giving bananas, a drop warning light… there’s lots of exciting stuff ahead.

Before we get there, let me know if you have any questions or comments about what we’ve done so far. I’ll be following the discussions in the forum below.(Source:raywenderlich


上一篇:

下一篇: