libro
www.tuyano.com
初心者のためのCocos2d-xゲームプログラミング入門

物理エンジンを使おう (4/4)

作成:2015-06-20 11:41
更新:2015-06-20 11:41

■衝突イベントとEventListenerPhysicsContact

剛体を持ったスプライトでは、「衝突処理」が非常に重要になります。衝突というと大げさですが、要するに「スプライトが別のスプライトと接触したとき」の処理ですね。

こうした処理も、Cocos2d-xでは「イベント」を利用して行います。イベントというのは、先に画面タッチの処理を実装するのに使いました。ユーザが操作した時などに発生するイベントに応じて処理を実行させるために、イベントリスナーというクラスが用意されていました。

このイベントは、ユーザ操作だけに用意されているわけではなく、プログラム的な動作などでも多用されています。スプライトの衝突検知なども、イベントが利用されているのです。すなわち、スプライトと別のスプライトが接触すると、それに応じたイベントが発生するようになっているのですね。開発者は、このイベントのためのイベントリスナーを用意し、その中で衝突時の処理をよいすればいい、というわけです。では、イベントリスナー利用の流れを以下に整理しましょう。


1. PhysicsBodyに衝突のための設定をする
《PhysicsBody》->setCategoryBitmask( 整数 );
《PhysicsBody》->setContactTestBitmask( 整数 );
まず、イベントリスナー準備の前に、それぞれのSpriteに設定するPhysicsBodyに設定をしておきます。

setCategoryBitmask」は、カテゴリを設定するためのものです。引数で指定した番号のカテゴリにジャンル分けします。

setContactTestBitmask」は、どのジャンルのものと衝突が発生するかを指定するものです。この引数に指定したカテゴリのものとぶつかると、衝突イベントが起こります。

つまり、setCategoryBitmaskで「自分はどのカテゴリに属するか」を指定し、setCategoryBitmaskで「どのカテゴリのものと衝突するか」を指定することで、そのPhysicsBodyがどんなものと衝突するかが設定される、というわけです。


2. EventListenerPhysicsContactインスタンスを作る
auto 変数 = EventListenerPhysicsContact::create();
衝突イベント用のイベントリスナーを用意します。これは「EventListenerPhysicsContact」というものを使います。


3. イベント処理を設定する
《EventListenerPhysicsContact》->onContactBegin 
    = [this](PhysicsContact &contact)->bool {……}

《EventListenerPhysicsContact》->onContactPreSolve
    = [this](PhysicsContact &contact)->bool {……}

《EventListenerPhysicsContact》->onContactPostSolve
    = [this](PhysicsContact &contact)l {……}

《EventListenerPhysicsContact》->onContactSeparate
    = [this](PhysicsContact &contact) {……}
EventListenerPhysicsContactに用意されているイベント用のプロパティに関数を設定します。EventListenerPhysicsContactには4種類のイベント用プロパティが用意されています。それぞれ必要なものに処理用の関数を設定しておきます。

onContactBegin――接触した際のイベント
onContactPreSolve――接触する直前に発生するイベント
onContactPostSolve――離れる直前に発生するイベント
onContactSeparate――離れた際のイベント


4. イベントリスナーを組み込む
this->getEventDispatcher()->
    addEventListenerWithSceneGraphPriority(《EventListenerPhysicsContact》, this);
EventDespatcherに、EventListenerPhysicsContactを組み込みます。これはタッチ時のイベント処理などと同じですね。addEventListenerWithSceneGraphPriorityで組み込むだけです。


――では、実際の実装例を以下に挙げておきましょう。実行すると、キャラクタのスプライトが重力でしてに落ちてきて、床に触れた途端に消滅します。ここでは、onContactBeginを使って処理を行っています。
auto nodeA = contact.getShapeA()->getBody()->getNode();
auto nodeB = contact.getShapeB()->getBody()->getNode();
イベントが発生したスプライトは、このようにして取得します。といっても、Spriteではなく、Nodeインスタンスとして取り出しています。Nodeは、Spriteなどのウィジェットのスーパークラスです。つまりここで行っているのは、

contact――衝突の情報を管理するPhysicsContactインスタンス
getShapeA/getShapeB――衝突したPhysicsShape(物理的な形状オブジェクト)を得る
getBody――PhysicsShapeからPhysicsBodyを得る
getNode――PhysicsBodyから、この剛体が組み込まれているNodeを得る

――こんな感じで必要な物を取り出していたのですね。これで、衝突した双方のウィジェットがNodeとして取り出せます。
nodeA->removeFromParent();
ここでは、そのうちの1つを消しています。removeFromParentは、そのNodeが組み込まれている親から、そのNodeを取り除くものです。これで、そのNodeがレイヤーから取り除かれ、画面から消える、というわけです。

とりあえず、これで「スプライトどうしの衝突」の処理が行えるようになりました!

※プログラムリストが表示されない場合

AddBlockなどの広告ブロックツールがONになっていると、プログラムリスト等が表示されない場合があります。これらのツールをOFFにしてみてください。

●プログラム・リスト●

#pragma execution_character_set("utf-8")

#include "HelloWorldScene.h"

USING_NS_CC;

Scene* HelloWorld::createScene()
{
    auto scene = Scene::createWithPhysics();
    PhysicsWorld* world = scene->getPhysicsWorld();
    world->setGravity(Vec2(0, -500));
    world->setSpeed(1.0f);
    world->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

bool HelloWorld::init()
{
    if (!Layer::init())
    {
        return false;
    }
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    Director::getInstance()->setDisplayStats(false);
    
    auto material = PHYSICSBODY_MATERIAL_DEFAULT;
    material.density = 1.0f; // 密度
    material.restitution = 0.1f; // 反発係数
    material.friction = 0.5f; // 摩擦係数

    sprite1 = Sprite::create("base.png");
    sprite1->setPosition(origin.x + visibleSize.width / 2, origin.y + 100);
    this->addChild(sprite1, 1);

    auto baseBody = PhysicsBody::createBox(sprite1->getContentSize(), material);
    baseBody->setDynamic(false);
    baseBody->setRotationEnable(false);
    baseBody->setCategoryBitmask(1);
    baseBody->setCollisionBitmask(1);
    baseBody->setContactTestBitmask(1);
    sprite1->setPhysicsBody(baseBody);

    sprite1->setScaleX(2.0);
    sprite1->setRotation(10.0f);

    sprite2 = Sprite::create("character.png");
    sprite2->setPosition(origin.x + visibleSize.width / 2, origin.y + 300);
    this->addChild(sprite2, 1);

    auto charBody = PhysicsBody::createCircle(sprite2->boundingBox().size.width / 2, material);
    charBody->setMass(10.0f);
    charBody->setDynamic(true);
    charBody->setRotationEnable(true);
    charBody->setCategoryBitmask(1);
    charBody->setCollisionBitmask(1);
    charBody->setContactTestBitmask(1);
    sprite2->setPhysicsBody(charBody);

    auto listener = EventListenerPhysicsContact::create();
    listener->onContactBegin = [this](PhysicsContact &contact)->bool
    {
        auto nodeA = contact.getShapeA()->getBody()->getNode();
        auto nodeB = contact.getShapeB()->getBody()->getNode();
        nodeA->removeFromParent();
        
        return true;
    };
    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);


    return true;
}
※関連コンテンツ

「初心者のためのCocos2d-xゲームプログラミング入門」に戻る