はじプロにおける2Dシューティングゲーム実装について

# 自己紹介
kuwa と申します。
普段はWEB業界でエンジニアをしています。個人では[vkdb.jp](https://www.vkdb.jp/)というサイトを運営していて、来年20周年を迎えます。
はじプロのプログラマーIDは `P004 FFW G82`

# 2Dシューティングゲーム実装について
私はNintendo Swith用ソフト[『ナビつき！ つくってわかる はじめてゲームプログラミング』](https://www.nintendo.co.jp/switch/awuxa/)において、数本のゲームを開発、公開してきましたが、

https://kuwavkdb.hatenablog.com/entry/2021/10/03/013052

その中でもシューティングゲーム2本の開発が終わった時に、満足の行くゲームができたなという実感がありました。
その2本の開発時に心がけた事や、具体的な実装について解説していきたいと思います。

※それ以降、自分の作品に対するハードルがあがり新作が完成に至らなくなっているのは別の話……。

:::note warn
**おことわり**
- 「はじプロのフリープログラミングでなんらかのゲームを開発した事のある人」を読者として想定しているため、はじプロ特有の用語を説明なく使用しています。
- 例えば「[おめくり式](https://www.youtube.com/watch?v=g4WopfoYfp8)」のような「公式に公開された情報からは得られない高度な技術」については触れません。
- 実装方針や実装方法はあくまで一例であり、これがベストな手段であるといったものではありません。
:::

## 基本方針
本稿では2本のシューティングゲーム開発をとりあげていますが、まず共通する考え方について。

### どんなゲームを作る？
自分が好きなゲーム、遊びたいゲームを作るのが大前提です。
- 2Dの中で多重スクロールによって奥行きが表現されたゲームが好きです。これを実装します。
- ステージが進む毎にダイナミックに背景が転換していくゲームが好きです。当時はじプロでそのようなゲームは見たことがなかったので、その実現を目指します。
- プレイヤーがその場その場で状況を判断して攻撃を掻い潜りつつ敵を倒すようなゲームを目指します。
- なぜミスをしたかわからない、初見殺し、わかっていても避けようがない、運次第、のような理不尽さは好きではないので、そうならないようなるべく気を配ります。

### 当たり判定
シューティングゲームでは通常、敵や敵の弾を避け続ける必要があります。避けたつもりがダメージ判定になるようではプレイヤーにストレスを与えてしまうため、多くのゲームでは自機の見た目よりも当たり判定を小さく設定されています。
はじプロでも同様に、自機の見た目を担うモノノードン、それより小さいサイズのさわっているセンサーやおしゃれなモノを連結して接触判定に使います。これは縦スクロールでも横スクロールでも同様です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2458/aa9621a4-1f30-8d81-11da-b0575a353a31.png" width=300 title="左図: 自機ノードンそのもので当たり判定を担うと、敵弾が当たらない様に見える。
右図: 自機ノードンより小さいサイズで当たり判定を担う事で、見た目に近くなる。">

### 無敵時間
敵や敵弾と接触した時、特別な制御を何もしないと当たり判定が連続で発生し、残機が一気に減ってしまいプレイヤーに理不尽さを感じさせてしまいます。被弾時は自機の当たり判定を一時敵に無効にして、連続で被弾する事を防ぎます。
また、この時自機を点滅させたり半透明（風のテクスチャ）にするなど、一時的に無敵である事がプレイヤーにわかるようにします。

### ゲーム画面とキャラクター
はじプロの3D空間に実際にモノノードン等で背景を構築すると、はじプロ空間の大きさの範囲内でゲームを作成する必要がでてくるため、ステージの長さに上限が生まれます。ステージの長さが決まっていると、スクロールのスピードを上げれば上げるほどすぐにステージが終わってしまいます。それだけが理由ではありませんが、以下のゲームではゲーム画面ノードンを固定し、背景をフリースライドノードンで動かす事で画面スクロールする方法を取っています。
また、自機や敵機等のキャラクターも、フリースライドノードンを使用して動かしています（例外あり）。

### 難易度
上級者でなければクリアできないようなゲームではなく、頑張ればなるべく誰でもクリアできる難易度を目指します。そうは言ってもスキルは人それぞれなのであくまで難しくなり過ぎないように注意しながら。
被弾するとスコア的にペナルティを課すなどするかわりに機数は無限にしてしまうのも一つの手段です（この場合、残機の管理やゲームオーバー処理が不要になる副次的なメリットがあります）。
また、ゲーム進行に合わせて徐々に難易度が上昇するようにします。

:::note info
**難易度上昇のアプローチ**
下記の例では、徐々に敵の数を増やす、徐々に敵のバリエーションを増やす、倒せない敵を増やす（隕石など）の他に、後半になると敵がより画面の両端に寄るようにするなどを採用しています。
:::

## 事例1. 横スクロールシューティング
[きゃべつ杯Jan2022](https://geocities.imawamukashi.com/jan2022.html) にエントリーした「LEGACIC MIRAGE（レガシック ミラージュ）」の開発について。

:::note info
**モチーフ**
「P-47」（NMK）、「USAAF ムスタング」（NMK）、「エアバスター」（カネコ）といった1990年前後のアーケードゲームをイメージして開発しましたが、そもそも原作に近い表現ができるとは思っていないため、「再現」ではなくあくまで「当時のゲームっぽい何か」を目指しました。タイトルの「LEGACIC MIRAGE」もそういった意味合いです。
:::

### 実際に開発したゲームの紹介動画

<iframe width="560" height="315" src="https://www.youtube.com/embed/hX8cWIKKLCQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" loading="lazy" allowfullscreen></iframe>

ゲームID: `G 005 NH9 PR2`

### 多重スクロール実装のアプローチ
まずゲーム画面ノードンを固定、モノ発射ノードンに雲や木のテクスチャを貼って、画面の右から左に向かって発射し続ける事で右に向かって進んでいくように見せます。さらに速度と大きさを変えて複数重ねる事で奥行きを表現しました。背景にははじプロの夕方の風景をそのまま使用していますので、流れる雲の色もそれに合わせています。
![スクリーンショット 2022-11-08 22.45.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2458/9f78655e-c3a7-3c0c-e3b2-dfc277454830.png)

### ステージ展開

#### 空→森→空→海
本作は、空→森→空→海→空→宇宙 と背景が転換していきます。
実際には空の下に森を配置していて、最初は空のみが表示される位置にゲーム画面ノードンを配置。ゲーム画面ノードンを徐々に下に移動させていく事で森ステージに移行します（自機のY座標もそれに合わせて移動させます）。
森ステージでは雲の代わりに「木」のテクスチャを高速に発射し疾走感を演出。
海ステージは地面のテクスチャを水色に切り替え、木の代わりに「波」のテクスチャを高速に発射します。

:::note info
森から海にそのまま移行すると急にテクスチャが変わってしまい違和感があるため、一度ゲーム画面を上に移動させて空ステージに移行して森が見えない状態にし、その間に地上のテクスチャを海に切り替えています。これにより、次に画面を下に移動させたときには違和感なく海ステージに移行できます。
:::

#### 海→空→宇宙
最後の宇宙ステージは当然空の上にあるのが自然です。海から空への移行は先程までの説明と同じですが、宇宙はそうはいきません。また、これまでのステージでははじプロの背景を使用していますが、それを宇宙に切り替える事はできません。そこで以下の様な手順を踏みました。
1. 大きなモノノードンに雲のテクスチャを貼って右上から斜めに登場させ、上空の雲に突入したように見せます。
1. ゲーム画面が完全に雲に入ると背景となるモノノードンは灰色一色の状態になります。
1. 雲のテクスチャを停止させます。
1. 背景モノノードンのテクスチャを灰色から徐々に暗い色に切り替えて行きます
1. 最終的にテクスチャを黒に切り替えたら、モノ発射で星のテクスチャを流す事で、宇宙空間となります。

:::note info
モノノードン1枚でゲーム画面全体を覆うには、かなりカメラに近い位置に配置する必要があります。そのままやると自機や敵が見えなくなってしまうので、ゲーム画面自体のZ座標をプラス方向に動かしてズームアップしたような演出をする事で回避しています。
:::

#### しくみデータ
これらの仕組みを抜き出したしくみデータをアップロードしていますので、詳しい実装についてはそちらをご覧ください。
ゲームID： `G 007 VYV H7N`

## 事例2. 縦スクロールシューティング
前作に続いて作った縦スクロールシューティングゲーム「VERTICAL VESTIGE（バーティカル ヴェスティージ）」について。

:::note info
**モチーフ**
特に特定のゲームをイメージしたわけではないのですが、ステージ構成はシューティングゲームでよくある（気がする）シチュエーション（地上から始まり宇宙へ向かう）を表現しています。
実は元々縦シューを作ろうとしていたわけではなく、後にステージ2の背景となる「上空から見たアマゾン」的な背景から作り始めましたが、この背景は、「グラナダ」（ウルフチーム）にインスパイアされたものです。
:::

### 実際に開発したゲームの紹介動画

<iframe width="560" height="315" src="https://www.youtube.com/embed/ZJoPiZyAKLc" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" loading="lazy" allowfullscreen></iframe>

ゲームID: `G 005 WD0 L1M`

### 多重スクロールのアプローチ
#### ベルトコンベア方式
本作では、ベルトコンベア方式（と、個人的に呼んでいるだけ）で背景をスクロールさせるアプローチを取っています。これを2層重ね、さらにLEGACIC MIRAGE同様にモノ発射を重ねる事で多重スクロールを表現しています。
<!-- <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2458/32b686a6-bde3-137e-5aae-a38059a0b48b.png" width="200" ALT="スクロールイメージ"> -->

https://twitter.com/kuwavkdb/status/1495048783431831553

##### 3つのモノノードンを並べてスムーズにスクロールさせる
- ゲーム画面ノードンの位置を固定し、テクスチャを貼ったモノノードンをフリースライドを使ってスライドさせる形でスクロールを表現します。
- カウンターを３つ用意。
- 各カウンター初期値をループ範囲の1/3ずつずらして設定。
- 定数を入力してループし続けるカウンターをマッピング経由でフリースライドに接続し、三枚の直方体をベルトコンベアっぽく動かします。
![スクリーンショット 2022-11-22 23.16.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2458/9fc16b27-f36a-aec6-8071-8ba636e16c8e.png "フリースライドを使ってベルトコンベア方式")


:::note warn
上記の図では省略していますが、ゲーム画面を通り過ぎた直方体のZ座標をそのままループ最小にしてしまうと、背景が上部ぶ戻っていく過程が見えてしまいますので、ゲーム画面を通り過ぎたら一定時間テクスチャをOFFにしてその間に元の位置に戻す等の制御が必要です。
:::

:::note info
**なぜ3枚？**
- モノノードンのサイズはゲーム画面とほぼ同じ大きさとします。それ以上大きくする（＝ゲーム画面を小さくする）とグラフィックが荒過ぎ、小さくすると縦横のサイズが足らなくなり必要なモノノードン＋テクスチャが4倍以上に増えてしまいます。
- １枚だけだと少しスライドしただけで背景が見切れてしまいます。
- 2枚だとスライドして画面から出た瞬間にすぐにまた反対側に移動させる必要があり表示が間に合いません。
- 3枚あれば途切れる事なくスクロールし続ける事ができます。
:::

##### ベルトコンベア方式のメリット
ステージの背景テクスチャを3枚用意してループさているため、ステージの長さに制限が生じません。本作でステージごとに一定時間が経過したら次ステージに進むようになっているように、ゲーム自体の進行は止めたまま背景をスクロールさせ続ける以下の様な演出が可能になります。
- プレイヤーがボスを倒すまで背景をループさせる（本作ラスボス）。
- セリフなどでストーリーを展開させるようなタイプの作品の場合、セリフ表示している間は背景をループさせておき、セリフを読み終わったら次に展開させる。


:::note info
**応用**
上記の仕組みを応用して、スペースコロニーを再現したりもできます。
以下のゲームは本稿で紹介している2作品とは違い実際にはじプロ空間を右に進んでいくのですが、ベルトコンベア方式で背景を縦に流す事でスペースコロニーを表現しています。
![スペコロ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2458/34ab021d-2bb6-2d70-f817-b88ada6b38c4.gif "スペースコロニー")
ゲームID: `G 000 PYJ 17M`
:::

#### もっと奥行きを
ベルトコンベアを2つ重ねるだけだと2重スクロールにしかなりませんが、モノノードンに厚みを持たせて表と裏を使うことでさらに多重度を増やしたり、下層ベルトコンベアの下に固定背景を配置する事で更に奥行きを出すことができます。

#### ステージ転換問題
ステージ1の背景（海）からステージ2の背景（森）へのテクスチャ変更の際、そのまま切り替えてしまうと海だった背景が突然森に切り替わる事となり違和感が生じます。その対策として、最上部の背景よりさらに上にゲーム画面よりも大きな雲を流して「海」を見えなくした状態でテクスチャを「森」に切り替える事で、転換の瞬間を見えないようにしています。
![ステージ境目loop.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2458/9d65c2f8-72fd-f416-1923-d1f5386e4542.gif "ステージ境目")

:::note info
ゲーム画面を覆った段階で「大きな雲」は一定時間停止しているのですが、単なる灰色一色となっているためプレイヤーには止まっているようには見えないハズです。
:::

### 敵の実装
#### 基本的な考え方
雑魚敵の実装も、基本的には背景と同じ方法論です。マッピング経由でフリースライドのZに入力されるカウンターに対して定数を入力し続ける事で画面の上から下に移動し続けさせます。
画面外に出るか、攻撃が当たった時にカウンターをリセットして元の位置に戻します。

:::note warn
Z座標用のカウンターをリセットする際、そのままリセットするだけだと高速に画面上に移動する途中で自機の当たり判定が反応してしまう可能性があります。
Y軸をずらして当たり判定を回避しつつ、戻る過程が見えないようにテクスチャをOFFにするなどの対処をする事が望ましいです。
:::

#### 動きのバリエーション
上記の実装ではただ単に上から下にまっすぐ移動するだけです。これに対してX：Y座標の値を加減算する事でいろいろな動きを作ることができます。
1. -180〜180のカウンターに定数を入力し続け、これを「角度を位置にノードン」に入力。X座標、Y座標をフリースライドのXとZに入力。
1. -180〜180のカウンターに定数を入力し続け、これを「角度を位置にノードン」に入力。X座標をフリースライドのXに入力。
1. 二次関数、三次関数、その他etc.とマッピングを組み合わせるといろいろできると思います。

:::note info
フリースライドに入力する前にマッピングを経由して値を調整する事で曲線を横長にしたり縦長にしたり等できます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2458/e00dc7b4-fab9-1c32-eefb-81a8590bf37a.png" width="160"  />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2458/ff73e032-3fc5-05b2-02af-f3e4626b2afb.png" width="320" ALT="背景色に埋もれない敵弾" TITLE="背景色に埋もれない敵弾" />
:::

#### 敵弾について
敵が放ってくる弾（以下「敵弾」）については雑魚敵とは異なり、フリースライドではなくモノ発射ノードンを使用しています。以下の2種類を織り交ぜて使用することで、見た目の弾数の割には緊張感のある弾避けが楽しめるように調整しています。
- 自機の位置とは無関係に発射されるもの
- 自機の方向に対して発射されるもの

#### 敵弾見づらい問題
本作は海→森→大気圏→宇宙 というステージ構成になっています。これをざっくり背景色で表すと、<font color="cyan">■</font>水色→<font color="green">■</font>緑→■黒→■黒 となります。背景色がガラッと変わることで注意しなければならないのが視認性です。特に敵弾は雑魚敵と比べてサイズが小さくスピードも速いため普通の球を使うととても見づらくなります。シューティングゲームにおいて敵弾を避けることは非常に重要な要素ですので、ストレスなく弾避けを楽しんでもらうために対策をします。

##### 敵弾にテクスチャ
多くのアーケードシューティングゲームに倣って、目立つ色を2色（赤と黄色がオーソドックスだと思います）使ったテクスチャ、更にその2色を反転させたテクスチャ、この2枚を一定速度で切り替え続ける事で、今回のステージ構成であれば十分な視認性が得られ、プレイヤーは弾除けに集中することができます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2458/dccc3452-0b45-405d-1b4c-75f360d5a91c.gif" width="80" ALT="背景色に埋もれない敵弾" TITLE="背景色に埋もれない敵弾" />

## 本稿で触れなかった事柄
以下は機会があればまた。
- ボスの実装について
    - LEGACIC MIRAGEの列車ボス
        - 特定のタイミングで登場し、部位破壊で後退、急所が破壊されるまで前進後退を繰り返す。
    - VERTICAL VESTIGEのラスボス
        - 一定のダメージ、または一定時間経過で第二形態に移行。一見回避不可能な速度で攻撃しつつも一定間隔でプレイヤーが回避する為の「隙」を発生させる。
- 自機の表現方法
    - LEGACIC MIRAGEではスティック入力に合わせたテクスチャ変更
    - VERTICAL VESTIGEではスティック入力に合わせてノードンを傾ける
- グレイズ（BUZZ）について
    - スコアアタック、リスクとリターン
- タイトル画面について

# あとがき
はじプロで２Dシューティングゲームがもっと増えると良いなと思っていますが、作りたいものを作りたいように楽しみながら作るのが一番です。本稿がこれから２Dシューティングゲームに挑戦してみたいと思われた方の試行錯誤の一助になれば幸いです。