以前、C#で3Dっぽい縦シューティングゲームをつくりました。

これはC# formアプリケーションですが、今回はネットでも遊べるようにThree.jsでつくってみることにします。といってもJavaScriptでつくるのはきついのでTypeScriptでつくります。コンパイルするときにエラーがあることがわかるので安心して使えます。

仕様はかつて作成したものとほとんど変わりません。前作はカメラを前方へ移動させていましたが、今回はカメラの位置は固定です。地面に描画された直線をうごかして戦闘機が前方へ移動しているように見せかけます。

自機以外はスプライトをつかって描画します。自機は水平な板の上にテクスチャを描画します。自機だけスプライトを使わないのはスプライトだと不自然に見えてしまうからです。カメラは自機の後方やや上方にあるので水平な板の上にテクスチャを描画するほうが自然にみえます。

作成するクラスは

敵タイプ1とタイプ2、ボス敵の動作を管理するEnemy1クラス、Enemy2クラス、Bossクラス、これらの基底クラスであるEnemyクラス

自機と敵が発射する弾丸を管理するJikiBurretクラスとEnemyBurretクラス

爆発を管理するExplosionクラス

これらの基底クラスであるGameCharacterクラス

自機の動作を管理するJikiクラス、

それからサウンドを再生するためのPlaySoundeffect、PlayBattleBGM、PlayBattleBossBGクラスを作成します。

それではGameCharacterクラスを示します。

弾丸や敵キャラが共通してもつものとして存在する座標、移動している方向、ライフがあります。またスプライトをつかった描画をするのでその生成に必要なマテリアル、マテリアルを取得するメソッド、Sceneへの追加と除去の処理があります。これらをまとめたのがGameCharacterクラスです。

次に敵の移動を管理するためのEnemyクラスを示します。ゲームにおける敵の特徴は撃墜すると得点できることです。またGameCharacterクラスと同じ機能を必要としています。そこでGameCharacterクラスを継承してEnemyクラスをつくります。

はい、これだけです。これを継承して他のタイプの敵をつくります。

Enemy1クラスを示します。この敵は3回弾丸を命中させないと死なない敵です。左右ジグザグに移動しながら接近してくる敵です。弾丸を発射するかはときどき乱数をつかって決めています。

最初の出現場所のY座標は30とし、X座標は乱数で決めます。また最初の横の移動を右左どちらにするかも乱数で決めます。

ボスを倒すとステージクリアなのですが、ステージがあがるにつれてだんだん動きがはやくなります。また弾丸を撃つ頻度もあがっていきます。

Enemy2クラスを示します。この敵は1回の命中で死ぬ敵ですが、近くまで近づいてきて3発の弾丸を同時発射し、上方へ退避するという嫌な動き方をする敵です。

タイプ1の敵と同様、最初の出現場所のY座標は30とし、X座標は乱数で決めます。また最初の横の移動を右左どちらにするかも乱数で決めます。そしてステージがあがるにつれて動きもはやくなります。

硬い敵ではないのですが、接近後上方へ退避するという動きは撃墜しにくいです。撃墜時の点数はタイプ1と同じ40点にしています。

敵キャラの最後にBossを示します。こいつを倒すとステージクリアです。上方から降りてくるように登場し、左右に移動しながら円状に大量の弾丸をばらまきます。またlifeが低下してくると雑魚キャラも戦いに参戦します。ボスのlifeが0になると大爆発をして雑魚キャラもいっしょに爆発します(ここの処理は後述)。

敵の弾丸の位置や移動を管理するためのEnemyBurretクラスを示します。飛ぶときに少しだけ大きさを変化させます。ただし当たり判定はかわりません。THREE.SpriteMaterialを取得する処理と飛行時のマテリアルの変更以外はたいした処理はしていません。

自機の弾丸の位置と移動を管理するためのJikiBurretクラスを示します。EnemyBurretクラスよりも単純です。

爆発の火球(?)の位置と移動を管理するためのExplosionクラスを示します。スプライトとしてどれを使うかの違いだけで基本的に弾丸の処理とかわりません。

効果音を鳴らすためのPlaySoundeffectクラスを示します。ここで一番大変だったのは気の利いた効果音を探すことだったのかも?

BGMを鳴らすためのPlayBattleBGMクラスとPlayBattleBossBGMクラスを示します。前者は通常の戦闘時、後者は対ボス戦のときのBGMです。戦闘が長引くと音が切れてしまうのと、最後が変なところで終わっているので、最後まで再生しないで適切な場所で巻き戻しています。どこまで再生されているのかを調べて、超えている場合は適切な場所にシークします。

最後にJikiクラスを示します。これはスプライトを使っているわけではないので他のクラスのようにGameCharacterクラスを継承していません。戦闘機のPNG画像からテキスチャをつくって平面にはりつけています。

new THREE.MeshStandardMaterial({ map: texture });ではなく、transparent: true もいれておくのが地味に大切で、これをしないと背景色を変更したときに平面がある部分だけ黒いままで変に目立ってしまうという問題がおきます。

では次回はこれらのクラスをつかってゲームとして完成させます。