【cocos3.x+box2d+tileMap】制作马里奥游戏(四)碰撞检测

前端之家收集整理的这篇文章主要介绍了【cocos3.x+box2d+tileMap】制作马里奥游戏(四)碰撞检测前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

转载请注明来源:http://www.jb51.cc/article/p-dxevjywf-ya.html

Box2d物理引擎还提供一个很重要的功能:碰撞检测。如马里奥游戏中,需要检测马里奥与怪物、蘑菇、金币等的碰撞,通过判断不同的碰撞点、碰撞对象做出不同的处理。

我们要在马里奥中实现的碰撞效果如下:


一、理论

Box2d通过设置碰撞监听来处理碰撞,在创建世界后,可以进行设置监听:

  1. _b2World->SetContactListener(this);

设置的监听对象需要继承b2ContactListener类,实现函数

  1. virtual void BeginContact(b2Contact* contact):碰撞开始时的回调函数,一般简单的碰撞检测使用;
  2. virtual void EndContact(b2Contact* contact):碰撞发生后的回调函数,一般简单的碰撞检测使用;
  3. virtual void PreSolve(b2Contact* contact,const b2Manifold* oldManifold):碰撞求解前的回调函数,求解就是指计算碰撞产生的冲击力,需要计算碰撞冲击力造成的破坏等效果时,需要使用此回调函数
  4. virtual void PostSolve(b2Contact* contact,const b2ContactImpulse* impulse):碰撞求解后的回调函数,需要计算碰撞冲击力造成的破坏等效果时,需要使用此回调函数

这四个回调方法中,前两个功能有限但使用起来简单,后两个提供的信息量大,但使用起来比较复杂,这个要根据游戏的具体要求而定,如果我们的游戏过程对物理要求不高,仅仅是实现碰撞检测功能,那么我们主要使用BeginContact(b2Contact* contact)这个回调函数就足够了,如果我们要处理碰撞之前和碰撞之后的效果,根据碰撞中产生的相互作用力来计算物理碰撞后的移动,则我们必须好好的利用全部这四个函数,它们联合作用起来,可以模拟出比较真实而复杂的物理碰撞效果

发生碰撞后,Box2d会自动调用对应函数,开发者进行具体实现。

二、实践

1.规划

  • 设置一个调度者,所有碰撞由其进行分发
  • 所有需要进行碰撞处理的类,继承自相同的基类,方便调度者处理
  • 各自的碰撞事件各自处理

2.碰撞基类实现

其实就是一个虚类,定义碰撞处理函数,后面为了方便处理,还加了一个碰撞方向检测函数

  1. class BaseContactNode : public Node{
  2. public:
  3. typedef enum{
  4. DIRECTION_UP = 1,DIRECTION_DOWN = -1,DIRECTION_RIGHT = 2,DIRECTION_LEFT = -2
  5. }DIRECTION;
  6. public:
  7. //碰撞处理函数
  8. virtual void beginContact(Node*,b2Contact*) = 0;
  9. //获取碰撞方向,这里只简单判断上下左右
  10. virtual int getContactDirection(Node* node,b2Contact* contact){
  11. int isReverse = 1;
  12. auto manifold = contact->GetManifold();
  13. Node* other = static_cast<Node*>(contact->GetFixtureB()->GetBody()->GetUserData());
  14. //判断碰撞参考者
  15. if((node != other && manifold->type == b2Manifold::e_faceB)
  16. || (node == other && manifold->type == b2Manifold::e_faceA)){
  17. isReverse = -1;
  18. }
  19.  
  20. int ret = 0;
  21. if (manifold->localNormal.y == -1) {
  22. ret = DIRECTION_UP ;
  23. }else if(manifold->localNormal.y == 1){
  24. ret = DIRECTION_DOWN;
  25. }else if(manifold->localNormal.x == 1){
  26. ret = DIRECTION_RIGHT;
  27. }else if(manifold->localNormal.x == -1){
  28. ret = DIRECTION_LEFT;
  29. }
  30. return ret * isReverse;
  31.  
  32. }
  33. };
