ゾンビを実装 主人公を追いかける「ゾンビ」を実現しましょう! シーンの設定 ゲームオブジェクトと3Dモデル Timmyと同様に、ゾンビのゲームオブジェクトを作成しましょう。空のゲームオブジェクトを作成し、その下に子オブジェクトとしてZombieのモデルを追加する: ただし、ゾンビは物理的に動かないので、コライダーとリジッドボディが要らない。ゾンビの動かした方については、下記で説明する。 アニメーション 今回は、アニメーションは1つしかなくて、ずっと歩くようにしましょう。ただし、Timmy と同様に「 アバター 」の設定が必要。 そして、ゾンビのアニメーターコントローラーを作成し、「ZombieWalk」のアニメションをデフォルトにする これで実行してみてください(ループを忘れずに~) 自動的に道を探す「ナビゲーション」 人間を制御するキャラクターが、当たり判定を確認するだけで、動きの実装ができる。人間が勝手に障害物などを避けて動く。一方、CPUキャラクターがとても馬鹿であり、障害物を避けたらすることができない。 この問題を解決するには、Unityが「Navigation」(ナビゲーション)を提供する。ナビゲーションとは、自動車のナビと同じように、目的地を設定し、最適なルートを探す処理である。 ナビゲーションは以下の主なコンポーネントを組み合わせて、作成する ナビゲーションメッシュ(NavMesh Surface):ナビの「地図」歩けるところ、歩けないところを指定する エージェント(NavMesh Agent):地図の上に動くもの。一般的に、CPUキャラクターである その他にも、ナビゲーションの影響にあるコンポーネント(NavMesh Link、NavMesh Obstacle、など)もあるが、最低限でこの2つの組み合わせで十分。 ナビゲーションメッシュ(NavMesh Surface) ナビの地図である。 地図を作るのは、シーンを固定してから !シーンが変わったら(壁を増やした、障害物の位置を変えた)、地図も更新しないといけないので、ご注意ください。 まず、地図に使うオブジェクトをグループ化にしないといけないので、シーンを整理し、ゾンビと主人公の間にいくつかの壁を作ってみましょう。空ゲームオブジェクト「Stage」を作成し、中には床面、ランプそして新しい壁を追加してください。 ここで「ナビゲーションメッシュ」を作りましょう。「Stage」を選択し、「NavMesh Surface」を追加してください ここで地図を作る時に必要なパラメータを設定できるが、最も重要なのは、何を基づいて地図を作るのか。これは「Object Collection」から設定ができる。今回は「Stageとその子オブジェクトを使用」にしたいので「Current Object Hierarchy」(現在オブジェクトのヒエラルキー)を選択しましょう。 そして、ナビゲーションメッシュを作成するには「Bake」(焼く)ボタンを押すだけ。このボタンを押すと、Stageとその子オブジェクトのすべてを処理し、歩ける「地図」を水色で表示される: 壁にあまり近づかないよう、周囲に「歩けない」領域が現れる。この地図が焼いているので、 ステージが変わっても地図が変わらない!  1つの壁を削除しても、「歩けない」領域が残る: ナビゲーションメッシュを焼いた後にシーンが変わったら、もう一度「Bake」してください。 エージェント(NavMesh Agent) エージェントは、水色の「地図」(ナビゲーションメッシュ)上で歩くものである。目的地を設定すれば、現在地から最適な道を探し、自動的に動く。 今回のゲームで、ゾンビが動いてほしいので、エージェントにしましょう。ゾンビ(親オブジェクト)に「NavMesh Agent」を追加してください。 ここでエージェントのパラメータを調整ができる。主なパラメータは: Base Offset(基礎オフセット) 足元からの「ずれ」 Speed(速度) 移動速度 Angular Speed(回転速度) 向きを変える速度 Acceleration(加速度) 加速と減速の速さ Stopping Distance(停止距離) 目的地までこの距離になったら止まる Radius(半径) エージェントの「太さ」 Height(高さ) エージェントの「身長」 とりあえず、ゾンビの速度を「1」にし、残りのパラメータをそのままにしましょう。 スクリプト プレーヤーを追いかける スクリプトは、プレーヤーの位置を求め、エージェントの目的地を設定するだけ。「PlayerChase」のスクリプトは以下の通りである: using UnityEngine; using UnityEngine.AI; // プレーヤーを追いかける public class PlayerChase : MonoBehaviour { // 追いかけるプレーヤー private GameObject player; // エージェントの目的地を設定するため private NavMeshAgent agent; private void Start() { // 後ほどプレハブにするので、Unityで紐づけせず、 // 実行中にプレーヤーを探す player = GameObject.Find("Player"); // NavMeshAgentのコンポネント取得 agent = GetComponent(); } private void Update() { // 追いかける // SetDestination はエージェントの目的地を設定するメソッド。 // 引数はその位置である(プレーヤーの位置) agent.SetDestination(player.transform.position); } } このスクリプトを Zombie にアッタッチし、実行してみてください。 Timmyの体力 これで、追いかけるようになったが、ゾンビが主人公を捕まっても、なにもならない。Timmyの体力を実装しましょう。「HitPoints」のスクリプトは以下の通りである: // 体力を管理するクラス public class HitPoints : MonoBehaviour { // 体力 private int hp = 3; } 当たり判定とダメージ ここでトリガーを活用ができる。もし、 HitPoints がダメージの 起源(ゾンビ、罠、炎など)と重なったら、ダメージを受ける。どれぐらい受けるのかをその「ダメージの起源」( DamageSource )で指定ができる まず、ダメージの起源を作成しましょう: // ダメージの起源 public class DamageSource : MonoBehaviour { // ダメージの量 [SerializeField] private int damage; // ダメージの量(読み込む専用) public int GetDamage() { return damage; } } そして、 HitPoints スクリプトは: // 体力を管理するクラス public class HitPoints : MonoBehaviour { // 体力 private int hp = 3; // トリガーに入ったら private void OnTriggerEnter(Collider other) { // ダメージの起源かどうかを確認 DamageSource src = other.GetComponent(); if (src == null) return; // 体力を減らし、0 以下にならないように制限する hp = Mathf.Max(0, hp - src.GetDamage()); } } 確認 確認するには、ゾンビにダメージの起源を追加しなければならない。Zombieに新しい子オブジェクトを追加し、トリガーと DamageSource を追加してください そして、Player に HitPoints を追加し、 Debug.Log で体力を表示してください。 UIの更新(体力) Debug.Log でカッコ悪いので、UIも作成しましょう。画面の左に、3つのハート画像をグループとして配置しましょう そして、 UIManager を更新し、ハートの表示・非表示しましょう。 // UIを管理する public class UIManager : MonoBehaviour { // コインのテキスト [SerializeField] private TextMeshProUGUI coinText; // 鍵のテキスト [SerializeField] private TextMeshProUGUI keyText; // ハートの配列 [SerializeField] private GameObject [] hearts; // 表示したいハートの数 public void SetHearts(int count) { for(int i = 0; i < hearts.Length; i++) hearts[i].SetActive(i < count); } // (省略) } SetHearts は ブロック崩しゲームの「SetLives」 と同じ処理であるが、書き方を短くしただけである。後は、 HitPoints の体力が変わったら、UI を更新するにで、 HitPoints を修正しましょう: using UnityEngine; // 体力を管理するクラス public class HitPoints : MonoBehaviour { // 体力を表示するため [SerializeField] private UIManager uiManager; // 体力 private int hp = 3; private void Start() { uiManager.SetHearts(hp); } // トリガーに入ったら private void OnTriggerEnter(Collider other) { // ダメージの起源かどうかを確認 DamageSource src = other.GetComponent(); if (src == null) return; // 体力を減らし、0 以下にならないように制限する hp = Mathf.Max(0, hp - src.GetDamage()); uiManager.SetHearts(hp); } }