Cocos2dx Box2dの衝突判定

ChainZ(クリエイター)
いろいろやってます。

box2dを使うと、衝突判定が必要になると思うので、cocos2dxでbox2dのb2ContactListenerを使ってみました。

box2dのb2ContactListenerを継承

box2dで衝突によるコールバックは、b2ContactListener中の

// 衝突が始まった時に実行
virtual void BeginContact(b2Contact* contact);

// 衝突が終わった時に実行
virtual void EndContact(b2Contact* contact);

二つのメソードが受け取ることになっています。一番簡単な使い方は、cocos2dxのSceneレイヤーでb2ContactListenerを継承します。

#include "Box2D/Box2D.h"

class HelloWorld : public cocos2d:Layer, public b2ContactListener
{
public:
    static cocos2d::Scene* createScene();
    virtual bool init();  

    // 衝突が始まった時に実行
    virtual void BeginContact(b2Contact* contact);

    // 衝突が終わった時に実行
    virtual void EndContact(b2Contact* contact);
};

cppファイルでBeginContact()EndContact()の宣言をします。

void HelloWorld::BeginContact(b2Contact *contact)
{
}

void HelloWorld::EndContact(b2Contact *contact)
{
}

引数のb2Contact* contactは二つのFixtureの情報を持ています。

b2Fixture* GetFixtureA();
// 衝突している一つ目のFixutreを取得
b2Fixture* GetFixtureB();
// 衝突している二つ目のFixutreを取得

そうです、box2dの衝突はbodyでではなく、Fixutureで判定しています。でも下記の方法で、衝突しているオブジェクトのbodyも取得できます。

b2Body *bodyA = contact->GetFixtureA()->GetBody();
b2Body *bodyB = contact->GetFixtureB()->GetBody();

実際使ってみよう。

今私が参加しているプロジェクトは、チャリ走のようなエンディングなしのゲームです。そこで、プレイヤーがジャンプ可能の条件、つまり地面と接触しているかどうかはbox2dの衝突コールバックで判定しています。

まずはプレイヤーを作成

auto player = Player::createPlayer("player.png");
player->setTag(TAG_PALYER);
// Playerを作成
// Playerはcocos2dxのSpriteを継承しています。
// 地面と接触しているかどうかを判定するbool型メンバー変数_on_groundを定義しています

b2Body *player_body;
// b2Bodyを作成 --略

player_body->SetUserData(player);
// UserDataとしてb2Bodyに渡す。

this->addChild(player);

同じく、地面のSpriteを作成

auto ground = Sprite::ground("ground.png");
ground->setTag(TAG_GROUND);

b2Body *ground_body;
// b2Bodyを作成 --略

ground_body->SetUserData(ground);
this->addChild(ground);

それで、box2dの衝突コールバックメソードで判定をします:

void HelloWorld::BeginContact(b2Contact *contact)
{
    void* the_item_a = contact->GetFixtureA()->GetBody()->GetUserData();
    void* the_item_b = contact->GetFixtureB()->GetBody()->GetUserData();
    // 一見ややこしいですが、
    // 衝突しているFixtureからBodyを取得し、またBodyからUserDataを取得する

    if (the_item_a && the_item_b)
    // UserDataが両方有効であれば
    {
        if ((static_cast<Sprite*>(the_item_a)->getTag() == TAG_PLAYER &&
             static_cast<Sprite*>(the_item_b)->getTag() == TAG_GROUND))
             //もしAがプレイヤー、Bが地面の場合
        {
            static_cast<Player*>(the_item_a)->setOnGround(true);
        }
        else if ((static_cast<Sprite*>(the_item_a)->getTag() == TAG_GROUND &&
                  static_cast<Sprite*>(the_item_b)->getTag() == TAG_PLAYER))
                  //もしBがプレイヤー、Aが地面の場合
        {
            static_cast<Player*>(the_item_b)->setOnGround(true);
        }

    }
}

何で二回判定しないといけないというと、box2dはFixtureAFixtureBどっちがどっちにぶつかったのかわからないからです。

原理をまとめると、プレイヤーと地面のSpriteにそれぞれタグをつけて、UserDataとしてb2bodyに渡します。その後、box2dの衝突メソードで、衝突しているFixtureからbodyを取得し、またbodyからUserDataのSpriteを取得し、そのタグをチェックします。

感想

実はプレイヤーが地面と接触しているかどうかを判定するには、Playerというクラスを作成して、その中に当たり判定を行ったほうが一番いいです。ここでただbox2dの当たり判定の実装を説明しただけ、今度は実際に当たり判定を実装したPlayerクラスを作ってみます。