简单说一下其中用到的属性Box2d会将碰撞的大量信息保存下来,包括但不限于
  • contact->GetFixtureB()->GetBody()获取碰撞者body
  • contact->GetManifold()获取碰撞相关信息,返回的b2Manifold中包括
    • localNormal法向量,可以判断受力方向
    • points碰撞位置
    • type,碰撞参考者类型,可能是相对物体A——e_faceA,或B——e_faceB
  • 可以通过contact->GetWorldManifold(&worldManifold)获取碰撞点相对于世界坐标的信息

3.调度者实现

实现很简单,直接分发^_^

  1. void MarioScene::BeginContact(b2Contact *contact){
  2. if (contact && contact->IsTouching())
  3. {
  4. auto A = static_cast<Node*>(contact->GetFixtureA()->GetBody()->GetUserData());
  5. auto B = static_cast<Node*>(contact->GetFixtureB()->GetBody()->GetUserData());
  6.  
  7. auto pBaseBoxSprite = static_cast<BaseContactNode*>(A);
  8. if(pBaseBoxSprite){
  9. pBaseBoxSprite->beginContact(B,contact);
  10. }
  11.  
  12. pBaseBoxSprite = static_cast<BaseContactNode*>(B);
  13. if(pBaseBoxSprite){
  14. pBaseBoxSprite->beginContact(A,contact);
  15. }
  16. }
  17. }

4.碰撞处理

不同对象的碰撞处理不相同,这里拿马里奥和怪物两个做例子。

4.1 马里奥碰撞处理

注意:

下面的代码中可以看到,在碰到问号后,我的逻辑是生成一个蘑菇,本来我是想直接生成,同时添加body到Box2d世界中,但直接在一个Assert上报错了b2Assert(m_world->IsLocked() == false),这是因为在Box2d的碰撞处理中(世界步中in the middle of a time step),会将世界锁定,不允许修改,所以我们只能将状态缓存下来,在后续的update中进行添加

然后直接上代码

  1. void MarioPlayer::beginContact(Node* node,b2Contact* contact){
  2. int direction =getContactDirection(this,contact);
  3. if(direction == DIRECTION_UP){
  4. //受力方向是上,说明已经碰到了地面,可以重新起跳
  5. delStatus(MarioPlayer::STATUS_JUMP);
  6. }
  7. if (node && node->getTag() == MarioScene::CONTACT_MONSTER) {
  8. //如果碰到了怪物
  9. auto monster = static_cast<MarioMonster*>(node);
  10. if(direction != DIRECTION_UP && monster->getStatus() != MarioMonster::STATUS_DIED){
  11. //如果不是踩到怪物头上且怪物不是死亡状态,则Game Over
  12. MarioScene::die();
  13. }else if(direction == DIRECTION_UP && monster->getStatus() == MarioMonster::STATUS_DIED){
  14. //碰撞死后的乌龟顶端,则乌龟会以更快的速度移动,当然,这样写是因为我当前场景里只有乌龟
  15. if(this->getPosition().x >= monster->getPosition().x){
  16. monster->diedImpluse(DIRECTION_RIGHT);
  17. }else{
  18. monster->diedImpluse(DIRECTION_LEFT);
  19. }
  20. }
  21. }else if(node && node->getTag() == MarioScene::CONTACT_BONUS){
  22. //如果碰到问号,则生出一个蘑菇
  23. auto marioScene = static_cast<MarioScene*>(this->getParent());
  24. if(marioScene){
  25. marioScene->addBonusList(node);
  26. }
  27. }
  28. }

4.2 怪物碰撞处理

相对来说比较简单,直接上代码

  1. void MarioMonster::beginContact(Node* node,b2Contact* contact){
  2. int direction = getContactDirection(this,contact);
  3.  
  4. if(node && node->getTag() == MarioScene::CONTACT_PLAYER){
  5. if(direction == DIRECTION_DOWN){
  6. //如果被马里奥踩到,死亡
  7. die();
  8. }
  9. }else if(node && node->getTag() == MarioScene::CONTACT_MONSTER){
  10. //如果被其他怪物碰到,死亡(其他乌龟是可以被用来做炮弹的)
  11. die(false);
  12. }
  13. }

三、结语

至此,马里奥世界中,和怪物等的碰撞交互也实现了,已经很接近真实的马里奥世界,剩余的就是继续丰满,添加不同的怪物、增加不同的马里奥状态如开枪等、设计更大的地图、增加更多的地图原素了。

猜你在找的Cocos2d-x相关文章