前回までで敵と、敵を出現させるオブジェクトができてだいぶFPSらしくなってきました。しかしこのままでは単に出てくる敵を撃つだけで、クリアもゲームオーバーもないので全然面白くありません。そこでここではクリア・ゲームオーバー条件を設定して実装していくことにします。
今回のFPSのクリア・ゲームオーバー条件
今回FPSを作るにあたっては、どういう条件でクリア・ゲームオーバーにするか少し迷いました。というのもあまり複雑にしてしまうと作るのが難しくなってしまうからです。
そこで結局、次のようなシンプルな条件を設定することにしました。
- クリア条件:ゾンビを一定以上倒す
- ゲームオーバー条件:ゾンビが特定のゾーンに侵入する
まずクリア条件は敵を決まった数だけ倒すという分かりやすいものにしました。他の候補としては「一定時間ゲームオーバーにならないように耐える」というのも考えましたが、それだと守りのイメージがあって「ゾンビスレイヤー」というタイトルに合わないので却下しました。
またゲームオーバー条件については、ゾンビがあるゾーンに侵入したらゲームオーバーという風にしました。このようにした理由は、ゾンビに
- プレイヤーの邪魔をするゾンビ
- ゾーンを狙いに行くゾンビ
という役割分担ができて面白いなと思ったからです。もちろんその他の案として「プレイヤーにHPを設定して、それが0になったら死亡」という方法も考えたのですが、これだと全部のゾンビがプレイヤーめがけて襲いかかることになって単調だと思ったのでやめました。
ゲームオーバーゾーンの作り方
では条件を設定したところで早速それを実装していきます。まずはゾンビが侵入したらゲームオーバーになる「ゲームオーバーゾーン」を作りましょう。
まず空のオブジェクトを作成し、名前を「GameOverZone」にしたらボックスコライダー(Box Collider)をアタッチしてコライダーを適当な大きさにして、さらに「トリガーにする」(Is Trigger)にチェックを入れてトリガーにしておいてください。
そうしたら新しいスクリプト「GameOverZone.cs」を作成して次のC#スクリプトを書いてください。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameOverZone : MonoBehaviour { [SerializeField] GameManager gameManager; void OnTriggerEnter(Collider other) { if(other.gameObject.tag == "Enemy") { StartCoroutine(gameManager.GameOver()); } } }
書けたら先ほど作ったオブジェクトにこのスクリプトをアタッチしましょう。処理内容はいたって単純で、トリガーのコライダーに他のコライダーが入ったときに呼ばれる「OnTriggerEnter」メソッドを使い、敵がこのゾーンに入ったらゲームオーバー処理が呼ばれるようにしてあります。
これでゲームオーバーゾーンができました。
キル数表示UIの作成
次にキル数を表示するためのUIをメインキャンバスに追加します。下の図の赤枠部分のようにテキストを作成して配置してください。
GameManager.csの改造
そうしたらGameManagerを改造していきます。ここではキル数関係の処理のほか、ついでにいくつか他の部分も改造するのですべてのコードを掲載しておきますね。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviour { [SerializeField] int maxScore = 999999; [SerializeField] int maxKill = 100; [SerializeField] Canvas mainCanvas; [SerializeField] Canvas titleCanvas; [SerializeField] Text scoreText; [SerializeField] Text killText; [SerializeField] FirstPersonAIO firstPerson; [SerializeField] FirstPersonGunController gunController; [SerializeField] Text centerText; [SerializeField] float waitTime = 2; [SerializeField] EnemySpawner[] spawners; int score = 0; int kill = 0; bool gameOver = false; bool gameClear = false; public int Score { set { score = Mathf.Clamp(value, 0, maxScore); scoreText.text = score.ToString("D6"); } get { return score; } } public int Kill { set { kill = value; killText.text = kill.ToString("D3") + "/" + maxKill.ToString(); if(kill >= maxKill) { StartCoroutine(GameClear()); } } get { return kill; } } void Start() { InitGame(); } void InitGame() { Score = 0; Kill = 0; firstPerson.playerCanMove = false; firstPerson.enableCameraMovement = false; Cursor.lockState = CursorLockMode.None; Cursor.visible = true; gunController.shootEnabled = false; } public void StartGameByButton() { StartCoroutine(GameStart()); } public IEnumerator GameStart() { Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; firstPerson.enableCameraMovement = true; yield return new WaitForSeconds(waitTime); centerText.enabled = true; centerText.text = "3"; yield return new WaitForSeconds(1); centerText.text = "2"; yield return new WaitForSeconds(1); centerText.text = "1"; yield return new WaitForSeconds(1); centerText.text = "GO!!"; firstPerson.playerCanMove = true; gunController.shootEnabled = true; SetSpawners(true); yield return new WaitForSeconds(1); centerText.text = ""; centerText.enabled = false; } public IEnumerator GameOver() { if (!gameOver) { gameOver = true; firstPerson.playerCanMove = false; firstPerson.enableCameraMovement = true; gunController.shootEnabled = false; SetSpawners(false); centerText.enabled = true; centerText.text = "Game Over"; StopEnemies(); yield return new WaitForSeconds(waitTime); centerText.text = ""; centerText.enabled = false; gameOver = false; yield return SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().buildIndex); } else { yield return null; } } public IEnumerator GameClear() { if (!gameClear) { gameClear = true; firstPerson.playerCanMove = false; firstPerson.enableCameraMovement = true; gunController.shootEnabled = false; SetSpawners(false); centerText.enabled = true; centerText.text = "Game Clear!!"; StopEnemies(); yield return new WaitForSeconds(waitTime); centerText.text = ""; centerText.enabled = false; gameClear = false; yield return SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().buildIndex); } else { yield return null; } } void StopEnemies() { GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy"); foreach (GameObject enemy in enemies) { EnemyController controller = enemy.GetComponent<EnemyController>(); controller.moveEnabled = false; } } void SetSpawners(bool isEnable) { foreach(EnemySpawner spawner in spawners) { spawner.spawnEnabled = isEnable; } } }
主な変更点についてですが、
- キル数をカウントして一定以上になったらクリア
- クリア・ゲームオーバーになったらシーンを再読み込み
- その他、細かい部分の修正
といったことを行っています。
変更したらGameManagerのインスペクタを開いて「Kill Text」のところに先ほど作ったテキストを設定しておいてください。
敵へのダメージ処理の追加
さて、そういえば敵へのダメージ処理をまだ追加していなかったのでここでそれを追加しておきます。FirstPersonGunController.csの「★ここに敵へのダメージ処理などを追加」とコメントしてあるところの下に次のコードを記入してください。
string tagName = hit.collider.gameObject.tag; if(tagName == "Enemy") { EnemyController enemy = hit.collider.gameObject.GetComponent<EnemyController>(); enemy.Hp -= damage; }
敵のコントローラを取得して、ダメージ分だけHPを減らす処理を追加しました。試しに敵を撃ってみてきちんと倒れれば成功です。
次のページ→ステージの作り方1:霧(フォグ)の追加