WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

「LiquidFun」で遊んでみる

こんにちは。今回ブログを担当する長屋です。

前回に引き続きCocos2d-xにLiquidFunを組み込んで遊んでみたいと思います。
LiquidFunのバージョンが1.0から1.1に更新されました。

☆バージョン1.0から1.1の更新点☆
・iOSのサポート。
・ARM系のCPUに最適化されました。
・JavaScriptのサポート(Emscriptenを利用してLiquidFunを変換)。
・パーティクルの接触判定の追加。
・シュミレーションの安定性と当たり判定の改善。

☆cocos2d-x ☆
http://www.cocos2d-x.org/download

☆LiquidFun☆
https://github.com/google/liquidfun/releases

「liquidfun」のサンプルについて
上記のURLからダウンロードしたファイルを展開し、以下のディレクトリ内のファイルを各環境に合わせてビルドしてください。
  liquidfun-1.1.0/liquidfun/Box2D/Testbed/

環境別実行方法
http://google.github.io/liquidfun/Building/html/index.html

iPhone5上でサンプルを動かしてみましたが、粒子の数が増えるとかなり重たくなります。
モバイル端末で使用する場合は粒子の数を減らしたり精度を落とすなどの工夫が必要かもしれません。

☆遊んでみる☆

猫が乗った車を操縦して水柱の足場を渡り向こう岸につくギミックを作ってみました。

image01

☆車体作成☆
車体とタイヤのボディを生成してジョイントします。
タイヤを回転させるためにb2RevoluteJointDefクラスを使用して結合します。
車体自体は前回同様「PhysicsEditor」を利用して生成したデータを読み込んでいます。
またタイヤはただの円オブジェクトを生成して使用しています。

void CatCar::JointTire(){  
  
    //車体とタイヤをジョイントする  
    //今回はタイヤなので回転ジョイントを生成  
    b2RevoluteJointDef revoluteJointDef;  
    revoluteJointDef.motorSpeed = 0.0f;  //回転スピード  
    revoluteJointDef.maxMotorTorque = 0; //回転トルク  
    revoluteJointDef.enableMotor = true; //回転させるかどうか?  
  
    //フロントタイヤをジョイントする  
    revoluteJointDef.Initialize(catBody, frontTireBody, frontTireBody->GetWorldCenter());  
    b2RevoluteJoint * frontTireRevolute = (b2RevoluteJoint*)world->CreateJoint(&revoluteJointDef);  
  
    //リヤタイヤをジョイントする  
    revoluteJointDef.Initialize(catBody, rearTireBody, rearTireBody->GetWorldCenter());  
    b2RevoluteJoint * rearTireRevolute = (b2RevoluteJoint*)world->CreateJoint(&revoluteJointDef);  
  
}

ジョイントした際に返されるb2RevoluteJointのインスタンスを保持しておくことで回転速度やトルクを変えることができます。
今回は自作スライドパットの値を取得してリアタイヤの回転速度とトルクを変更しています。
またフロントタイヤは慣性に任せて回したいためトルクを0にしておきます。

☆水柱の作成☆

前回と同様パーティクルシステムを使用してパーティクルを生成します。

    void HelloWorldScene::initPhysics(){  
  
        ~~省略~~  
  
        //パーティクル設定  
        b2ParticleSystemDef particleSystemDef;  
        particleSystemDef.radius = 4.0/PTM_RATIO;  
  
        //パーティクルシステムを生成  
        //ボディと同じようにシステム自体はいくつも作成可能。  
        b2ParticleSystem * particleSystem = world->CreateParticleSystem(&particleSystemDef);  
  
    }  
  
    //水柱クラスを生成  
    void ColumnWater::ColumnWater(b2ParticleSystem * particleSystem){  
  
        ~~ 省略 ~~  
  
        //グループ設定  
        //今回はパーティクルを管理する為に生成したので形は指定しない。  
        b2ParticleGroupDef groupDef;  
        b2ParticleGroup * particleGroup = particleSystem->CreateParticleGroup(groupDef);  
  
        ~~ 省略 ~~  
    }

更新処理にて生成したパーティクルを更新&設定した間隔でパーティクルを生成。

void ColumnWater::update(float dt){  
  
    //パーティクル更新処理  
    updateParticles(dt);  
  
    //設定された間隔でパーティクルを生成  
    this->elapsedTime += dt;  
    int count = this->elapsedTime / this->interval;  
    while (count > this->particleCount ) {  
        this->addParticle();  
        this->particleCount++;  
    }  
}  
  
