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

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

【cocos2d】VertexZをやめてZソートで半透明表示の問題を回避

現在開発中のスペハリ風シューティングゲームではcocos2dのVertexZを使って3D空間でのキャラクターの重ね合わせを行っていました。
【偽スペースハリアー】地上物を追加。だが違う。何かが違う! - 夏までにiPhone アプリつくってみっか!
上記の記事にあるとおり、「毎フレームreorderChildするのが重そう」と考えたのがそうした理由です。

ところが、気になる現象が見つかってしまいました。

最近作った爆発エフェクトのキャラクターは半透明部分がかなりの面積を占めるのですが、この半透明部分の奥にある別のキャラクターが欠けてしまうことがあるという現象です。

f:id:takujidev:20140719135422j:plain:w600

上のスクリーンショットの中央部分にある爆発の上側にあるプレイヤーキャラクターの弾や左側にあるテトラポットが不自然に欠けて向こう側が見えてしまっているのがわかると思います。

詳しいことはわかりませんが、VertexZを使うとZバッファという方法で重ね合わせが制御され、不透明、半透明に関わらず、「手前にキャラクターがあるピクセルには奥のキャラクターを描画しない」という処理になるようです。
完全に透明な部分についてはアルファテストという処理を入れる事で重ね合わせが出来ますが、半透明な部分はこの方法では無理なようです。

そこで、cocos2dのzパラメーターをキャラクターのz位置に合わせて毎フレーム更新する方法で重ね合わせをすることにしました。cocos2dはz値が小さい方から順に描画され、手前に何かが描かれているかどうかは考慮しないので半透明部分の重ね合わせも上手く行くはずです。
いわゆる、Zソートという方法です。

このゲームではバッチノードは使っていませんが、1つのノードに全てのキャラクターをaddChildする作りになっています。
したがって、そのノード上で全ての子ノードのzパラメーターをreorderChildで更新します。

- (void)update:(ccTime)delta
{
    for (Sprite3D* child in self.children) {
        [self reorderChild:child z:-child.pos3D.z];
    }
    [self reorderChild:[self getChildByTag:PLAYER_TAG] z:100]; // playerは特別扱いで一番手前に表示
}

このゲームでは視点のz座標が0、スクリーン位置のz座標が100で奥に行くに従ってzが大きくなって行くのですが、cocos2dではzが大きいほど手前に表示されるので、マイナスを付けて値をひっくり返しています。
また、プレイヤーキャラクターは必ず他のキャラクターより手前に表示したいのですが、敵の弾が当ったときはキャラクターと同じz値になり、表示の順番によってはプレイヤーキャラクターの手前に来てしまう事があるので、プレイヤーだけはz位置に関係なく特別に大きい値を入れています。

f:id:takujidev:20140719141550j:plain:w600

このように、爆発の半透明部分の奥にあるキャラクターが欠けずに正しく表示されるようになりました。

さて、気になるパフォーマンスを見てみましょう。

まずは、Zバッファを使った場合:

f:id:takujidev:20140719142128p:plain

Zソートを使った場合:

f:id:takujidev:20140719142212p:plain

パフォーマンスには影響なしです。

安心しました。
https://itunes.apple.com/jp/app/travelshooting-jp-toraberushutingu/id917570972?mt=8&uo=4&at=10laCt
https://itunes.apple.com/jp/app/beecluster-wu-liaono-zongsukurorushutingugemu/id663801586?mt=8&uo=4&at=10laCt