2Dシューティングゲームを作りました!学んだことまとめ
今回は、簡単な2Dシューティングゲームを作りました。
Playerを左右に動かして弾を撃ち、Enemyを攻撃します。Enemyも弾を撃ってきて、Playerが敵の弾に当たるとゲームオーバーになります。
さらに、Enemyには体力をつけ、左右にランダムに動くようにしました。
これまでのゲーム制作ですでに移動、当たり判定、Prefab、Tagなどの基本的な機能は使っているので、今回はシューティングゲームを作る中で新しく学んだことや、理解が深まったことを中心にまとめます。
学んだこと
① Scriptを分けると「誰の仕事なのか」が重要になる
制作中、Enemyを一定時間ごとに攻撃させるコードを、間違えてBulletControllerに書いたことがありました。
やりたかったこと自体は、
時間を数える
↓
一定時間たつ
↓
EnemyBulletを作るで合っています。
しかし、この仕事をするのはBulletではなくEnemyです。
そのため、
BulletController
→ PlayerのBulletを動かす
EnemyController
→ Enemyを動かす・EnemyBulletを作ると考える必要がありました。
ここで、コードを書くときには、
「何をするコードなのか」だけでなく、「誰がその仕事をするのか」
を考える必要があると分かりました。
Scriptを分けるということは、単にファイルを分割することではなく、ゲームの中の仕事を担当者ごとに分けることでもあると理解できました。
② ゲーム全体の状態をGameManagerにまとめる
今回、新しくGameManagerを使いました。
EnemyBulletがPlayerに当たるとゲームオーバーになります。
しかし、ゲームオーバーになると影響するのはPlayerだけではありません。
Playerの操作を止めたり、Enemyの攻撃を止めたり、GameOverという文字を表示したりする必要があります。
そこで、
GameManager
├─ ゲームクリアしたか
└─ ゲームオーバーになったかというように、ゲーム全体の状態を一か所で持つようにしました。
たとえばEnemyBulletがPlayerに当たった場合は、
EnemyBullet
↓
Playerに当たった
↓
GameManagerにゲームオーバーを知らせる
↓
GameManager
「ゲームオーバーになった」
↓
PlayerやEnemyもその情報を利用するという流れになります。
PlayerやEnemyがそれぞれ勝手にゲームオーバーを管理するのではなく、ゲーム全体に関係する情報をGameManagerにまとめ、それぞれのScriptがその情報を利用するという形です。
ここでは、一つのObjectだけではなく、複数のObjectに影響する状態を管理する方法を学びました。
③ 「HPを持つ」のはEnemy、「HPを減らすきっかけ」はBullet
Enemyには、
public int hp = 3;としてHPを持たせました。
一方、PlayerのBulletがEnemyに当たったことを最初に知るのはBulletControllerです。
そのため、
EnemyController
→ HPを持っている
BulletController
→ Enemyに当たったことを知るというように、必要な情報を持っているScriptと、その情報を変えるきっかけを知るScriptが別々になっています。
そこでBulletControllerから、
EnemyController enemy = other.GetComponent<EnemyController>();として、当たったEnemyについているEnemyControllerを取得しました。
その後、
enemy.hp -= 1;とすることで、そのEnemyが持っているHPを減らしました。
流れにすると、
BulletがEnemyに当たる
↓
当たったEnemyのEnemyControllerを取得
↓
そのEnemyのHPを1減らす
↓
HPが0になったか確認
↓
0ならEnemyを倒すとなります。
以前にも別のScriptが持つ情報を利用することはありましたが、今回は、「今当たった相手」が持っているScriptを取得して、その相手自身の情報を変更するという使い方を学びました。
④ ランダム移動は「毎回適当に動く」のではなく「目的地をランダムにする」
Enemyを左右にランダムに動かす機能も作りました。
最初に、
targetX = Random.Range(-7f, 7f);として、Enemyが次に向かうX座標をランダムに決めました。
そのあと、現在地からそのtargetXへ少しずつ移動させます。
そして目的地に到着したら、また新しいtargetXをランダムに決めます。
つまり、
ランダムな目的地を決める
↓
その目的地へ少しずつ移動する
↓
目的地に到着する
↓
次のランダムな目的地を決める
↓
また移動するという仕組みです。
ここで重要だったのは、「ランダムに動く」という目的を、そのままコードにするわけではないということです。
「ランダムに動く」を、
① 目的地を決める
② 目的地へ移動する
③ 到着したか確認する
④ 次の目的地を決めると小さな処理に分けることで実現しました。
「Enemyをランダムに動かしたい」という大まかな目的から、実際に必要な処理へ分解していく考え方を学べた部分でした。