如何使用Chipmunk物理引擎制作简单的iPhone游戏
(2011-10-28 18:38:41)标签:appleiosiphone移动互联网游戏cocos2dit |
如何使用Chipmunk物理引擎制作简单的iPhone游戏
原文在此:http://www.raywenderlich.com/3128/how-to-create-a-simple-iphone-game-with-chipmunk-physics-tutorial
相关资源:http://d1xzuxjlafny7l.cloudfront.net/downloads/CatNapResources.zip
示例代码:http://d1xzuxjlafny7l.cloudfront.net/downloads/CatNap.zip
首先说一下我们要制作的游戏主题“猫咪打呼噜”,这是一款使用Chipmunk physics物理引擎制作的简单的解谜游戏。
故事的背景是这样的,猫咪忙碌了一天,非常困倦,很想去睡觉,但面前却有无数的障碍阻挡着自己。
玩家要做的是触碰那些阻挡在猫咪身前的砖块,摧毁它们,从而让猫咪有机会躺在床上。但千万要注意别把猫咪弄醒了!
我们将使用Chipmunk引擎来制作这款游戏,并详细讲解游戏的制作过程。
本教程假定大家已经了解了Cocos2D的基础知识,并了解精灵,动作,精灵表单和动画等概念。如果之前没学过,不妨看看博客上之前的Cocos2D漫游指南系列文章。
什么是Chipmunk?
Chipmunk是使用C 语言开发的游戏物理引擎,是Cocos2D的一部分。使用Chipmunk,我们可以让游戏中的物体表现得更接近真实物体-受到重力影响,撞到其它物体,弹跳等等。
Chipmunk由Scott Lembcke(http://wiki.slembcke.net/main/published/HomePage)开发。最近它是基于Erin Catto(https://profiles.google.com/u/0/erincatto)的Box2D(http://www.box2d.org/)物理引擎(同样包含在Cocos2D中),但目前已有了很大的区别。
Chipmunk和Box2D之间还是存在着一定的差异,但两个都是相当不错的物理引擎,使用哪一个都可以帮你制作出色的游戏。
Chipmunk是如何工作的?
Chipmunk使用“虚拟Chipmunk空间”来模拟物体的物理行为。开发者要做的工作如下:
1. 将物体添加到空间中。 例如:代表动物,花朵或玩具布偶的形状。
2.告诉chipmunk有哪些力施加在这些物体上,比如:重力,风力,外星引力光束等。
3.不时让chipmunk更新物理模拟,比如:chipmunk可能会计算某个动物比刚才向下落了一点距离,一朵花在风中轻轻弯腰,等等。
4.根据物理模拟情况来更新“真实的”Cocos2d世界。比如:将动物,花朵或布偶的位置设置在chipmunk中虚拟物体所在的地方。
最最重要的是要记住,Chipmunk的世界和Cocos2D的世界是完全不同的。
物体和形状
在我们学习具体的编码之前,还有一个one more thing要注意的:Chipmunk物体和形状的概念。
一个Chipmunkbody代表了Chipmunk虚拟空间中的一个物体。它可能会包含一个或多个Chipmunk形状,从而定义了该物体的几何形状,如下图所示:
上图显示了我们将要用作猫咪睡床的Chipmunk body(物体)。它包含了三个Chipmunk形状,其中一个代表床的左侧,另一个代表床的右侧,而最后一个则代表了床的底部。
Chipmunk的body(物体)可以分为两类:
1.Dynamic bodies(动态物体):也即可以移动的物体,是我们在游戏开发中最经常用到的
2.Static bodies(静态物体):也即不能移动的物体,比如地面或其它不能移动的永久性夹具
对于每个Chipmunk物体,我们都可以指定其mass(质量)。一个形状的质量越大,则越难以移动。
当我们创建形状的时候,可以指定是boxes(盒子),circles(圆形),polygons(多边形),或segments(线段,也即带有厚度的直线)中的哪一种。对于每种形状,我们可以设置以下几个属性:
1.elasticity(弹性):代表了物体的弹性。如果设置为0,则完全没有弹性。如果设置为1,则属于完全弹性。如果设置为高于1的数值,则会比落下时弹的更高。
2.friction(摩擦力):代表一个物体表面的光滑程度。如果设置为0,则表示完全光滑。如果设置为1或更高,则物体表面非常粗糙。
Hello,Chipmunk!
看了这么多的理论知识,该实际操作一下了!
打开Xcode,点击File\New\New Project,选择iOS\cocos2d\cocos2d_chipmunk模板,点击Next。将项目命名为CatNap,并选择一个目录保存。
在继续之前,先让我们编译运行一下项目。此时会在模拟器中看到一个奇怪的光头哥:
光头哥到底是何方神圣,以及为何他的大名叫Grossini,我们不得而知。
这个小小的示例显示了Chipmunk的妙处。当我们触碰屏幕的时候,就会有更多的光头哥出现在屏幕中(是不是有点像黑客帝国的Smith探员?),他们之间会发生碰撞。
如果你是在设备上进行测试,那么这些光头哥们还会受到重力和加速计的影响。
如果感兴趣,大家可以自己好好研究下这个默认的示例代码。但这里我们打算从头开始。当然,不是因为我太变态,而是从零开始学会如何设置对今后的游戏开发会很有帮助。
但是,别担心,这些步骤没有那么复杂,现在放宽心,学着和下面的这哥们一样开心点:
好吧,哈哈大笑过后,让我们从头开始吧!
从零开始
点击File\New\New File,选择iOS\Cocoa Touch\Objective-C class,点击Next。选择Subclass of CCLayer,点击Next,命名为ActionLayer.m,然后点击Finish。
然后把本教程中用到的资源文件拖到Resources中。
打开ActionLayer.h,使用下面的代码替代其中的内容:
#import"cocos2d.h"
@interfaceActionLayer : CCLayer {
}
+(id)scene;
@end
这段代码只是创建了一个标准的CCLayer。后续我们会加入一些Chipmunk代码,但现在暂时留白。
切换到ActionLayer.m,使用以下代码替代其中的内容:
#import"ActionLayer.h"
@implementationActionLaye
+(id)scene{
CCScene *scene = [CCScenenode];
ActionLayer *layer = [ActionLayernode];
return scene;
}
-(id)init{
if((self = [superinit])){
CGSizewinSize = [CCDirectorsharedDirector
CCLabelBMFont *label = [CCLabelBMFontlabelWithSt
label.position = ccp(winSize.width/2,winSize.height/2);
returnself;
}
@end
以上代码完成了以下工作:
1.使用scene类方法来创建并初始化CCScene,并将ActionLayer层添加为场景的子节点
2.init方法只是在屏幕中间放了一个标签,上面写着”hello,chipmunk!”
需要注意的是,这里用到了CCLabelBMFont,会需要提供一个字体文件。而这个字体文件可以使用Glyph Designer或Hiero来创建。
一切就绪后,打开AppDelegate.m,做出以下修改:
//在文件顶部添加以下代码:
#import"ActionLayer.h"
//在applicationDidFinishLaun
此时编译运行程序,会看到下面的画面:
到目前为止,我们所作的一切和Chipmunk还没有任何关系。接下来就好玩了!
创建一个基本的Chipmunk 场景
为了创建chipmunk场景,通常会通过以下步骤进行:
1.初始化Chipmunk
第一步就是初始化Chipmunk类库。在整个游戏中只需要进行一次就好了,所以通常我们可以在AppDelegate.m中的applicationDidFinishLaun
让我们试试看。
打开AppDelegate.m,对代码做以下修改:
//在文件顶部添加以下代码:
#import"chipmunk.h"
//在applicationDidFinishLaun
cpInitChipmunk();
这就搞定了!以上操作可能是学习Chipmunk中最简单的一步了。需要注意的是以上函数只能调用一次!否则会遇到内存泄露的问题。
2.创建Chipmunk空间
接下来就是创建一个虚拟的Chipmunk空间,以便在其中运行物理模拟。
代表Chipmunk空间的对象是cpSpace,我们只需创建并初始化它即可。
切换到ActionLayer.h文件,并对代码做以下调整:
//在文件顶部添加:
#import"chipmunk.h"
//在声明部分添加:
cpSpace *space;
以上代码只是导入了chipmunk的头文件,并声明了一个实例变量,用于记录Chipmunk空间,因为后续我们将频繁用到它!
然后切换到ActionLayer.m,并对代码做以下调整:
-(void)createSpace{
space = cpSpaceNew();
space->gravity = ccp(0,-750);
cpSpaceResizeStaticHash(space, 400, 200);
cpSpaceResizeActiveHash(space, 200, 200);
}
//在init方法中注释那些用于创建标签的代码,并添加以下代码:
如你所想,以上代码中最重要的就是createSpace这个方法。该方法中,第一行调用cpSpaceNew()为Chipmunk虚拟空间创建了一个新的对象,并将其存储在space这个实例变量中。
第二行代码将chipmunk世界的重力设置为x轴是0,而y轴则向下。至于具体的数值是多少,可能就看你自己的感觉了。
最后两行代码分别创建了Chipmunk的静态和活跃哈希散列。在Chipmunk中用它来加速碰撞检测。
这里简单的将Chipmunk空间划分成网格。如果两个物体在不同的网格中,Chipmunk会立即知道物体没有发生碰撞,从而无需进行下一步的数学运算。
因此以上代码的作用就是,设置网格的大小(第二个参数),以及网格的数量(第三个参数)。建议将网格的大小设置得比一般的物体要大,而网格的数量则至少是游戏中物体数量的十倍。
对于简单的小游戏来说当然无所谓,但是考虑到游戏的性能,必须把这个放在心上。
3.(可选)添加“地面”
对很多游戏来说,有必要添加一些物体到Chipmunk空间中来代表关卡中的地面。
例如,在我们要制作的这个小游戏中,我们创建了一个直线段,从左下角到右下角。这样就相当于设置了地面物体。今后所创建的物体落下来会撞到地面上,而不是跌出屏幕。
为了添加地面物体,需要对ActionLayer.m做以下调整:
//添加一个方法用于创建地面物体
-(void)createGround{
//1
CGSizewinSize = [CCDirectorsharedDirector
CGPointlowerLeft = ccp(0,0);
CGPointlowerRight = ccp(winSize.width,0);
//2
cpBody *groundBody = cpBodyNewStatic();
//3
float radius = 10.0;
cpShape *groundShape = cpSegmentShapeNew(groundBody, lowerLeft, lowerRight, radius);
//4
groundShape->e = 0.5;// elasticity
groundShape->u = 1.0; //friction
//5
cpSpaceAddShape(space, groundShape);
}
在init方法中,在调用createSpace的方法后添加以下代码:
到目前为止,这是我们首次看到如何向Chipmunk的场景中添加物体。因此有必要一步步解释:
1.因为我们将把地面物体设置为从屏幕左下角到右下角的直线段,因此需要获得相关的坐标信息,以便后续使用。
2.创建一个新的Chipmunk物体,将其设置为静态物体,因为我们不希望地面可以移动。通常情况下我们需要将物体添加到场景中,但是对静态物体就没这个必要。事实上,如果特意添加,那么动态物体会直接跌出地面!
3.创建一个直线段形状,并将其关联到所创建的物体上。
4.设置弹性系数和摩擦力系数
5.将该形状添加到Chipmunk的虚拟空间中。
4.(可选)添加物体/形状
接下来,我们将创建一个辅助方法,从而把动态(可移动)的Chipmunk物体添加到场景中。并调用它来创建几个盒子。
在ActionLayer.m中添加以下方法:
-(void)createBoxAtLocation:(CGPoint)location{
floatboxSize = 60.0;
float mass = 1.0;
cpBody *body = cpBodyNew(mass, cpMomentForBox(mass, boxSize, boxSize));
body->p = location;
cpSpaceAddBody(space, body);
cpShape *shape = cpBoxShapeNew(body, boxSize, boxSize);
shape->e = 1.0;
shape->u = 1.0;
cpSpaceAddShape(space, shape);
}
然后在init方法中,在createGround这行代码的后面添加以下代码:
以上代码和第三步中的代码有一些类似之处,但有两个主要的区别。
第一个区别是,我们不是使用cpBodyNewStatic来创建物体,而是使用cpBodyNew来创建动态(可移动的)物体。
当使用cpBodyNew方法来创建物体的时候,需要传入两个参数,分别是质量和”moment of inertia(转动惯量,又称惯性矩)”。我们都知道质量是什么,那么什么是转动惯量?它的作用是决定物体旋转的难易程度。为了计算出惯性矩的参数,我们调用了一个辅助方法cpMomentForBox,它会根据形状类型和质量等来计算惯性矩。
第二个区别是,这里我们创建了一个盒子形状,而不是线段,所以调用了cpBoxShapeNew方法,而不是cgSegmentShapeNew。
在定义完这个辅助方法后,我们就可以在init方法中两次调用该方法,来创建两个不同的物体,并添加到屏幕中。
5.在update循环中设置步进同步
在创建完基本的Chipmunk世界后,我们需要每帧运行一次仿真。这里先说一种最简单的方法。
在init方法中,在createBoxAtLocation代码之后添加以下代码:
然后再添加一个新的方法:
-(void)update:(ccTime)dt{
cpSpaceStep(space, dt);
}
以上方法只是简单的调用cpSpaceStep,从而让chipmunk每帧都运行一次物理仿真。
6.(可选)启用debug draw
完成了以上的步骤之中,虚拟世界实际上已经在运行了,但我们不会在屏幕上看到任何东西。
为了让chipmunk虚拟空间中的物体视觉化呈现出来,我们需要使用一些类将Chipmunk的物体绘制到屏幕之中。它会遍历每个Chipmunk物体,并调用OpenGL指令在屏幕中绘制该物体的形象。
让我们试一下吧。在为本教程所提供的资源中,有一个子目录”Helper Code”,其中有drawSpace.c和drawSpace.h两个文件,将这两个文件拖到项目中,确保选中”copy items into….”,然后点击Finish。
好吧,如果你这时手痒编译一下,会发现提示有200个错误,红色的error让人心惊胆战。
在Xcode4中,选中drawSpace.c,在工具栏View部分点击第三个tab选项卡,从而可以看到Utilities窗口。在该面板下面选择”Show the File Inspector”,然后在Identity and Type下面,会看到File Name和File Type。然后将File Type 中的Default-C Source 更改为Objective-C source,如下图所示。
然后切换到ActionLayer.m,在文件顶部添加以下代码:
#import"drawSpace.h"
然后在ActionLayer.m中添加以下方法
-(void)draw{
drawSpaceOptions options ={
0, //drawHas
0,//drawBBs,
1,//drawShapes
4.0,//collisionPointSize
4.0,//bodyPointSize,
2.0//lineThickness
drawSpace(space, &options);
}
那么首先就是告诉这个函数我们需要绘制什么。这里我们要求不要绘制chipmunk hash,不要绘制bounding boxes,但是要绘制形状本身。同时我们还设置了point size和限度厚度等。
编译运行项目,如果没有出现低级错误,应该会看到屏幕上有几个物体跌落到地面上,稍微弹起,最后停下来:
很不幸的是,一旦这些盒子落下来,就基本上停在那里不动了。如果我们可以用手拖动这些物体该多好!说干就干!
7.(可选)添加鼠标关节
在本教程提供的资源文件夹中找到Helper Code这个文件夹,然后把cpMouse.c和cpMouse.h拖到项目中,确保选中”copy items into….”,然后点击Finish。
然后和刚才一样把file type更改为Objective-C source。
接下来打开ActionLayer.h,并对代码做出以下修改:
在文件的顶部添加以下代码:
#import"cpMouse.h"
然后在类声明中添加以下代码:
cpMouse *mouse;
以上代码导入了头文件,并定义了一个实例变量用于保存鼠标节点。
接下来切换到ActionLayer.m,并对代码做以下修改:
在init方法中,在调用scheduleUpdate方法后添加以下代码:
mouse = cpMouseNew(space);
self.isTouchEnabled = YES;
接下来添加以下几个方法:
-(void)registerWithTouchDispatc
}
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
CGPointtouchLocation = [selfconvertTouchToNodeSp
cpMouseGrab(mouse, touchLocation, false);
returnYES;
}
-(void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event{
CGPointtouchLocation = [selfconvertTouchToNodeSp
cpMouseMove(mouse, touchLocation);
}
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{
cpMouseRelease(mouse);
}
-(void)dealloc{
cpMouseFree(mouse);
cpSpaceFree(space);
}
以上代码应该不难看懂,首先我们启用了对触摸事件的处理机制,然后调用了几个方法来初始化鼠标节点,当触摸开始时抓住东西,当触摸点移动时也移动这个东西,当触摸结束或取消时释放鼠标节点。
编译运行代码,此时就可以移动屏幕中的物体了。
使用精灵图片来装饰物体
现在我们有了个基本的Chipmunk场景,可以在其中加入一些艺术的成分了。
首先把项目资源中的SpriteSheets文件夹拖到Xcode项目中,确保选中”copy items into …”,点击Finish。
Sprite sheet中的图片如下图所示:
首先我们需要创建几个Chipmunk物体和盒子形状来代表猫咪,橙色砖块和黑色砖块。
那么我们如何将Chipmunk物体和Cocos2d精灵绑定在一起呢?我们只需要在游戏每一帧将精灵的坐标/角度和Chipmunk物体的坐标/角度保持一致即可。
那么我们如何知道精灵对应的是哪个chipmunk物体呢?有几种方法可供使用,我们采取的是下面这种:
1.创建一个CCSprite的子类(CPSprite),代表Chipmunk精灵子类
2.让这个子类来保存相关的信息,用于判断精灵所对应的Chipmunk物体
3.在CPSprite类中创建一个update方法,并将其位置/角度设置为和Chipmunk物体的位置/角度相同。
4.对于层中的每个CPSprite,逐帧调用update方法。
理论知识具备了,让我们动手试试吧。
点击File\New File,选择iOS\Cocoa Touch\Objective-C class,点击Next。选择Subclass of CCSprite,点击Next,将其命名为CPSprite.m,然后点击Save。
打开CPSprite.h,使用以下的代码替代其中的内容:
#import"cocos2d.h"
#import"chipmunk.h"
@interfaceCPSprite:CCSprite{
cpBody *body;
cpShape *shape;
cpSpace *space;
BOOLcanBeDestroyed;
}
@property(assign)cpBody *body;
-(id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location spriteFrameName:(NSString *)spriteFrameName;
-(void)update;
-(void)createBodyAtLocation:(CGPoint)location;
-(void)destroy;
@end
在以上的代码中,首先我们导入了cocos2d和chipmunk的头文件。然后我们创建了几个实例变量,用于记录精灵所对应的chipmunk物体,以及chipmunk空间和形状。同时我们定义了一个BOOL变量,用于记录当玩家触摸该物体时能否将其销毁的状态。
接下来定义了该对象的初始化方法,并预先定义了一个方法,根据精灵所对应的Chipmunk物体来更新精灵的位置/旋转等信息。我们还定义了一个方法,可以使用精灵的大小来创建Chipmunk物体/形状。最后定义了一个方法用于销毁chipmunk物体/形状及其精灵。
完成了以上的定义,现在让我们来实现这些方法。
切换到CPSprite.m文件,并使用以下的代码来替代其中的内容;
#import"CPSprite.h"
@implementationCPSprite
@synthesize body;
-(void)update{
self.position = body->p;
self.rotation = CC_RADIANS_TO_DEGREES(-1* body->a);
}
-(void)createBodyAtLocation:(CGPoint)location{
float mass = 1.0;
body = cpBodyNew(mass, cpMomentForBox(mass, self.contentSize.width,self.contentSize.height));
body->p = location;
body->data = self;
cpSpaceAddBody(space, body);
shape = cpBoxShapeNew(body, self.contentSize.width, self.contentSize.height);
shape->e = 0.3;
shape->u = 1.0;
shape->data = self;
cpSpaceAddShape(space, shape);
}
-(id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location spriteFrameName:(NSString *)spriteFrameName{
if((self = [superinitWithSpriteFrame
space = theSpace;
canBeDestroyed =YES;
returnself;
}
-(void)destroy{
if(!canBeDestroyed) return;
cpSpaceRemoveBody(space, body);
cpSpaceRemoveShape(space, shape);
}
@end
在以上代码中,update方法最简单,只是将精灵的位置设置为和chipmunk物体的位置相同。然后把旋转角度也设为相同,但经过了一个小小的转换(因为cocos2d使用角度而不是弧度,而且方向上使用顺时针而非逆时针)。
createBodyAtLocation方法和之前实现的createBoxAtLocation方法非常类似。首先我们创建了一个物体,然后创建了物体上的盒子形状。唯一的区别在于,这里将盒子的大小设置为与精灵的大小相同(使用contentSize)。
此外,还有一个区别是,将物体的数据指针和形状都设置为和自身(精灵)相同。这样以来,当我们使用chipmunk物体或形状的时候,就可以通过数据指针来获取所对应的精灵。
initWithSpace方法首先调用了其父类(CCSprite)的initWithSpriteFrameName方法,然后设置了实例变量,并调用创建物体的方法。
Destroy方法也比较简单了,调用了cpSpaceRemoveBody和cpSpaceRemoveShape方法来销毁物体和形状,同时调用removeFromParentAndClean
现在我们的类已经准备好了,接下来就可以使用它将猫咪和砖块精灵添加到场景中。但为了让代码今后具有可扩展性,我们可以创建以下的子类。
Sprites类的子类
创建子类很简单,让我们快速过一遍。打开File\New\New File,选择iOS\Cocoa Touch\Objective-C class,点击Next。在Subclass of的后面输入CPSprite ,点击Next,命名为CatSprite.m,点击Save。
打开CatSprite.h,使用以下代码替代其中的内容:
#import"CPSprite.h"
@interfaceCatSprite : CPSprite{
}
-(id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location;
@end
然后打开CatSprite.m,使用以下代码替代其中的内容:
#import"CatSprite.h"
@implementationCatSprite
-(id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location{
if((self = [superinitWithSpace:theSpacelocation:locationspriteFrameName:@"cat_sleepy.png"])){
canBeDestroyed = NO;
returnself;
}
@end
以上代码没什么特别的,我们使用一个精灵图片来初始化了猫咪精灵,并设置canBeDestroyed属性为NO.
然后再次点击File\New\New File,选择iOS\Cocoa Touch\Objective-C class,点击Next。在Subclass of 之后输入CPSprite,点击Next,命名为SmallBlockSprite.m,并点击Save。
打开SmallBlockSprite.h文件,并使用以下代码替代其中的内容:
#import"CPSprite.h"
@interfaceSmallBlockSprit
}
-(id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location;
@end
然后切换到SmallBlockSprite.m文件,使用以下代码替代其中的内容:
#import"SmallBlockSprite.h"
@implementationSmallBlock
-(id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location{
if((self = [superinitWithSpace:theSpacelocation:locationspriteFrameName:@"block_1.png"])){
returnself;
}
@end
上面的代码就不解释了,几乎完全相同,只是替换了精灵图片。
最后还需要一个文件。再次点击File\New\New File,选择iOS\Cocoa Touch\Objective-C class,点击Next。在Subclass of部分输入CPSprite,点击Next,命名为LargeBlockSprite.m,然后点击Save。
使用以下代码替代LargeBlockSprite.h中的内容:
#import"CPSprite.h"
@interfaceLargeBlockSprit
}
-(id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location;
@end
使用以下代码替代LargeBlockSprite.m中的内容:
#import"LargeBlockSprite.h"
@implementationLargeBlock
-(id)initWithSpace:(cpSpace *)theSpace location:(CGPoint)location{
if((self = [superinitWithSpace:theSpacelocation:locationspriteFrameName:@"block_2.png"])){
returnself;
}
@end
好了,三个精灵类都有了,现在可以使用它们来创建几个实例,并将其添加到层中了。
切换到ActionLayer.h文件,并对代码做出以下修改:
//在文件的顶部添加:
#import"CatSprite.h"
#import"SmallBlockSprite.h"
#import"LargeBlockSprite.h"
在类的声明部分添加:
CCSpriteBatchNode *batchNode;
CatSprite *cat;
以上代码导入了要使用的精灵类头文件,并添加了一个实例变量用于保存精灵表单,同时定义了一个猫咪精灵对象。
接下来让我们切换到ActionLayer.m,并在init方法的最后添加以下代码(在isTouchEnabled = YES之后)
batchNode = [CCSpriteBatchNodebatchNo
cat = [[[CatSpritealloc]initWithSpace:spacelocation:ccp(245,217)] autorelease];
SmallBlockSprite *block1a = [[[SmallBlockSpritealloc]initWithSpace:spacelocation:ccp(213,47)]autorelease];
SmallBlockSprite *block1b = [[[SmallBlockSpritealloc]initWithSpace:spacelocation:ccp(272,59)]autorelease];
SmallBlockSprite *block1c = [[[SmallBlockSpritealloc]initWithSpace:spacelocation:ccp(267,158)] autorelease];
LargeBlockSprite *block2a = [[[LargeBlockSpritealloc]initWithSpace:spacelocation:ccp(270,102)]autorelease];
LargeBlockSprite *block2b = [[[LargeBlockSpritealloc]initWithSpace:spacelocation:ccp(223,139)]autorelease];
LargeBlockSprite *block2c = [[[LargeBlockSpritealloc]initWithSpace:spacelocation:ccp(214,85)]autorelease];
以上代码创建了一个精灵表单,并将精灵帧加载入缓存之中。然后我们使用刚才定义的子类来创建了几个精灵。
当然,为了让精灵和Chipmunk物体的状态符合,我们需要在每帧手动调用update方法。在update方法的最后添加以下代码:
for(CPSprite *sprite inbatchNode.children){
编译运行项目,可以看到你那可爱的猫咪坐在几个砖头上!
音效和破坏!
到现在为止,除了使用鼠标关节来移动这些砖头,我们还没法通过触摸来摧毁它们。此外,既然是游戏,不来点音效也说不过去!当然,我们还得需要一个背景。
将Sounds文件夹拖到Xcode的Resources中,确保选中”copy items into….”,点击Finish。
这个确保选中已经重复了一万遍,但还得重复,直到彻底形成了条件反射为止。
接下来打开ActionLayer.m文件,并注释ccTouchBegan/Moved/Ended/Cancelled等方法中和cpMouseGrap,cpMouseMove,cpMouseRelease这些鼠标关节相关的代码,因为后续将不再使用鼠标关节。
完成注释之后,对ActionLayer.m的代码做出以下调整:
//在文件的顶部添加:
#import"SimpleAudioEngine.h"
然后在ccTouchBegan方法中,在刚才注释的cpMouseGrab的代码之后添加以下代码:
cpShape *shape = cpSpacePointQueryFirst(space, touchLocation, GRABABLE_MASK_BIT, 0);
if(shape){
CPSprite *sprite = (CPSprite *)shape->data;
以上代码的作用是,使用chipmunk内置的cpSpacePointQueryFirst这个辅助方法来检查是否形状位于触摸点。然后使用数据指针来获取和该形状相关的CPSprite精灵对象。
同时我们还加了一行代码用来播放音效。
接着让我们再来添加背景音乐和背景。
在init方法的底部添加以下代码:
CCSprite *background = [CCSpritespriteWithFile:@"catnap_bg.png"];
background.anchorPoint = CGPointZero;
编译运行游戏,一个简单的小游戏就成型了。
多边形
Cat Nap小游戏看起来还不错,但一个很大的问题就是猫咪和它的形状有点不太匹配。我们使用了最简单的盒子来制作其形状,但这样就留下了大量的空白区域,显得很不协调。
在chipmunk中,除了使用最简单的盒子,圆形或线段,还可以使用多边形。我们只需告诉chipmunk多边形中每个顶点的位置即可!
但是如何获取这些顶点的位置呢?比较傻的方法是在图片编辑器中手动获取,但我们也可以使用第三方的工具,比如Johannes Fahrenkrug开发的Vertex Helper(https://github.com/jfahrenkrug/VertexHelper)。我们既可以直接通过github页面下载,也可以在mac app store中购买。
要注意的是,通过github下载的是项目文档,因此需要编译运行,画面如下;
将cat_sleepy.png拖到Vertex Helper中,然后做出以下设置:
1.将Rows/Cols设置为1/1
2.将Type设置为chipmunk
3.将style设置为Initialization
4.在顶部工具栏中点击Edit Mode。
然后点击cat四周来定义所需的顶点。有几点需要特别注意的事项:
1.必须顺时针点选顶点。
2.顶点数目尽量少(这里只定义了4个)
3.无需手动连接起点和终点,vertex helper会自动帮你完成这一工作。(并使用一个灰线标示)
4.多边形必须是凸形的。也就是说不能有大于180度的内角,如下图所示:
下图是我给猫咪定义的顶点(可能你所设置的顶点会有所区别)
完成之后,将上面的代码拷贝,并放置到CatSprite.m的一个新方法中。
仔细观察会发现,创造多边形和盒子形状差不多,除了我们使用cpMomentForPoly和cpPolyShapeNew这两个新方法。
编译运行游戏,似乎像那么回事了,但是形状还是太大了!
这是因为我们使用的是retina尺寸的图片,因此在像素值上是所需的两倍。所以只需在刚才的顶点坐标中除以2即可,如下:
intnum =4;
CGPointverts[] = {
cpv(-31.5f/2, 69.5f/2),
cpv(41.5f/2,66.5f/2),
cpv(40.5f/2,-69.5f/2),
cpv(-55.5f/2,-70.5f/2)
编译运行游戏,现在一切正常了!