cocos2dx box2dの衝突判定
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はFixtureA
とFixtureB
どっちがどっちにぶつかったのかわからないからです。
原理をまとめると、プレイヤーと地面のSprite
にそれぞれタグをつけて、UserData
としてb2body
に渡します。その後、box2dの衝突メソードで、衝突しているFixtureからbodyを取得し、またbodyからUserDataのSprite
を取得し、そのタグをチェックします。
##感想
実はプレイヤーが地面と接触しているかどうかを判定するには、Player
というクラスを作成して、その中に当たり判定を行ったほうが一番いいです。ここでただbox2dの当たり判定の実装を説明しただけ、今度は実際に当たり判定を実装したPlayer
クラスを作ってみます。