5.1さらうどん

@giginetの技術ブログ。ゲーム開発、iOS開発、その他いろいろ

cocos2d-xでPhysicsWorldを使ったときのScene Graphの挙動にハマった話

f:id:gigi-net:20140710105133p:plain

ちょっと物理エンジンを使う必要があって、cocos2d-x 3.0から本体に取り込まれた物理エンジンを使ってみることにしました。

ところが、以前一度使ったことがあったのですが、バグっぽい挙動で数時間ハマって困った。

あれこれ調べてみて無事に解決したのでブログに記しておきます。

以下、3.1環境を想定して読んでください。

問題

今回起こった問題は、「剛体を付けた子ノードが親ノードに追従しない」という問題。

3.x系になってから一度物理エンジンは使ったことがあるけど、スクロール無しで完結するゲームだったので特に困らなかった。

Scene Graphが維持されないという問題は、画面スクロールがあるようなゲームを作るときに大変困る問題だと思う。

問題を検証するために、3.1環境で新規プロジェクトを作成し、HelloWorldSceneを書き換えてみた。

物理エンジンの初期化

Scene* HelloWorld::createScene()
{
    // 物理エンジンを有効にしたシーンを作成する
    auto scene = Scene::createWithPhysics();
    
    // 物理空間を取り出す
    auto world = scene->getPhysicsWorld();
    
    // 重力を設定する
    world->setGravity(Vec2(0, -1));
    
    // デバッグモードを有効化する
    world->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
    
    auto layer = HelloWorld::create();
    
    scene->addChild(layer);
    
    return scene;
}

まず、createScene()メソッドを書き換えて、物理エンジンを有効化します。

2.xと違い、3.xからはPhysicsWorldがSceneに取り込まれたので、Sceneにくっつけてあげます。

ちなみに、公式のドキュメントがあるんですが、3.0α版の時点で書かれたモノのようで、公式ドキュメントなのに、最新版の環境でドキュメントを写すとビルドすら通らないという割と酷い代物なのでご注意を!おそらく最新環境では上記コードが正しいです。

Physics | Cocos2d-x

問題:子ノードが親ノードに追従しない

   
    // add "HelloWorld" splash screen"
    auto sprite = Sprite::create("HelloWorld.png");
   
    // Spriteを剛体にする
    auto body = PhysicsBody::createBox(sprite->getContentSize());

    // ここをコメントアウトすると物理エンジン無しの挙動を簡単にシミュレートできる
    sprite->setPhysicsBody(body);
    
    // 重力を無効化する
    body->setGravityEnable(false);
    
    // 適当なラッパー用のレイヤーを用意する(わかりやすいように色を付けている)
    auto node = LayerColor::create(Color4B::BLUE);
    this->addChild(node);
    
    // 検証のためにラッパーがゆっくり上に上がるようにする
    node->runAction(RepeatForever::create(MoveBy::create(30, Vec2(0, 5000))));
    
    // position the sprite on the center of the screen
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    
    // Spriteをシーンに直接追加せず、ラッパーに追加する
    node->addChild(sprite, 0);
    

こういうコードを書くと、nodeが上に上がる際に、それの子ノードであるspriteが追従して一緒に上がる挙動になるかと思います。

物理エンジン無し(3.1)

f:id:gigi-net:20140710103828p:plain

青いレイヤーがActionで上に移動していき、その子ノードのSpriteが一緒に追従して上っていく。想定された挙動。

sprite->setPhysicsBodyの行をコメントアウトするだけ。

物理エンジンあり(3.1)

f:id:gigi-net:20140710103708p:plain

同じ挙動をして欲しいのに、なぜか親ノードだけが上がっていき、Spriteは動かない。バグっぽい。

ちなみに、ラッパーを作らずに、thisを直接上に動かしても再現するけど、見やすさのためこのような実装にした。

明らかにバグっぽい

どうやら、PhysicsBodyをセットしたノードは親ノードの動きに追従してくれないみたい。

酷い挙動だ。

親ノードに剛体を付けたりしてみたけれど、それでもダメ。

マニアックな問題ではなく、物理エンジンとスクロールを併用しようとすると結構ハマる問題では。

例えば、『AngryBird』や、『LIMBO』のような物理エンジンを載せたプラットフォームアクション的なよくあるゲームを作るだけでも困る。

解決するには

当初は、自分で中のコードを弄って解決していたのですが、3日前(2014/7/7)に出たばかりの3.2rc0のCHANGELOGを見ていたらこんな記述が

   [FIX]           Physics integration: child node can move with its father

cocos2d-x/CHANGELOG at e1b29a8ef61248ec047b17be921e031d0d637904 · cocos2d/cocos2d-x

どうやら3日前に出たバージョンでは直っていたようだ。

3.2rc0

f:id:gigi-net:20140710104753p:plain

3.2rc0に上げたところ無事に動く。よかった!

がんばってdiffを追ってみたところ、2週間前のこのPull Requestで修正されていたようだ。

closed #5614: add transform support for physics by boyu0 · Pull Request #7234 · cocos2d/cocos2d-x

このPR出している人、物理エンジンガチ勢みたいな人で、履歴を見てみると物理エンジン周りに大量のPRを送っている。ありがたい限りである。

テストアプリ

3.2rc0から、付属しているcpp-testアプリに「Physics Transform Test」というテストが追加されていて、そこで挙動を確認できる。

このように、移動だけでなく、親ノードに対して回転や拡縮を行っても子ノードが追従して動くようになった。嬉しい。

f:id:gigi-net:20140710105133p:plain

まとめ

cocos2d-xヤバい。数週間に1回バージョンアップする上に、1回のバージョンアップで大量の変更が取り込まれ、数日前に動かなかった物が今日は動く、みたいなことがよく起きる。

今回発見できた問題はたまたま氷山の一角で、この手のバグは大量にありそうなのでご注意を!