【Unity】汎用的な巡回AIを作る方法 with AI Navigation & ScritableObject

# 自己紹介
Pythonも書かなければならないが、Pythonが嫌いな大学生。Unityしか勝たん！
# はじめに
今回はギリ実用に耐えうるであろう巡回AIを作りたいと思います。
ネットには意外にシンプルを極めたコードが中心で実用的に使うには…というものが多いです。

今回作るのは巡回AIですから、指定した地点をぐるぐる回るものです。

## 前提知識
AI NavigatiionやScritableObjectなどはほかの方が解説されていますし、自分が書くよりそちらのほうが分かりやすいと思います。
そのため、それら２つの基本的な部分については触れません。

# 今回作るもの
ScritableObjectに地点を登録して、その場所を行ったり来たりできるAIです。

# ソースコード

<details><summary>NPCのソースコード</summary>

```C#:NPC.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.AI;

public class NPC : MonoBehaviour
{
    [SerializeField] private NPCObject npc;
    [SerializeField] private NavMeshAgent agent;
    private int nowPositionIndex;

    private float waitTime;

    public void Start()
    {
        RandomList();  
    }
    public void FixedUpdate()
    {
        var hasIndexNext = nowPositionIndex < npc.PassPositionList.Count - 1;
        bool canGoing;

        if (hasIndexNext)
        {
            canGoing = npc.PassPositionList[nowPositionIndex + 1].w <= waitTime;
        }
        else
        {
            canGoing = npc.PassPositionList[0].w <= waitTime;
        }

        var isGoal = agent.remainingDistance <= 0.1f;
        if (isGoal && canGoing)
        {
            Debug.Log("目的地の更新");
            //待ち時間のリセット
            waitTime = 0;

            agent.destination = npc.PassPositionList[nowPositionIndex];
            if(hasIndexNext)
            {
                nowPositionIndex++;
            }
            else
            {
                RandomList();
                nowPositionIndex = 0;
            }
        }
        else if(isGoal)
        {
            waitTime += Time.fixedDeltaTime;
        }

        Debug.Log(waitTime);
    }
    private void RandomList()
    {
        if (npc.shouldRandomMove)
        {
            npc.PassPositionList = npc.PassPositionList.OrderBy(x => Guid.NewGuid()).ToList();
        }
    }
}
```
</details>

<details><summary>ScriptableObjectのソースコード</summary>

```
C#:NPCObject.cs
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu]
public class NPCObject : ScriptableObject
{
    /// <summary>
    /// xyzは座標。wは到着後の待ち時間
    /// </summary>
    [SerializeField] public List<Vector4> PassPositionList = new();
    [SerializeField] public bool shouldRandomMove;
}
```

</details>


## ソースコード解説
> まぁ、相変わらず変数名が汚い部分もありますが、これでもだいぶ考えています。
そろそろどうにかしないと…

ScritableObjectのコードは至ってシンプル。
ランダムに移動するかどうかのbool型変数があってそれ以外は座標＋止まっている時間のVector4です。

ここにVector4を使うかほかの変数を改めて作るかですが、Unity側から操作したいというのとエディター拡張を作るのがしんどいという理由でこうなりました。

### RandamList関数に関して
この関数、処理中にToListという関数を使っています。
これちょっと難しい言葉を使うと、アロケーションが発生するんですよね。
簡単に言うとメモリを無駄に使ってしまうんです。

これあんまりよくないですし、なんならよく使う関数ですから本当に良くないです（語彙力）
代替え案ご存知の方いましたら教えてください。

### AI Navigationについて少し…
`agent.destination`
これはAI Navigationの目的地設定ですね。
`agent.remainingDistance <= 0.1f`
これはゴールしているか否かの判定式です。0.1になっているのは参考にしたサイトがそうだったからという浅い理由です。
近ければいいだろ理論。

# 今後の展開
この程度なら簡単に作れますね。
今後はエディター拡張も含めこの関数が終わったら次のポジションへ、のような形に変えてみようと思っています。

出来たらまた記事にしますね。

# おわりに
特に記載することもないのですが、私がUnity認定試験を受けた話をまとめましたので良ければご覧ください。
というか学生だったら無料で受けれる可能性があります。
↑の文だけ読んでくれたらそれでいいです。広まれ、認定試験

https://qiita.com/AtsuAtsu0120/items/0a1a542b25e10329ac66