Cocos2d-x3.2总结---使用物理引擎进行碰撞检测
Cocos2d-x3.2总结---使用物理引擎进行碰撞检测【转自】:http://blog.csdn.net/cbbbc/article/details/38541099 通常在游戏简单逻辑判断和模拟真实的物理世界时,我们只需要在定时器中判断游戏中各个精灵的条件是否满足判断条件就可以了。例如,在飞机大战中,判断我方子弹和敌机是否发生碰撞一般在定时器中通过敌机所在位置的矩形是否包括了子弹的位置来判断是否发生碰撞。在消除类游戏中,判断在y轴或x轴上是否要消除相同物品一般在定时器中通过循环来检测在某个方向上是否有连续的相同物品满足消除个数来移除精灵就可以了。 碰撞形状:因为形状与刚体相关联,所以你可以为一个刚体定义形状。为了定义一个复杂的形状,你可以给刚体绑定足够多的形状。形状包含着一个对象的表面属性如摩擦力、弹性等。 约束/关节:约束和关节被用来描述刚体之间是如何关联的人们经常对Chipmunk中的刚体和碰撞形状以及两者与精灵之间的关系产生混淆。精灵是对象的可视化表现,而碰撞形状是定义对象应该如何碰撞的不可见的属性。精灵和碰撞形状两者的位置和角度都是由刚体的运动控制的。通常你应该创建一个游戏对象类型,把这些东西捆绑在一起。 在Cocos2d-x3.2中,物理世界被融入到Scene中,即当创建一个场景时,就可以指定这个场景是否使用物理引擎。sprite自带body属性,直接设置body,而形状、约束等属性添加到body中即可。碰撞的检测通过事件分发器来监控,所以你首先要创建监听器--EventListenerPhysicsContact,再把其添加到分发器中。
1 Sprite* ball; 2 PhysicsWorld* m_world; 3 void setPhyWorld(PhysicsWorld* world) {m_world=world;}; 4 static cocos2d::Scene* createScene(); 5 virtual bool init(); 6 CREATE_FUNC(HelloWorld); 在代码1中,m_world是用来保存在scene中创建的物理世界,也就是上面说的空间,以备得到空间中的参数,如重力系数等。 第二步,在createScene()中添加代码2,它的作用是创建一个有物体空间的场景。 1 //创建有物理空间的场景 2 Scene* scene=Scene::createWithPhysics(); 设置Debug模式,你会看到物体的表面被线条包围,主要为了在调试中更容易地观察 4 scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL); 5 HelloWorld* layer=HelloWorld::create(); 6 把空间保持我们创建的层中,就是上面所说m_world的作用,方便后面设置空间的参数 7 layer->setPhyWorld(scene->getPhysicsWorld()); 8 scene->addChild(layer); 9 return scene; 第三步,在init()添加代码3。 代码3: 1 if (!Layer::init()) 2 { 3 return false; 4 } 5 6 Size visibleSize = Director::getInstance()->getVisibleSize(); 7 Point origin = Director::getInstance()->getVisibleOrigin(); 8 9 ballOne = Sprite::create("Ball.jpg"); 10 ballOne->setPosition(visibleSize.width / 2,visibleSize.height / 2); 11 创建物体,并且物体的形状为圆形,第一参数为半径,第二个参数为物体材质 12 第三个参数为边的厚度,就是在Debug模式下看到的物体外面线条的厚度,默认为0 13 PhysicsBody* ballBodyOne = PhysicsBody::createCircle(ballOne->getContentSize().width / 14 是否设置物体为静态 15 ballBody->setDynamic(false); 16 设置物体的恢复力 17 ballBodyOne->getShape(0)->setRestitution(1.0f); 18 设置物体的摩擦力 19 ballBodyOne->getShape(0)->setFriction(0.0f); 20 设置物体密度 21 ballBodyOne->getShape(0)->setDensity(22 设置质量 23 ballBodyOne->getShape(0)->setMass(5000); 24 设置物体是否受重力系数影响 25 ballBodyOne->setGravityEnable(false); 26 27 设置物体的冲力 28 Vect force = Vect(500000.0f,500000.0f); 29 ballBodyOne->applyImpulse(force); 30 把物体添加到精灵中 31 ballOne->setPhysicsBody(ballBodyOne); 32 设置标志 33 ballOne->setTag(1); 34 this->addChild(ballOne); 35 36 设置第二个球 37 ballTwo = Sprite::create(38 ballTwo->setPosition(visibleSize.width / 3,128)">3); 39 PhysicsBody* ballBodyTwo = PhysicsBody::createCircle(ballOne->getContentSize().width / 40 41 ballBodyTwo->setDynamic(false); 42 ballBodyTwo->getShape(43 ballBodyTwo->getShape(44 ballBodyTwo->getShape(45 46 ballBodyTwo->setGravityEnable(47 48 49 force = Vect(-50 ballBodyTwo->applyImpulse(force); 51 ballTwo->setPhysicsBody(ballBodyTwo); 52 ballTwo->setTag(53 this->addChild(ballTwo); 54 55 创建一个盒子,用来碰撞 56 Sprite* edgeSpace = Sprite::create(); 57 PhysicsBody* boundBody = PhysicsBody::createEdgeBox(visibleSize,PHYSICSBODY_MATERIAL_DEFAULT,128)">58 boundBody->getShape(59 boundBody->getShape(60 61 edgeSpace->setPhysicsBody(boundBody); 62 edgeSpace->setPosition(Point(visibleSize.width / 2)); 63 this->addChild(edgeSpace); 64 edgeSpace->setTag(0); 65 66 true; 在代码3中,我们看到了物体的创建并且创建是就自带形状,然后物体的物理属性。一个物体可以先创建,然后再添加形状。一个物体可以有多个形状,只要不冲突就可以,并且同一个物体多个形状之间不发生碰撞,因为他们属于同一碰撞组。所以我们看到getShape(0)的调用,因为我们添加形状时默认从零开始,上面我们只添加了一个形状。添加形状代码在Cocos2d-x3.2代码为PhysicsBody::addShape()。你可以在...cocos2dcocosphysics目录下CCPhysicsBody.cpp中看到代码4。 代码4: 1 PhysicsShape* PhysicsBody::addShape(PhysicsShape* shape,bool addMassAndMoment/* = true*/) if (shape == nullptr) return nullptr; 4 add shape to body 5 if (_shapes.getIndex(shape) == -1) 6 { 7 shape->setBody(this); 8 calculate the area,mass,and desity 9 area must update before mass,because the density changes depend on it. 10 if (addMassAndMoment) 11 { 12 _area += shape->getArea(); 13 addMass(shape->getMass()); 14 addMoment(shape->getMoment()); 15 } 16 if (_world != nullptr) 17 { 18 _world->addShape(shape); 19 } 20 _shapes.pushBack(shape); 21 if (_group != CP_NO_GROUP && shape->getGroup() == CP_NO_GROUP) 22 { 23 shape->setGroup(_group); 24 } 25 } 26 return shape; 27 } 代码4为物体在同一组中添加形状,如果你想再进一步追寻下去,可以继续转到不同函数的定义中,最后你会看到Chipmunk原生的代码。 void PhysicsBody::applyImpulse(const Vect& impulse) 3 applyImpulse(impulse,Vec2()); 6 const Vect& impulse,255)">const Vec2& offset) 7 { 8 cpBodyApplyImpulse(_info->getBody(),PhysicsHelper::point2cpv(impulse),128)"> 9 PhysicsHelper::point2cpv(offset)); 10 } 代码6: void PhysicsShape::setFriction(float friction) 2 { 3 _material.friction = friction; 4 5 for (cpShape* shape : _info->getShapes()) 6 { 7 cpShapeSetFriction(shape,PhysicsHelper::float2cpfloat(friction)); 8 } 9 } 在代码5中的cpBodyApplyImpulse为Chipmunk的原生代码。设置冲力时,Vec2()构造函数产生默认值为(0,0)。 void PhysicsShape::setDensity(float density) if (density < 0) 4 { 5 return; 6 } 7 _material.density = density; 8 if (_material.density == PHYSICS_INFINITY) 9 { 10 setMass(PHYSICS_INFINITY); 11 } 12 else if (_area > 13 { 14 setMass(PhysicsHelper::float2cpfloat(_material.density * _area)); 15 } 16 } 代码7首先是判断密度是否小于0,然后再判断密度是否是无限大,最后才设置密度。在代码3中,setTag的作用是当发生碰撞时能让我们知道那两个精灵发生碰撞,具体见下文碰撞检测。 void HelloWorld::onEnter() 3 Layer::onEnter(); 添加监听器 5 auto contactListener = EventListenerPhysicsContact::create(); 6 设置监听器的碰撞开始函数 7 contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin,0)">添加到事件分发器中 9 _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener,128)">10 }
代码9: bool HelloWorld::onContactBegin(const PhysicsContact& contact) 3 Sprite* spriteA = (Sprite*)contact.getShapeA()->getBody()->getNode(); 4 Sprite* spriteB = (Sprite*)contact.getShapeB()->getBody()->getNode(); int tagA = spriteA->getTag(); int tagB = spriteB->getTag(); 7 if (tagA == 1 && tagB == 2 || tagA == 2 && tagB == 8 { 9 spriteA->removeFromParent(); 10 spriteB->removeFromParent(); true; 13 } 对于代码8,你是否有这样的疑问--监听器是否可以在其他地方添加,而不是在onEnter函数中。这是可以的,只要在发生碰撞前添加就可以,这里在onEnter添加也只是为了在发生碰撞前添加而已。关于CC_CALLBACK--回调函数的更详细的讲解,你可以阅读这篇文章《浅析schedule和回调函数》。 代码9中,我只给出了碰撞begin函数,Chipmunk一共有4个回调函数,Chipmunk中文手册如下说明: 2.preSolve():该步中两个形状相互接触。回调返回false,Chipmunk在这一步会忽略碰撞,返回true来正常处理它。此外,你可以使用cpArbiterSetFriction(),cpArbiterSetElasticity()或cpArbiterSetSurfaceVelocity()来提供自定义的摩擦,弹性,或表面速度值来覆盖碰撞值。 3.postSolve():两种形状相互接触并且它们的碰撞响应已被处理。如果你想使用它来计算音量或者伤害值,这时你可以检索碰撞冲力或动能。 4.separate():该步中两个形状刚第一次停止接触。确保begin()/separate()总是被成对调用,当删除接触中的形状时或者析构space时它也会被调用。碰撞回调都与cpArbiter结构紧密相关。你应该熟悉那些为好。 注1:标记为传感器的形状(cpShape.sensor==true)从来不会得到碰撞处理,所以传感器形状和其他形状间永远不会调用postSolve()回调。它们仍然会调用begin()和separate()回调,而preSolve()仍然会在每帧调用回调,即使这里不存在真正的碰撞。注2:preSolve()回调在休眠算法运行之前被调用。如果一个对象进入休眠状态,postSolve()回调将不会被调用,直到它被唤醒。 1 typedef int (*cpCollisionBeginFunc)(cpArbiter *arb,255)">struct cpSpace *space,255)">void *data) 2 typedef int (*cpCollisionPreSolveFunc)(cpArbiter *arb,cpSpace *space,128)">3 typedef void (*cpCollisionPostSolveFunc)(cpArbiter *arb,128)">4 typedef void (*cpCollisionSeparateFunc)(cpArbiter *arb,255)">void *data) 碰撞处理函数类型。所有这些函数都附带一个arbiter,space和用户data指针,只有begin()和preSolve()回调会返回值。上面带cp开头的函数都是Chipmunk原生函数,如果你想了解得更多,可以阅读Chipmunk中文手册中相关函数的说明。 bool PhysicsWorld::init(Scene& scene) do 5 _info = new PhysicsWorldInfo(); 6 CC_BREAK_IF(_info == nullptr); 7 _scene = &scene; 8 _info->setGravity(_gravity); 9 cpSpaceSetDefaultCollisionHandler(_info->getSpace(),128)">10 (cpCollisionBeginFunc)PhysicsWorldCallback::collisionBeginCallbackFunc,128)">11 (cpCollisionPreSolveFunc)PhysicsWorldCallback::collisionPreSolveCallbackFunc,128)">12 (cpCollisionPostSolveFunc)PhysicsWorldCallback::collisionPostSolveCallbackFunc,128)">13 (cpCollisionSeparateFunc)PhysicsWorldCallback::collisionSeparateCallbackFunc,128)">14 15 16 } while (17 18 }
void cpSpaceSetDefaultCollisionHandler( 2 cpSpace *space,128)">3 cpCollisionBeginFunc begin,128)">4 cpCollisionPreSolveFunc preSolve,128)">5 cpCollisionPostSolveFunc postSolve,128)">6 cpCollisionSeparateFunc separate,128)">7 void *data 8 ) 好吧,此时我们知道了Cocos2d-x3.2是如何和上面那4个函数联系在一起并且把它们封装到哪些函数中去。因此, 1 contac tListener->onContactBegin对应cpCollisionBeginFunc, 2 3 contactListener->onContactPreSolve对应cpCollisionPreSo lveFunc, 5 contactListener->onContactPostSolve对应cpCollisionPostSolveFunc, 6 7 contactListen er->onContactSeperate对应cpCollisionSeparateFunc。 因此,我们只要在物理监听器中设置好回调函数,并把它添加到事件分发器中就可以触发碰撞检测事件了。 什么?效果2中小球碰撞后怎么不消失,这不是我预想的结果啊!是不是哪里出了问题。我马上就在onContact-Begin中设置断点进行调试,发现两球即使碰撞也没有触发碰撞函数,这不是坑爹吗?就这个问题,一开始我想是不是每帧刷新的速度太快了导致检测不了碰撞,但是从效果2中可以看到明明两球发生了碰撞,结果就是两球受力弹开了 static inline cpBoolqueryReject(cpShape *a,cpShape *b) return ( 4 BBoxes must overlap 形状a和b的包围盒是否重叠 6 !cpBBIntersects(a->bb,b->bb) 7 形状a和b是否附着在同一物体身上 Don't collide shapes attached to the same body. 9 || a->body == b->body Don't collide objects in the same non-zero group 11 形状a和b是否在同一非零群组中 12 || (a->group && a->group == b->group) 13 Don't collide objects that don't share at least on layer. 14 形状a和b是否在同一层中 15 || !(a->layers & b->layers) Don't collide infinite mass objects 17 形状a和b的质量是否为无限大 18 || (a->body->m == INFINITY && b->body->m == INFINITY) 19 ); 20 } 在代码12中,我给出了中文的翻译注释,如有错误,请见谅。如果代码12返回值为真,则不会发生碰撞。但是Cocos2d-x3.2封装了Chipmunk的碰撞过滤,方式与Chipmunk原生代码不同。Cocos2d-x3.2有三个碰撞过滤标志categoryBitmask、contactTestBitmask和collisionBitmask。这三个标志在目录...cocos2dcocosphysicsCCPhysicsShape.h下有英文的注释,下面是我的翻译和加上我的一些理解。 categoryBitmask:
1 ballBodyOne->setCategoryBitmask(0x0001); 2 ballBodyOne->setCollisionBitmask(3 ballBodyTwo->setCategoryBitmask(0x0010); 4 ballBodyTwo->setCollisionBitmask(0x0010); 我们可以看到效果3。 效果3: 从效果3中可以到两球不会发生碰撞,会相互穿过对方。因为ballOne的分类掩码和ballTwo的碰撞掩码做逻辑与的结果为0,ballTwo的分类掩码和ballOne的碰撞掩码做逻辑与的结果为0,所以不会发生碰撞。注意,即使ballOne的ballOne的分类掩码和ballTwo的碰撞掩码做逻辑与的结果非0,但是ballTwo的分类掩码和ballOne的碰撞掩码做逻辑与的结果为0,依旧不会发生碰撞的。这正如物理书上所说,作用力和反作用力是相互的。读者可以自己修改代码,观察运行效果。 3 ballBodyOne->setContactTestBitmask(5 ballBodyTwo->setCategoryBitmask(6 ballBodyTwo->setCollisionBitmask(7 ballBodyTwo->setContactTestBitmask(0x0001);
ballBodyOne->applyImpulse(force); 2 ballOne->runAction(RepeatForever::create( 3 Sequence::create(MoveTo::create(1.0f,Vec2(300,128)">200)),128)"> 4 MoveTo::create(400)),128)"> 5 MoveTo::create(200,128)"> 6 MoveTo::create( 7 NULL))); 9 ballBodyTwo->applyImpulse(force); 10 ballTwo->runAction(RepeatForever::create( 11 Sequence::create(MoveTo::create(100,128)">12 MoveTo::create(700,128)">13 NULL)));
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |