i-school - 3Dレールガンシューティング 手順11
 敵の情報を設定し、制御するためのクラスの作成を行います。

 敵用のゲームオブジェクトを配置しただけでは、それがゲーム内において敵であるのかどうかはプログラムは判断出来ません。
そこで、敵用のゲームオブジェクト用のクラスを作成し、それをアタッチすることで、その敵用のゲームオブジェクトが
プログラム内においても「敵としての役割」を持つように設定します。



手順11 ー敵の作成ー

 新しい学習内容は、以下の通りです。

<学習内容>
 ・enum のみのスクリプト
 ・NavMeshAgent に用意されている変数とメソッド
 ・コルーチンを変数に代入して実行する
 ・ローカル関数の実装例



設計


 NavMeshAgent の機能を利用し、敵をプレイヤーの位置に向かって移動させる機能を実装します。

NavMeshAgent
ナビゲーションと経路探索
ナビメッシュエージェント


他にも記事が多くありますので、自分で調べておきましょう。



 NavMeshAgent 機能の他、敵の情報を管理し、制御させるためのクラスを作成して、敵のゲームオブジェクトにアタッチします。

 通常の敵とボスでは移動方法が異なるため、これらを事前に enum に登録しておき、それを元に NavMeshAgent を機能させるように制御します。
NavMeshAgent の Agents の設定と Bake の方法については割愛していますので、まずは、そちらの準備を行ってください。


敵のゲームオブジェクトの設定を行う


 NavMeshAgent の機能を利用できるようにするため、インスペクターの Add Component ボタンから NavMeshAgent コンポーネントを追加します。
また、Ray による判定を有効にするため、コライダーと Rigidbody も追加します。コライダーの形状は任意ですが、CapsuleCollider がよいでしょう。


<設定例1>



<設定例2>



EnemyMoveType スクリプトを作成する


 敵の移動方法を考えて、事前に種類として enum の列挙子に登録を行っておきます。
これは参考事例です。

 今回は enum のみのスクリプトとして作成しています。
enum のみのスクリプトは、MonoBehaviour クラスの継承がありませんので、ゲームオブジェクトに依存しません。つまり、アタッチできません
ですが、スクリプトを作成した段階で、すべてのスクリプト内で自由に宣言して利用することができます。



 enum ではゲーム内に登場させたい種類の情報を、列挙子(れっきょし)という形で種類を作成できます。
今回はエネミーの移動の種類という情報を EnemyMoveType という名前で作成し、その中に エネミーの移動の種類 を登録しておきます。
これは追加可能な情報ですので、先々に 敵の移動の種類が増えても対応できます


EnemyMoveType.cs

<= クリックすると開きます。


 スクリプトを作成したらセーブします。



 3つ以上の情報を管理する場合には、enum でその種類を登録しておくことをおすすめします
enum を利用する場合、その登録してある列挙子からしか情報を指定できませんので、
例えば、文字列と異なり、指定に際して打ち間違えが発生しませんので、不備の値が入ることも防ぐことが出来ます。

 ゲームの内容に応じた enum を考えて作成して運用します
プレイヤーの状態用(毒、混乱、痺れとか)、アイテムの種類(消耗品、武器、防具、など)、
ゲームの状態管理(ゲーム開始前、ゲーム中、ゲーム終了)など、非常に応用が利く機能です。



 なお enum では各列挙子に自動的に整数の番号が与えられます 一番上から 0 で連番になっています
今回の場合であれば、Agent には 0、Boss _1 には 2 の数字が与えられています。

 この番号は見えない情報ですが、列挙子を int 型にキャストを行うことで取得して利用出来ます
下記の例の場合、enumValue には 1 が代入されます。

<enum の列挙子のキャスト>
int  enumValue = (int)EnemyMoveType.Boss_0;

 また、列挙子の宣言時に数字を指定して代入することも可能です。その場合には連番ではなく、指定した数値を取得出来ます。

<数字の代入の例(今回この方式は利用しません)>
EnemyType.cs
public enum EnemyMoveType {
    Agent = 10,
    Boss_0 = 5,
    Boss_1 = 100
}

 上記のように代入されている場合には、列挙子を int 型にキャストすると、代入してある値が取得出来ます。
今回は数字の代入は行っていませんので一番上の列挙子には 0 から順番に採番されています。


EnemyController スクリプトを作成する


 敵のゲームオブジェクトにアタッチして、敵の情報や、NavMeshAgent の制御を行うためのスクリプトを作成します。
処理の内容が多く、複雑なロジックになっていますので、変数の宣言も含めて、しっかりと読み解けるようにしてください。


EnemyController.cs

<= クリックすると開きます。



<NavMeshAgent に用意されている変数とメソッド>


 NavMeshAgent クラスには多くの変数とメソッドが用意されています。
この機能を上手く活用することにより、AI による NavMeshAgent のルートの自動移動を実装することができます。

 NavMeshAgent 型をスクリプト内で宣言し、各変数やメソッドを利用するためには using UnityEngine.AI; の宣言が必要です。


1.NavMeshAgent.destination 変数

 NavMeshAgent 目標地点を設定することができる Vector3 型の変数です。
この情報をセットすることで、NavMeshAgent に目的地へ移動することを伝え、移動を開始させることができます。

