ホーム < ゲームつくろー! < IKD備忘録

Cocos2d-x
パネルをバチン!と設置

(2015. 1. 28)


 ゲーム内では○と×のパネルを置いて行きます。一番簡単なのは画面内の指定の場所に○×の絵を描画するだけ。でも、やっぱりそれはつまらない訳ですよ。「らしく」作ってみます。



@ 置いた時のイメージを膨らます

 今回のパネルは金属製のイメージ。それを盤に置くのであれば、やっぱり「ガキーン!」とか「バチーン!」とか置きたいです。置いた時にパネルの周囲が眩しく光って、あと盤と擦れるだろうから、火花が散ると派手だなぁー。一度置いたらもうネジ止めされて動かないようにしたいので「ネジを回す」ような表現が合って良いかも。

 こんな感じで色々イメージするのが最初は大切。ここから必要そうな個々の技術を洗い出していくわけです。



A 必要な技術の洗い出し

 まず、パネルを盤に「ガキーン!」と置く。「パチン」じゃなくて「ガキン」。パネルにはちょっと重みがあるんでしょうね。そして、少し高みからはめ込む感じかなと。2Dなので高さ表現って難しいのですが、良くやるのは「大きめのスケールを徐々に小さくして元の大きさにする」ことで遠近感を擬似的に表現する演出。それならCCScaleTo等のアニメーションを使って実現出来ます。接地させた瞬間に「ガキーン」なので、ちょっとだけ揺らすと雰囲気が出るかもしれません。これはCCRotateTo等でZ軸の細かい回転でいけそうです。接地時にはパネルの周囲が光る演出を入れたい。これは「パネルの周囲の形に合わせた光るテクスチャ」を透明→不透明としてパネルの上に加算合成。…お?加算合成か。これが出来るかどうかは後で調べましょう。設置した時には火花も出したいのですが、これは「パーティクル」で表現出来ると嬉しいです。これも出来るかどうか調べます。

 という事で、次のようなシーケンスになりそうです:



B 回転をリピート

 Z軸中心で左右に素早く回転させたい。このシーケンスを細かく見ると「左にa度回転(fフレーム)」→「0度に戻す(fフレーム)」→「右にa度回転(fフレーム)」→「0度に戻す(fフレーム)」これで一巡です。これを何度か繰り返せば揺れる感じになりそうです。

 こういう繰り返すシーケンスはCCRepeatを使うと便利です:

float rotTm = 0.015f, rotAng = 3.0f;

// 回転1回分のシーケンス
CCSequence *rotAct = CCSequence::create(
    CCRotateTo::create( rotTm, rotAng ),
    CCRotateTo::create( rotTm, 0.0f ),
    CCRotateTo::create( rotTm, -rotAng ),
    CCRotateTo::create( rotTm, 0.0f ),
    NULL
);

// スケールの後に回転3回
CCSequence *seq = CCSequence::create(
    CCScaleTo::create( 0.1f, 1.0f ),
    CCRepeat::create( rotAct, 3 ), // 回転リピート
    NULL
);

最初に1回分の回転シーケンスを作ります。次にCCRepeat::createメソッドにそのシーケンスと回数を引数として渡すと繰り返し再生してくれます。



C 接地と同時にスプライトを描画

 接地時に光るスプライトを描画するには「パネルの接地のタイミング」を知る必要があります。上のシーケンスで言えば、最初のスケール変換が終わった後です。そこにコールバックを挟み、コールバック内で光るスプライトを作成し、透明→不透明のシーケンス(CCFadeIn)をアタッチする事にしましょう:

bool GameLayer::init() {
    ....

    CCSequence *seq = CCSequence::create(
        CCScaleTo::create( 0.1f, 1.0f ),
        CCCallFuncN::create( this, callfuncN_selector( GameLayer::flashPanelCallback ) ),
        CCRepeat::create( rotAct, 3 ), // 回転リピート
        NULL
    );
    panel_->runAction( seq );
    this->addChild( panel_ );

    ....

}