void ColumnWater::updateParticles(float dt){  
  
    //ユーザーデータ取得  
    void ** userData = particleSystem->GetUserDataBuffer() + particleGroup->GetBufferIndex();  
  
    //色リスト取得  
    b2ParticleColor * colorList = particleSystem->GetColorBuffer() + particleGroup->GetBufferIndex();  
  
    //座標リスト取得  
    b2Vec2* posList = particleSystem->GetPositionBuffer() + particleGroup->GetBufferIndex();  
  
    //グループが管理しているパーティクルのデータ更新  
    for(int i = 0; i != particleGroup->GetParticleCount();i++,colorList++,vecList++,userData++){  
        ((Sprite*)(*userData))->setPosition((*vecList).x*PTM_RATIO,(*vecList).y*PTM_RATIO);  
        ((Sprite*)(*userData))->setColor(Color3B((*colorList).r, (*colorList).g, (*colorList).b));  
        ((Sprite*)(*userData))->setOpacity((*colorList).a);  
    }  
}  
  
void ColumnWater::addParticle(){  
  
    //発生起点  
    b2vec origin(800.0f/PTM_RATIO,-10.0f/PTM_RATIO);  
  
    //パーティクル加速度  
    b2vec velocity(0,100.0f);  
  
    b2ParticleDef particleDef;  
    particleDef.flags |= b2_destructionListenerParticle;  
    particleDef.color = b2ParticleColor(100,150,255,200);  
  
    //パーティクルの寿命  
    particleDef.lifetime = 2.0f;  
  
    //グループへのポインタを渡しておく事でそのグループ内で管理する事ができる。  
    particleDef.group = particleGroup;  
  
    //設定した数値の間で発射方向をランダムに設定  
    float rate = (float)rand() / (float)RAND_MAX;  
    particleDef.position.x = origin.x + (width * rate) - (width / 2);  
  
    //設定した数値の間でランダムな位置にパーティクルを生成  
    float rate = (float)rand() / (float)RAND_MAX;  
    particleDef.velocity.x = velocity.x + (swing * rate) - (swing / 2);  
  
    //画像設定  
    Sprite * image = Sprite::create("tex.png")  
  
    image->setPositionX(particleDef.position.x * PTM_RATIO);  
    image->setPositionY(particleDef.position.y * PTM_RATIO);  
  
    //描画担当レイヤに渡す  
    drawLayer->addChild(image);  
  
    particleDef.userData = image;  
    particleSystem->CreateParticle(particleDef);  
  
}

これでパーティクルを生成して水柱のように放出できるようになりましたが、この状態だと生成したスプライトを削除できません。
パーティクルが破棄されるタイミングで通知を受け取れるようにしましょう。
bodyなどが破棄されたときに通知を受け取るための抽象クラスである「b2DestructionListener」にパーティクルが破棄されたときに呼ばれるメソッドが追加されています。

    //パーティクル設定時にb2_destructionListenerParticleフラグを立てる事で破棄されたタイミングで通知されるようになる。  
    void ColumnWater::addParticle(){  
        ~~~ 省略 ~~~  
        particleDef.flags |= b2_destructionListenerParticle;  
        ~~~ 省略 ~~~  
    }  
  
    //ワールドにリスナーをセットしておきましょう  
    //1つのインスタンスにつき一つしか(上書きは可能)リスナーを登録できないので、このシーンをリスナーにしました。  
    void HelloWorldScene::InitBox2d(b2ParticleSystem* particleSystem, int32 index){  
        b2World * world = new b2World(b2Vec2(0, -10.0));  
        world->SetDestructionListener(this);  
    }  
  
    //b2DestructionListenerを継承してこのメソッドをオーバーライドする。  
    //メソッド名がしゃれててかっこいい気がします。  
    //削除される直前のパーティクルを保持しているパーティクルシステムとバッファ内のインデックスが渡されます。  
    void HelloWorldScene::SayGoodbye(b2ParticleSystem* particleSystem, int32 index){  
        void ** userData = particleSystem->GetUserDataBuffer() + index;  
        ((Sprite*)(*userData))->removeFromParent();  
        Sprite * image = static_cast<Sprite*>(*userData);  
        if(image) image->removeFromParent();  
    }

出来上がった物がこちらです。

思った以上に調整が大変でした。
ゲームを作る際は調整部分も意識して作っていった方が良いかもです。

次回もLiquidFunで遊んでみるかもしれません。