<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent.destination
https://docs.unity3d.com/jp/current/ScriptReferenc...
Unity 公式マニュアル
NavMeshAgent に目的地へ移動することを伝える
https://docs.unity3d.com/ja/current/Manual/nav-Mov...


2.NavMeshAgent.speed 変数

 NavMeshAgent によるルート移動時の最大速度の設定値です。0 にすることで移動は停止します。


<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent.speed
https://docs.unity3d.com/jp/current/ScriptReferenc...


3.NavMeshAgent.ResetPath メソッド

 NavMeshAgent によるルートの設定情報を削除します。
これにより、移動先の地点の情報がなくなるため、再度、目的地の情報が設定されるまで NavMeshAgent は移動を停止します。


<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent.ResetPath
https://docs.unity3d.com/jp/current/ScriptReferenc...


コルーチンを変数に代入して実行する


 以下のページに詳細な説明があります。

 => コルーチンを変数に代入して実行する

 今回のケースでは、敵がプレイヤーを見失ったり、敵の攻撃処理の途中で敵が倒された場合に、攻撃の準備の処理を停止できるようにするためにこの機能を実装しています。

 コルーチンメソッドを実行しているスクリプトがアタッチされているゲームオブジェクトが破棄された場合、
コルーチンメソッドの処理も強制的に停止されます。そのため、主な目的としては、敵がプレイヤーを見失った場合に、攻撃準備処理を停止させることです。

 コルーチンメソッドは非同期処理であるため、それ以外の処理が終わるのを待たずに、コルーチンメソッド内の処理を進めていきます。
便利である反面、コルーチンメソッドの処理が不要になった場合にはしっかりと停止する必要があります。


<変数にコルーチンメソッドを登録して実行>

      private IEnumerator attackCoroutine;   // コルーチンメソッドを登録するための変数


        void SetAttackCoroutine() {

            // 攻撃用のメソッドを代入して登録
            attackCoroutine = Attack(player);

            // 登録したメソッドを実行
            StartCoroutine(attackCoroutine);

            Debug.Log("攻撃開始");
        }



<変数に登録されているコルーチンメソッドを停止>
  StopCoroutine(attackCoroutine);

参考サイト
Qiita @riekure 様
【Unity】非同期処理を理解する〜コルーチン編〜
https://qiita.com/riekure/items/9f59fec68c3e31f38f...


<ローカル関数>


 C# 7.0 以降ではメソッドの内部に入れ子としてメソッドを作成出来るようになっています。
メソッド内部に宣言するメソッドをローカル関数と呼びます。

 メソッド内の入れ子である関数であるため、修飾子の宣言は出来ません(private のみ)が引数も戻り値も持たせることが出来ます。
C# 8.0 以降では、属性を付与したりすることも出来ます。


<抜粋>
    private void OnTriggerStay(Collider other) {
        if (isAttack) {
            return;
        }

        // プレイヤーの情報を保持しており、攻撃中でないなら
        if (player != null) {

            // ローカル関数を実行
            SetAttackCoroutine();
        }

        // ローカル関数を定義
        void SetAttackCoroutine() {

            // IEnumerator 型の変数に攻撃用のメソッドを登録
            attackCoroutine = Attack(player);

            // 登録したメソッドを実行
            StartCoroutine(attackCoroutine);

            Debug.Log("攻撃開始");
        }


 ローカル関数は通常の関数と同じように、実行命令を行うことで実行されます。
ただし、利用する範囲(スコープ)が、ローカル関数が定義されているメソッド内部のみに限られます

 今回のケースであれば、SetAttackCoroutine メソッドを OnTriggerStay メソッド内で宣言していますので、
SetAttackCoroutine メソッドを呼び出す命令は、OnTriggerStay メソッド内でしか実行できません。

 利用方法としては、メソッドの呼び出し命令が特定のメソッド内にのみにあり、かつ、他のメソッドからも呼ばれることのない処理をローカル関数として準備することが一般的です。
利用範囲を制限している分、処理が読みやすくなりますし、エラーも見つけやすくなります。(使い道が限られている処理をメソッド化し、メソッド内部に閉じ込めておくことで可読性が上がります。)

MicroSoft
ローカル関数
https://docs.microsoft.com/ja-jp/dotnet/csharp/pro...
.NET column 様
C#のローカル関数とは?使い方やメリット・ラムダ式との違いも紹介
https://www.fenet.jp/dotnet/column/language/3933/


敵のゲームオブジェクトに EnemyCotroller スクリプトをアタッチして設定を行う


 敵のゲームオブジェクトに EnemyController スクリプトをアタッチして設定を行います。

 lookTarget 変数は None(null) のままで問題ありません。こちらには、ゲームの実行後にプレイヤーの情報を取得するようになっています。

 enemyNo 変数は 0 です。それ以外の、hp、 attackPower、 moveSpeed の各変数については、下記の画像を参考にしながら任意の値を設定してください。


<インスペクター画像>


 設定が完了したら、プレファブにしておきます。
次の手順でもこのゲームオブジェクトは利用するので、ヒエラルキーからは削除せずにおきます。



 以上でこの手順は終了です。

 => 次は 手順12 ー敵へのダメージ判定機能ー です。