quick cocos2d x中怎么将进程从HelloWorldLayer切换到Myquick cocos2d xClass

[iTyran原创] cocos2d中使用chipmunk做碰撞检测
查看: 15473|
评论: |原作者: u0u0
摘要: 我们知道cocos2d本身不包含物理引擎,但是它集成了2个开源物理引擎可供我们选择:box2d和chipmunk
[iTyran原创] cocos2d中使用chipmunk做碰撞检测
作者:u0u0- iTyran
版权声明:泰然原创作品,谢绝转载!否则将追究法律责任。
我们知道cocos2d本身不包含物理引擎,但是它集成了2个开源物理引擎可供我们选择:box2d和chipmunk。对于一般的正规矩形精灵的碰撞检测,可以简单的使用Core Graphics提供的CGRectIntersectsRect接口实现。其他情况要想减少工作量,最好的方法就是借助物理引擎。
本篇教程实现一个菱形和三角形的碰撞检测,足以演示基本的chipmunk用法。
Demo基于cocos2d 2.0 beta,chipmunk6.0.2.
教程截图:
Demo下载地址:
1 Demo流程
首先创建一个三角形的精灵,我把它命名为emery,它静止在屏幕上。每次点击屏幕,从左边固定位置会发射出一个菱形的bullet,bullet匀速直线运动碰到emery后消失。Reset重新开新游戏。
2 创建一个chipmunk的工程
首先需要安装cocos2d 2.0 beta,之所以选择2.0,有以下2个原因:cocos2d 2.0使用OpenGL ES2.0,早日过渡到OpenGL ES 2.0是大势所趋(1.x和2.0的区别,详见)。目前只有cocos2d 2.0才集成了chipmunk 6,而chipmunk 6相对于5接口上简化了很多,更易使用。
创建工程的时候选择cocos2d_chipmunk模板,
这样我们就得到一个包含了chipmunk的工程,并自带了测试代码。
而HelloWorldLayer将是我们的主战场,修改它的实现达到前述Demo效果。
3 chipmunk基本概念
space:物理空间,可容纳body, shape,joint。body:刚体,可被赋予shape。刚体具有质量,转动惯量,位置,线性速度,加速度,角度,角速度,角加速度等属性。刚体之间可通过joint连接。shape:决定刚体的碰撞外形。一个刚体上可覆盖上多个shape,同属于一个刚体的shape不会互相发生碰撞。joint: 用于连接刚体。本篇教程不涉及joint,也不涉及一个body多个shape。也就是一个精灵对于一个刚体有一个外形。有点绕口~。~关于space,默认有一个staticBody属性,staticBody是在整个物理检测中永远保持禁止不动的刚体。这通常用来把屏幕四周设置为墙体,以避免精灵飞出到屏幕外面。接下来我们看代码实现。
4 chipmunk系统初始化
在AppDelegate.m的applicationDidFinishLaunching函数前面加入
// init chipmunk
cpInitChipmunk();
初始化整个chipmunk系统,只需做一次。模板生成的代码是在layer初始化中完成chipmunk的初始化,这样处理并不是很合适,我把它移到这个位置来。
5 HelloWorldLayer.h解析
@interface HelloWorldLayer : CCLayerColor
cpSpace *space_; // strong ref
@interface PhysicsSprite : CCSprite
cpShape *shape_; // strong ref
cpSpace *space_; // weak ref
-(void) setPhysicsShape:(cpShape *)shape space:(cpSpace *)
HelloWorldLayer包含了一个cpSpace,每个layer对应一个space,这很好理解,layer自己管理自己的space,不同的layer的space会不一样。space的生命周期由layer管理。定义一个PhysicsSprite,他有两个成员变量shape_和space_,space_只是引用,不做生命周期管理。setPhysicsShape方法把精灵和刚体关联起来。
6 HelloWorldLayer.m解析
6.1 PhysicsSprite的实现
由于cocos2d 2.0 使用OpenGL ES 2.0,这里有了新的改变精灵位置和方向的方法:矩阵变换。看过泰然OpenGL ES系列教程()的对这个应该有印象。
-(BOOL) dirty
return YES;
重写dirty方法,返回YES,目的是让每次layer的update调用后重新绘制PhysicsSprite精灵。
-(CGAffineTransform) nodeToParentTransform
CGFloat x = shape_-&body-&p.x;
CGFloat y = shape_-&body-&p.y;
if ( !isRelativeAnchorPoint_ ) {
x += anchorPointInPoints_.x;
y += anchorPointInPoints_.y;
// Make matrix
CGFloat c = shape_-&body-&rot.x;
CGFloat s = shape_-&body-&rot.y;
if( ! CGPointEqualToPoint(anchorPointInPoints_, CGPointZero) ){
x += c*-anchorPointInPoints_.x + -s*-anchorPointInPoints_.y;
y += s*-anchorPointInPoints_.x + c*-anchorPointInPoints_.y;
// Translate, Rot, anchor Matrix
transform_ = CGAffineTransformMake( c,
return transform_;
重写精灵的矩阵变换方法nodeToParentTransform,模板提供的这个实现,能改变精灵的位置和角度。shape_-&body-&p和shape_-&body-&rot分别是刚体的位置坐标和角度。我们会看到,在update函数中,调用了chipmunk的cpSpaceStep方法,这个方法根据时间流逝计算出每个刚体的新位置和角度,然后在这里被使用最终达到精灵移动旋转的目的。
(void) removeFromParentAndCleanup:(BOOL)cleanup
cpSpaceRemoveBody(space_, shape_-&body);
cpBodyFree(shape_-&body);
cpSpaceRemoveShape(space_, shape_);
cpShapeFree(shape_);
[super removeFromParentAndCleanup:cleanup];
重写removeFromParentAndCleanup方法。
在碰撞发生后,bullet精灵消失,与之对应的刚体也需要释放。
在body被free之前,它必须先从space中移出,否则cpSpaceStep会继续计算这个刚体,接下来就是crash~。
shape也是同样的处理。
6.2 HelloWorldLayer初始化
-(id) init
if( (self=[super initWithColor:ccc4(166, 166, 166, 255)])) {
// enable events
self.isTouchEnabled = YES;
self.isAccelerometerEnabled = YES;
CGSize s = [[CCDirector sharedDirector] winSize];
CCLabelTTF *label = [CCLabelTTF labelWithString:@"Touch the screen"
fontName:@"Marker Felt" fontSize:36];
label.position = ccp( s.width / 2, s.height - 30);
[self addChild:label z:0];
// reset button
[self createResetButton];
// init physics
[self initPhysics];
[self addNewEmeryAtPosition:ccp(240,160)];
[self scheduleUpdate];
初始化CCLayerColor的颜色,作为背景色。&使图层接受按键相应和重力感应,Demo中并未使用重力感应。用CCLabelTTF在屏幕上显示一串提示信息。 初始化用来复位的reset按钮。 初始化物理引擎,后详解。 在240,160的位置初始化一个静止不动的emery。一切初始化完毕,进入更新循环周期。
6.3 初始化物理引擎
-(void) initPhysics
//CGSize s = [[CCDirector sharedDirector] winSize];
space_ = cpSpaceNew();
// set to zero
space_-&gravity = ccp(0, 0);
cpSpaceAddCollisionHandler(space_, kTagBulletNode, kTagEmeryNode, begin, NULL, NULL, NULL, NULL);
cpSpaceNew创建一个新的物理空间。模板代码有staticBody的初始化,在这里我们不需要墙体防止精灵飞出屏幕,去掉相关代码。
注:在chipmunk 5.3之前的版本,需要手动创建一个infinite的body,并用cpSpaceAddStaticShape方法加入到sapce中,现在这些繁琐的过程都由chipmunk自行完成。
gravity是一个矢量,表示重力的方向与大小,要简单地得到匀速直线运动的子弹,把重力设为0会是个简单的方法。
设置碰撞检测回调函数。
cpSpaceAddCollisionHandler的参数很多,按顺序如下:
c语言的特点,没有self,需要设置当前space,表示在这个空间发生的碰撞。
哪个形状和哪个形状发生碰撞。
同上,这2个参数可以一样。
碰撞开始前的回调。
碰撞开始时的回调。
碰撞结束时的回调。
碰撞分离后的回调。
传递给回调函数的参数。
kTagBulletNode, kTagEmeryNode是2个枚举变量,标识不同刚体的形状的,后面还会使用到。
在这个Demo中我们只是用到碰撞开始前的回调begin。
6.4 Emery精灵的创建
-(void) addNewEmeryAtPosition:(CGPoint)pos
PhysicsSprite *sprite = [PhysicsSprite spriteWithFile:@"triangle.png" rect:CGRectMake(0, 0, 50, 50)];
sprite.position =
sprite.tag = kTagEmeryN
[self addChild:sprite];
int num = 3;
CGPoint verts[] = {
ccp(-25,-25),
ccp(0, 25),
ccp(25, -25),
cpBody *body = cpBodyNew(1.0f, cpMomentForPoly(1.0f, num, verts, CGPointZero));
cpSpaceAddBody(space_, body);
cpShape* shape = cpPolyShapeNew(body, num, verts, CGPointZero);
shape-&collision_type = kTagEmeryN
shape-&data =
//shape-&e = 0.5f; shape-&u = 0.5f;
cpSpaceAddShape(space_, shape);
[sprite setPhysicsShape:shape space:space_];
PhysicsSprite创建一个精灵,triangle.png是一个等腰三角形,三角形外的区域是透明色。
设置精灵的tag 为 kTagEmeryNode,在demo中并没有实际的用途,不过这是个好习惯,对调试有帮助。
接下来是再熟悉不过的addChild了。
给多边形shape描点。num定义点的个数,verts按顺序包含每个点的坐标。首先我们得了解下chipmunk有四种shape:圆形,弧形,多边形,方形。分别有不同的函数来创建这些shape并计算他们的惯性值。我们这里只涉及到多边形。点的坐标是以图片的中心为原点,一个像素为一个单位长度,计算出每个点的坐标值。
cpBodyNew创建一个刚体,参数如下:
刚体质量。
刚体惯性值。
惯性值的计算使用chipmunk提供的cpMomentForPoly助手函数,参数如下:
刚体质量。
多边形点数量。
多边形点的坐标集。
偏移量,会作用到每个点,设为CGPointZero会让事情简单很多。
我们让刚体的坐标和精灵的坐标保存一致。chipmunk的坐标值和cocos2d的坐标值是一样的,不用转换,可直接赋值。 cpSpaceAddBody把刚体放到物理空间中,它将受到重力作用。还记得吗?我已经把重力设为失重状态。 我们还需要为刚体定义至少一个shape,cpPolyShapeNew来完成这项工作,参数如下:
多边形点数。
多边形点的坐标集。
偏移量,会作用到每个点,设为CGPointZero会让事情简单很多。
shape的collision_type属性,为自定义的一个类型,设为kTagEmeryNode,在设置碰撞回调的时候,我们已经使用了这个值,这里必须设置,否则碰撞回调不会被触发。 shape的data属性为一个void指针,我们设置为sprite,是为了方便的从shape获取与之对应的精灵。 cpSpaceAddShape,shape同样需要加入到物理空间中去。 最后setPhysicsShape把刚体关联到精灵中去。
addNewBulletAtPosition的实现流程大致相同,参数不同而已。
最大的不同点:cpBodySetVel(body, cpv(160, 0));
我们给bullet刚体设置了一个初速度,cpv构成一个速度矢量,表明了方向和速度大小。
6.5 碰撞检测
整个chipmunk工作流程是这样子:
在space中我们摆放好了各个刚体,刚体有shape,速度等属性。
每个update周期,cpSpaceStep依据时间的推移,计算出新的刚体位置坐标,方向等。
cocos2d依据这些数据来重新绘制精灵。
我们设置的碰撞回调函数会在cpSpaceStep中某个阶段被调用。
static int
begin(cpArbiter *arb, cpSpace *space, void *unused)
// Get pointers to the two bodies in the collision pair and define local variables for them.
// Their order matches the order of the collision types passed
// to the collision handler this function was defined for
CP_ARBITER_GET_SHAPES(arb, a, b);
// additions and removals can't be done in a normal callback.
// Schedule a post step callback to do it.
// Use the hook as the key and pass along the arbiter.
cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL);
// The object is dead, don't process the collision further
CP_ARBITER_GET_SHAPES取出是哪两个shape发生了碰撞,a,b是emery还是bullet,与之前设置回调函数时的第2,3参数的顺序有关。 我们不能直接在begin函数里面释放刚体或者shape,要等chipmunk做完必要的计算后才能释放。cpSpaceAddPostStepCallback用来安全的完成这个步骤。我们return 0,表示不需要chipmunk处理后面的回调过程了。
postStepRemove的实现如下:
static void
postStepRemove(cpSpace *space, cpShape *shape, void *unused)
PhysicsSprite *sprite = shape-&
assert(sprite.tag == kTagBulletNode);
if( sprite ) {
[sprite removeFromParentAndCleanup:YES];
shape-&data保存了精灵实例。
assert帮助我们调试,确认下将被销毁的是不是bullet精灵。
removeFromParentAndCleanup把精灵从屏幕上抹去。
6.6 HelloWorldLayer销毁时的内存清理
reset按钮的处理流程,涉及整个space清理,过程如下:
CCScene *s = [CCScene node];
id child = [HelloWorldLayer node];
[s addChild:child];
[[CCDirector sharedDirector] replaceScene: s];
按钮事件触发以后,创建了一个新的包含HelloWorldLayer 的Scene,来取代当前Scene。
cocos2d会自动去销毁之前的scene,dealloc将被调用。
- (void)dealloc
cpSpaceEachBody(space_, spaceBodyCallback, NULL);
cpSpaceEachShape(space_, spaceShapeCallback, NULL);
cpSpaceFree( space_ );
[super dealloc];
cpSpaceEachBody和cpSpaceEachShape是两个遍历方法,遍历space中的所有body和shape,并为每个找到的body或shape调用处理回调函数。
在处理回调函数spaceBodyCallback和spaceShapeCallback里面我们进行释放操作,在这里不cpSpaceRemoveBody和cpSpaceRemoveShape是安全的,space马上就会被释放,update也不会被调用。
运行Demo看下效果吧,点击屏幕任意位置会触发一次子弹。
Demo可能比较简陋,如果能达到Keep It Simple, Stupid的效果,那就简陋点吧。
刚表态过的朋友 ()
Powered byCocos2d-x3.0屏蔽下层触摸|皂荚花
欢迎来自的盆友!
程序不是看出来的,是写出来的!思想不是想出来的,是敲出来的!欢迎加群交流技术问题!
热度:7,659
关于这个问题网上有不少人在搜索,我自己曾经也写过一篇文章,,其中就用到了屏蔽下层触摸这个功能。Cocos2d-x从3.0版本以来,触摸机制有所改变,虽然实现的道理没有变,不过我今天还是写出这篇博客,就当是简单的复习一下3.0的事件分发机制吧,同时把自己在研究的过程中遇到的问题分享出来。这里采用的方法是最简单,最直接的方法,整体的思路是这样的。设置触摸监听器吞噬触摸,然后在回调函数onTouchBegan中返回true,同时确保这个层的触摸优先级大于你要屏蔽的层的优先级。也许这种方法不能满足你得需求,那就请自行研究或者看下其他博客,或者给我留言共同探讨吧。在实现中我们肯定会遇到的就是屏蔽菜单,让菜单变的不可点击,那我就写一个简单的场景,里边加入菜单,然后加入一个层来屏蔽掉下层的触摸。
bool CreateGame::init()
if(!Layer::init())
auto size = Director::getInstance()-&getWinSize();
Vector&MenuItem *& itemV
for(int i=1;i&4;i++)
auto item = MenuItemImage::create(&no_people.png&,&people.png&);
item-&setTag(i);
itemVector.pushBack(item);
auto menu1 = Menu::createWithArray(itemVector);
menu1-&alignItemsHorizontallyWithPadding(10);
menu1-&setPositionY(size.height*0.75);
this-&addChild(menu1);
auto swallowTouch = SwallowTouch::create();
this-&addChild(swallowTouch);
这里在最后添加了一个层,这个层就是用来的,大家要注意添加这个层的位置,这个层是最后添加进来的,也就是说它要显示的话是显示在最前边的,这个和接下来的屏蔽触摸有联系。以下是这个层的init函数。
bool SwallowTouch::init()
if(!LayerColor::initWithColor(Color4B(100,100,100,100)))
auto label = Label::createWithTTF(&touch!&,&fonts/Marker Felt.ttf&,32);
label-&setPosition(Point(350,800));
this-&addChild(label);
auto callback = [](Touch * ,Event *)
auto listener = EventListenerTouchOneByOne::create();
listener-&onTouchBegan =
listener-&setSwallowTouches(true);
_eventDispatcher-&addEventListenerWithSceneGraphPriority(listener,this);
//_eventDispatcher-&addEventListenerWithFixedPriority(listener,-1);
代码很简单,只是添加了一个事件监听器,设置这个事件监听器吞噬掉触摸,在onTouchBegan回调函数中返回了true。最主要的东西是将事件监听器添加到分发器中的代码,这里可以选择俩种方式来做,如果选择的是第一种,第二个参数是要求我们来将这个事件监听器和一个node绑定,什么是和node绑定,就是这个node对触摸的处理是和这个listener一样的,比如触摸的优先级,接受到触摸以后的处理代码,或者说这个listener来处理node接受到得触摸消息。这个时候Listener的优先级就是绑定的node的显示优先级,就是谁显示在场景的前面,谁先接受到触摸消息,所以在主场景的代码中,我们要把创建这个layer的代码放到后边,这样的话才会优先级高,才会首先收到触摸消息。然后来看第二种方法,addEventListenerWithFixedPriority函数的调用没有去绑定一个node,这个时候它的优先级就是通过第二个参数去传递的,我们通过改变这个优先级可以看下最后的效果,如果我设置为-1是可以成功屏蔽下层的菜单点击的,如果设置为0,那么程序运行会报错,如果设置为1,是屏蔽不掉下层的触摸的。这是因为0这个优先级是被占用了得,我们设置优先级不能设置为0,而要想屏蔽菜单的点击功能,必须设置优先级小于0,以下是这三个值的效果。
因为没有绑定那个LayerColor,所以是没有那个灰度的效果的。如果我只是要屏蔽下层按钮的点击,大可以将代码写到一个类中去完成,其实你也看到了,listener的作用就是事件监听,你可以给它绑定一个node,这个时候它的优先级就是node的显示优先级了,如果是手动设置优先级,不要设置为0的优先级就好了,这个时候当触摸分发的时候,会根据优先级关系来决定listener是否能接受的到触摸,写到一个类中的代码如下。
bool CreateGame::init()
if(!Layer::init())
auto size = Director::getInstance()-&getWinSize();
Vector&MenuItem *& itemV
for(int i=1;i&4;i++)
auto item = MenuItemImage::create(&no_people.png&,&people.png&);
item-&setTag(i);
itemVector.pushBack(item);
auto menu1 = Menu::createWithArray(itemVector);
menu1-&alignItemsHorizontallyWithPadding(10);
//设置菜单为不可点击
//menu1-&setEnabled(false);
menu1-&setPositionY(size.height*0.75);
auto item = (MenuItem *)menu1-&getChildByTag(1);
item-&selected();
this-&addChild(menu1);
auto layer = LayerColor::create(Color4B(100,100,100,100));
auto callback = [](Touch * ,Event *)
auto listener = EventListenerTouchOneByOne::create();
listener-&onTouchBegan =
listener-&setSwallowTouches(true);
_eventDispatcher-&addEventListenerWithSceneGraphPriority(listener,layer);
//_eventDispatcher-&addEventListenerWithFixedPriority(listener,-1);
this-&addChild(layer);
最后有一点需要注意的是,如果是固定优先值的监听器添加到一个节点(addEventListenerWithFixedPriority),那当这个节点被移除时必须同时手动移除这个监听器,但是添加显示优先监听器到节点(addEventListenerWithSceneGraphPriority)就不用这么麻烦,监听器和节点是绑定好的,一旦节点的析构函数被调用,监听器也会同时被移除。如果我们屏蔽了触摸,在某个逻辑处需要将这种触摸屏蔽去掉,比如就是做一个弹出对话框,用户点击了关闭按钮就应该将对下层的屏蔽去掉了,那么很显然,如果使用的是固定优先级,只是remove掉这个对话框最后是不能去除屏蔽的,因为事件监听器你并没有去掉啊,当有触摸事件过来的时候照样会分发给它事件的,所以解决办法不用说了吧。
2014年十月 &(8)
2014年九月 &(3)
2014年八月 &(7)
2014年七月 &(14)
2014年六月 &(15)
2014年五月 &(19)
2014年四月 &(14)
2014年三月 &(22)
2014年二月 &(59)
2014年一月 &(16)
小广告-翻墙小插件
文章关键字
Powered by安全检查中...
请打开浏览器的javascript,然后刷新浏览器
< 浏览器安全检查中...
还剩 5 秒&}

我要回帖

更多关于 quick cocos2d x 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信