void GameLayer::flashPanelCallback( Node* pSender ) {

    // ピカーン!
    auto flash_ = Sprite::create( "flash_panel.png" );
    CCSequence *seq = CCSequence::create(
        CCFadeIn::create( 0.5f ),
       CCFadeOut::create( 0.5f ),
        NULL
    );

    flash_->runAction( seq );

    // 表示位置
    Sprite *parent = (Sprite*)pSender;
    const Rect &rect = parent->getTextureRect();
    flash_->setPosition( rect.size / 2.0f );

    // 加算合成
    BlendFunc blendAdd;
    blendAdd.src = GL_ONE;
    blendAdd.dst = GL_ONE;
    flash_->setBlendFunc( blendAdd);

    parent->addChild( flash_ );
}

CCCallFuncN::createメソッドは引数にNodeクラスを取るコールバックを設定する時に使います。第1引数にコールバックメソッドを持っているオブジェクトへのポインタ、第2引数にコールバックを渡します。コールバックを渡す時にはcallfuncN_selectorマクロを使うと楽です。

 コールバックとして設定したflashPanelCallbackメソッド内では光るテクスチャ(flash_panel.png)を作り、フェードイン/アウトでピカーンと光らせます。表示位置はパネルの中心になります。その為引数のNodeをSpriteにキャストし、テクスチャサイズの半分だけ表示位置をずらしています。どういう事かは下の図をご覧ください:

Sprite::getTextureRectメソッドはテクスチャの表示位置をRect型で返してくれます。そのsizeはテクスチャサイズになるので、それを半分にするとテクスチャの中心座標が取れます。後は上のパネルの絵の周囲を覆うような光るテクスチャを用意して重ねれば出来上がりです。

 続くBlendFuncというのはスプライトの合成方法を決めるパラメータです。.srcはスプライト、.dstは下地です。今回は加算合成をしたいので両方の色を足し算するようにパラメータとしてGL_ONEを設定します。以上でピカーンはとりあえずOK:



D パーティクルで火花

 さらに接地時に周りに火花をパシーンと飛ばすとカッコ良さそうです。Cocos2d-xでパーティクル…。お!ありますね「ParticleSystemクラス」。Cocos2d-xに標準搭載されているパーティクルのようです。使い方は大きく2つ。パラメータを頑張って設定するか、パラメータ設定ファイルを指定するかです。後者の方ですが、コード内に次のようなコメントがありました:

CCParticleSystem.h
/**
    creates an initializes a ParticleSystem from a plist file.
    This plist files can be created manually or with
    Particle Designer: http://particledesigner.71squared.com/
    @since v2.0
*/
static ParticleSystem * create(const std::string& plistFile);

plistファイルというパラメータファイルでパーティクルを作成すると。plistファイルはマニュアルで作る事もできるし、Particle Designerを使っても出来るよと。なるほど、Particle Designerというパーティクルを発生させるアプリがあると教えてくれていますね(Particle Designer)。。早速DLだ〜っと思ったのですが、サイトには「Particle simulation software for Mac OS」。え〜Macだけ〜。しょんぼり…。

 じゃぁ仕方ない、コード内で1からパラメータ設定して作ろうじゃないか!っという話です。作り方はCocos2d-xのオフィシャルマニュアルにまんまありましたので抜粋します: 

// Cocos2d-x v3.x
    auto size = Director::getInstance()->getWinSize();
    auto m_emitter = ParticleSystemQuad::createWithTotalParticles( 900 );
    m_emitter->setTexture( Director::getInstance()->getTextureCache()->addImage( "fire.png" ) );

// v2.x及びv3.xで使用することができます
    m_emitter->setDuration( -1 );
    m_emitter->setGravity( Point( 0, -240 ) ); // v2.xでは CCPoint( 0, -240 )
    m_emitter->setAngle( 90 );
    m_emitter->setAngleVar( 360 );
    m_emitter->setRadialAccel( 50 );
    m_emitter->setRadialAccelVar( 0 );
    m_emitter->setTangentialAccel( 30 );
    m_emitter->setTangentialAccelVar( 0 );
    m_emitter->setPosition( Point( size.width / 2, size.height ) );
    m_emitter->setPosVar( Point( 400, 0 ) );
    m_emitter->setLife( 4 );
    m_emitter->setLifeVar( 2 );
    m_emitter->setStartSpin( 30 );
    m_emitter->setStartSpinVar( 60 );
    m_emitter->setEndSpin( 60 );
    m_emitter->setEndSpinVar( 60 );
    m_emitter->setStartColor( Color4F( 255, 255, 255, 1 ) );
    m_emitter->setStartColorVar( Color4F( 0, 0, 0, 0 ) );
    m_emitter->setEndColor( Color4F( 255, 255, 255, 1 ) );
    m_emitter->setEndColorVar( Color4F( 0, 0, 0, 0 ) );
    m_emitter->setStartSize( 30 );
    m_emitter->setStartSizeVar( 0 );
    m_emitter->setEndSize( 20.0f );
    m_emitter->setEndSizeVar( 0 );
    m_emitter->setEmissionRate( 100 );
    addChild( m_emitter, 10 );

