敵と接触したり弾に当たったなどの当たり判定 をcocos2d-xで実装します、今回は例として前章で設置したプレイヤーが床がないため、重力設定をONにするとプレイヤーが画面外へ落ちていってしまいました。そこで床を作成しプレイヤーがそこへ着地できるようにしてみます。
//床 auto wall = Sprite::create(); wall->setPosition(Vec2(size.width/2,0)); wall->setTextureRect(Rect(0,0,size.width,size.height/8)); wall->setColor(Color3B::BLACK); // wall->setRotation(3.0f);//3度の傾斜 //反発係数と摩擦係数の設定 auto material = PHYSICSBODY_MATERIAL_DEFAULT; material.restitution = 0.0f; material.friction = 2.0f; //物理法則の設定 auto pWall = PhysicsBody::createBox(wall->getContentSize(), material); pWall->setDynamic(false);//重力を受けない pWall->setRotationEnable(false);//回転運動不可 wall->setPhysicsBody(pWall); //貼り付け this->addChild(wall);
これを前章で作成したPlayer.cppのinit関数内に追記して実行してみます。
しっかり床に着地していることを確認できました。ちなみに6行目のコメントを外すとこのようにコロコロと転がっていくのも確認できます。
摩擦係数(friction)を少し設定しないと滑るだけですので転がりません、また反発係数(restitution)は値を入れると単純に跳ねます。
ここで1つ気づきます、処理を書かなくても接触すると止まるということを。ここを疑問に思う方は過去に実装した経験があるのかもしれませんが、通常は床を作成しても当たり判定で処理を書かなければすりぬけてしまうはずです。しかし、cocos2d-xの仕様は(物理エンジン系では当たり前ですが)剛体に設定していれば自動的に接触した時の振る舞いも適切に反映されるようになっていますのでご安心ください。
しかし、これでもう当たり判定が終わりなわけではないですよね、大事なのはなにと接触したか?を判定することです。
そこで登場するのがCategoryBitmaskです。これはオブジェクト毎に番号を割りてておき、接触した時にその番号を利用し接触した相手を判別できるというものです。
では早速その番号を作ってみましょう、このような番号は1,2などの整数で割り当ててもできるのですがそれでは扱いにくのでenum(列挙体)というものを利用します。
まずはPlayer.h
に以下の記述を追記します。
//列挙体で識別子を定義する enum class Object{ Player = 1, Wall = 2, };
これはソースコード上では1という数値の代わりにPlayerという文字で代用できますよという意味です。これでソースコードを読む時に1ってなんだったっけとなる心配もありません。次にPlayer.cpp
に戻りPlayerとWallそれぞれにCategoryBitmaskを割り当てます。赤枠内が追記した部分です。
続いて画像にもそれぞれ列挙体の番号でタグ付けしておきましょう。これは接触時に画像を操作するために必要な作業です。
player->setTag(static_cast<int>(Object::Player)); wall->setTag(static_cast<int>(Object::Wall));
次に接触時に呼ばれる関数の実装をします。ここではPlayer.cppのinit関数に追記します。
少し難易度が高い部分ですがテンプレのようなコーディングですので覚えてしまうのも手です。
//接触時に呼ばれる関数を実装する auto contact=EventListenerPhysicsContact::create(); //接触時に呼ばれる関数 contact->onContactBegin=[this](PhysicsContact& c){ //ShapeAかShapeBの片方がPlayerでもう一方がWall //ShapeAがPlayerだったらShapeBを返し、それでなければShapeAを返すことで接触相手を取得 auto other=(c.getShapeA()->getBody()==this->getChildByTag(static_cast<int>(Object::Player))->getPhysicsBody())?c.getShapeB():c.getShapeA(); //接触相手の情報を取得 auto body=other->getBody(); //Categoryを取得 auto cate=body->getCategoryBitmask(); //壁とあたったら if(cate & static_cast<int>(Object::Wall)){ //画面サイズ取得 auto s=Director::getInstance()->getWinSize(); //タグからプレイヤーを取得し位置を設定する this->getChildByTag(static_cast<int>(Object::Player))->setPosition(Vec2(s.width/2,s.height*2)); } return true; }; //接触判定の適応 this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(contact,this);
今回は説明をシンプルにするためにPlayer.h、Player.cppにまとめて記述しましたが実際には数多くのオブジェクトクラスが存在するので今回の例でしたらWall.h、Wall.cppを作成しそれぞれでオブジェクトの生成をするのが良いでしょう。また接触判定をするクラスに関してはオブジェクトを生成する必要がないのでStage.h、Stage.cppなどの名前で作成しそこで接触判定とPlayer、Wallの生成を行うのが設計としては良いでしょう。
プレイヤー画像提供:臼井の会