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

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

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

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


一、理论

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

_b2World->SetContactListener(this);

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

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

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

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

二、实践

1.规划

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

2.碰撞基类实现

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

class BaseContactNode : public Node{
public:
    typedef enum{
        DIRECTION_UP = 1,DIRECTION_DOWN = -1,DIRECTION_RIGHT = 2,DIRECTION_LEFT = -2
    }DIRECTION;
public:
    //碰撞处理函数
    virtual void beginContact(Node*,b2Contact*) = 0;
    //获取碰撞方向,这里只简单判断上下左右
    virtual int getContactDirection(Node* node,b2Contact* contact){
        int isReverse = 1;
        auto manifold = contact->GetManifold();
        Node* other = static_cast<Node*>(contact->GetFixtureB()->GetBody()->GetUserData());
        //判断碰撞参考者
        if((node != other && manifold->type == b2Manifold::e_faceB)
           || (node == other && manifold->type == b2Manifold::e_faceA)){
            isReverse = -1;
        }

        int ret = 0;
        if (manifold->localNormal.y == -1) {
            ret = DIRECTION_UP ;
        }else if(manifold->localNormal.y == 1){
            ret = DIRECTION_DOWN;
        }else if(manifold->localNormal.x == 1){
            ret = DIRECTION_RIGHT;
        }else if(manifold->localNormal.x == -1){
            ret = DIRECTION_LEFT;
        }
        return ret * isReverse;

    }
};
简单说一下其中用到的属性,box2d会将碰撞的大量信息保存下来,包括但不限于
  • contact->GetFixtureB()->GetBody()获取碰撞者body
  • contact->GetManifold()获取碰撞相关信息,返回的b2Manifold中包括
    • localNormal法向量,可以判断受力方向
    • points碰撞位置
    • type,碰撞参考者类型,可能是相对物体A——e_faceA,或B——e_faceB
  • 可以通过contact->GetWorldManifold(&worldManifold)获取碰撞点相对于世界坐标的信息

3.调度者实现

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

void MarioScene::BeginContact(b2Contact *contact){
    if (contact && contact->IsTouching())
    {
        auto A = static_cast<Node*>(contact->GetFixtureA()->GetBody()->GetUserData());
        auto B = static_cast<Node*>(contact->GetFixtureB()->GetBody()->GetUserData());

        auto pBaseBoxSprite = static_cast<BaseContactNode*>(A);
        if(pBaseBoxSprite){
            pBaseBoxSprite->beginContact(B,contact);
        }

        pBaseBoxSprite = static_cast<BaseContactNode*>(B);
        if(pBaseBoxSprite){
            pBaseBoxSprite->beginContact(A,contact);
        }
    }
}

4.碰撞处理

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

4.1 马里奥碰撞处理

注意:

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

然后直接上代码:

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

4.2 怪物碰撞处理

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

void MarioMonster::beginContact(Node* node,b2Contact* contact){
    int direction = getContactDirection(this,contact);

    if(node && node->getTag() == MarioScene::CONTACT_PLAYER){
        if(direction == DIRECTION_DOWN){
            //如果被马里奥踩到,死亡
            die();
        }
    }else if(node && node->getTag() == MarioScene::CONTACT_MONSTER){
        //如果被其他怪物碰到,死亡(其他乌龟是可以被用来做炮弹的)
        die(false);
    }
}

三、结语

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

相关文章

    本文实践自 RayWenderlich、Ali Hafizji 的文章《...
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@1...
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从C...
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发...
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《...
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试...