ParticleSystemQuadクラスはParticleSystemクラスの派生クラスで、幾つか機能を加えているそうです。パラメータは見ての通りいっぱいですね(^-^;。パーティクルの詳しい使い方については後でまたじっくり調べるとして、今は火花を出す事だけに集中してみます。

 パーティクルの基本は「どこから」「どっちへ」「どういう初速で」「どういう加速度で」「何を」「いつまで」「いくつ」です:

どこから setPosition
どっちへ setAngle
どういう初速で setSpeed
どういう加速度で setGravity
何を setTexture、setStartSize、setEndSize
いつまで setLife
いくつ setDuration、setEmissionRate

火花を散らすのはパネルの周辺からです。パーティクルは基本的に「点」から発生するので、「線」や「面」から発生させるのはちょっとコツが必要になります。Cocos2d-xのパーティクルは「set****Var()」と後ろにVarが付くメソッドがあります。これは「ばらつき」を設定するメソッドです。ですから線上から発生させたいならsetPositionVarメソッドを用いてばらつき範囲を指定すればいいんです:

float x, y;
em->setPosition( x, y );
em->setPositionVar( x, height / 2 )

上の図の場合、発生する角度は右方向(0度)。でもばらつきが無いと本当にすべてのパーティクルが0度にしか行かなくなって不自然なので45度だけばらつきを付けます:

em->setAngle( 0.0f );
em->setAngleVar( 45.0f )

火花の初速は結構速いはず。初速を決めるsetSpeedには1秒辺りの進む速度を指定します。適当に250くらいかな。ばらつきも適当に:

em->setSpeed( 250.0f );
em->setSpeedVar( 100.0f )

火花の加速度は多分0でいいかなと。速過ぎて加速感を感じられないので付けてもきっと良く分からないでしょう。

火花としての「物」であるテクスチャは丸くて眩しい感じの丸でOK:

背景が黒いですが、これは「加算合成」を想定しているためです。パーティクルの大きさはテクスチャサイズに依存せずsetStartSizeとsetEndSizeメソッドで直接大きさを指定します:

em->setTexture( Director::getInstance()->getTextureCache()->addImage( "fire.png" ) );
em->setStartSize( 15 );
em->setStartSizeVar( 4 );
em->setEndSize( 8.0f );
em->setEndSizeVar( 2 );

火花が発生してから消えるまでの時間(ライフタイム)は極短いはず。0.15秒くらいとしておきます:

em->setTexture( Director::getInstance()->getTextureCache()->addImage( "fire.png" ) );
em->setLife( 0.15f );
em->setLifeVar( 0.05f );

これで個々のパーティクルのパラメータは大体設定出来ました。後は「エミッター(発生器)」としてのパラメータを設定します:

em->setDuration( 0.05f );    // 発生時間
em->setEmissionRate( 1000 ); // 秒間当たり発生数

1秒間当たり1000個くらいの発生頻度にして、発生時間はごく短時間で良いので0.05秒くらいにしておきます。後は発生タイミング時にパーティクル自体をパネルノードに登録すれば準備完了です:

panel->addChild( particle, 10 );

実際は随分トライ&エラーを繰り返すのがパーティクルです。


 ここまででパネルを「ガキーン!」と置く事ができてます。動画を載せようかなと思ったのですが、あまりに面倒なのでやめました(^-^;。キャプチャ画像はこんな感じです:

ただ、まだ1枚のパネルを置いたに過ぎません。レイヤーのメソッドを直接コールバックしているなど「オブジェクト化」が出来ていません。次はその辺かなと思います。