読者です 読者をやめる 読者になる 読者になる

夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【偽スペースハリアー】CCActionとupdate:の併用で敵の爆発を演出

cocos2d スペースハリアー TravelShooting JP

前回、
【偽スペースハリアー】超能力ホーミングミサイル発射! - 夏までにiPhone アプリつくってみっか!
ではホーミングミサイルを発射して敵をやっつける機能を追加しましたが、爆発させる処理を入れていなかったのでただ消えるだけの物足りない状態でした。

今回は、敵を派手に爆発させ、空中にいる敵は爆発しながら地上に落下するようにしてみました。
本物のスペースハリアーの爆発はパーティクルは使わず、絵だけで表現しています。

本物をコマ送りで見てみると、爆発が始まってから燃え尽きるまで12枚の絵を切り替えてアニメーションさせています。爆発には後半赤っぽい色になるパターンと灰色っぽい色になるパターンの2種類あり、ダブりの絵を除くと合計15枚の絵をスクリーンショットから切り出しました。(キノコ雲状の爆発は今回は入れていません。)

絵を切り出すのは結構時間が掛かる作業ですが、プロトタイプ用の仮絵を自分で適当に描くのはあえてやめています。

前作のBeeClusterでは仮絵として練習も兼ねて描いた低クオリティの絵を書き直さずに結局本番でも使ってしまい、画風にばらつきが出てしまいました。

意思の弱い自分でもパクリの絵をさすがにそのまま本番には使えませんので、本番用の絵は後でまとめて描く事にしています。

では、動画をどうぞ。

爆発パターンのテクスチャーの切り替えは、
【偽スペースハリアー】CCActionを駆使してやられシーンを演出 - 夏までにiPhone アプリつくってみっか!
でやったようにCCActionのCCCallBlockで行います。

あとは空中オブジェクトの落下の演出なのですが、これをCCActionで実現するのはかなり面倒なことになります。
オブジェクトの座標はゲーム内部では3D空間で管理されています。それをcocos2dのCCMoveBy, CCMoveToの座標に落とし込むには3Dの動きを2D座標に変換したアクションを作らなくてはならないためです。

従いまして、落下の動きはupdate:メソッドで座標を毎フレーム更新する方式で実現する事にしました。
地面のx, z方向の動きに同期して動かす機能は既に実装済みでしたので、そのメソッドにy方向の制御を追加します。「爆発中は空中から地上に落ちる」というコメントより後の部分が今回追加したところです。

- (void)position3D
{
    Scene3D* scene = [Scene3D sharedScene3D];
    Position3D pos3D = self.pos3D;
    CGFloat fakeZ = [scene convertToFakeZ:pos3D.z];
    self.position = [scene screenPositionAtZStillObject:fakeZ finalPosition:pos3D.position];
    self.scale = [scene scaleAtZ:fakeZ];
    pos3D.z = pos3D.z + scene.zSpeed;
    self.vertexZ = -pos3D.z*0.1; // 0.1を掛けてzの値10000くらいまではvertexZに入れられるようにする
    if (self.category & TYPE_SHADOW) {
        self.vertexZ -= 0.1; // 影ならvertexZを少し小さくして本体の背後に表示されるようにする
    }
    if (pos3D.z < [scene screenZ]) {
        for (Sprite3D* part in self.parts) {
            [part removeFromParentAndCleanup:YES]; // 部品をノードツリーから削除
        }
        [self removeFromParentAndCleanup:YES]; // 自分をノードツリーから削除
    }
    pos3D.position.x = [scene scrollX:pos3D.position.x];
    
    // 爆発中は空中から地上に落ちる
    if (self.category & TYPE_EXPLOSION) {
        if (pos3D.position.y > 0) {
            pos3D.position.y += self.speedY;
            self.speedY += self.accelY;
            if (pos3D.position.y < EXPLOSION_Y) { // 地面に着いたか
                pos3D.position.y = EXPLOSION_Y;
            }
        }
    }
    self.pos3D = pos3D;
}

重力加速度を再現するため、一定の速度ではなく、毎フレームaccelYという加速度を前フレーム時点での速度 speedYに加算しています。

話は変わりますが、categoryというインスタンス変数は当初は敵味方判定のチェック用に使用していたのですが、どんどんビットが追加されあらゆる状態を持つようになってしまいました。

typedef enum {
    TYPE_PLAYER         = 1,        // プレイヤー
    TYPE_PLAYER_SHOT    = 1 << 1,   // プレイヤーの弾
    TYPE_ENEMY          = 1 << 2,   // 敵
    TYPE_ENEMY_SHOT     = 1 << 3,   // 敵の弾
    TYPE_OUCH_SMALL     = 1 << 4,   // 小さいOuchになる敵
    TYPE_OUCH_BIG       = 1 << 5,   // 大きいOuchになる敵
    TYPE_EXPLOSION      = 1 << 6,   // 爆発中
    TYPE_SHADOW         = 1 << 7,   // 影
    TYPE_LOCKON         = 1 << 8,   // ロックオン可能な敵
    TYPE_GROUND         = 1 << 9,   // 地上にいる敵
    TYPE_LOCKED         = 1 << 10,  // ロックオンされている中
} ObjType;

多分これからもどんどん追加されて行くと思いますが、この方式は一歩間違えると見つけにくいバグを入れ込む可能性が高いのでおすすめしないです。
これはメモリーが64kbyteしかなかった昭和の時代のプログラミング手法でしょう。

ちなみにビットのテストには上記のように

if (self.category & TYPE_EXPLOSION) {
}

とします。
ビットを立てるには,

self.category |= TYPE_EXPLOSION;

ビットを落とすには

self.category &= ~TYPE_EXPLOSION;

のようになります。
非常にわかりにくいですね。
普通にBOOLのプロパティを使う方が良いと思います。