敵が一体しか出現しないことにして作っている途中で複数の敵を作れるように変更し、管理クラスを作ってみました。
シーンには床に敵の目的地と中央のCubeがあります。目的地のルートのオブジェクトとCubeは敵のスクリプトにアタッチされています。
敵のスクリプトには、ランダムで目的地の変更を行うUpdate()と、Cubeからの距離を返すメソッドもあります。
using UnityEngine;
using UnityEngine.AI;
public class Agent : MonoBehaviour
{
[SerializeField] Transform points;
[SerializeField] Transform cube;
NavMeshAgent agent;
// Start is called before the first frame update
void Start()
{
agent = GetComponent<NavMeshAgent>();
agent.destination = points.GetChild(Random.Range(0, points.childCount)).position;
}
// Update is called once per frame
void Update()
{
// 目的地に来たらランダムの次の目的地を設定
if (!agent.pathPending && agent.remainingDistance < 0.5f)
{
agent.destination = points.GetChild(Random.Range(0, points.childCount)).position;
}
}
// Cubeからの距離
public float GetDistanceFromCube()
{
return Vector3.Distance(transform.position, cube.position);
}
}
敵を操作するスクリプトには敵がアタッチされていて、キー入力で敵の移動の停止やCubeからの距離の表示をします。
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AI;
public class GameScript : MonoBehaviour
{
[SerializeField] Agent agent;
[SerializeField] Text text;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
// 移動を停止
if(Input.GetKeyDown(KeyCode.E))
{
NavMeshAgent nav = agent.GetComponent<NavMeshAgent>();
if(nav.speed == 0)
{
nav.speed = 3.5f;
}
else
{
nav.speed = 0f;
}
}
// Cubeからの距離を表示
else if(Input.GetKeyDown(KeyCode.R))
{
text.text = agent.GetDistanceFromCube() + "";
}
}
}
敵を複製して、敵の管理クラスを作りました。初めにシーンにいる敵はアタッチしておきます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AgentManager : MonoBehaviour
{
[SerializeField] Agent[] agents;
Dictionary<int, Agent> agentsDic = new Dictionary<int, Agent>();
static AgentManager instance;
public static AgentManager GetInstance()
{
return instance;
}
// エージェントを登録
public void SetAgent(int id)
{
if(agentsDic.ContainsKey(id))
{
Debug.Log("IDが重複");
}
else
{
agentsDic.Add(id, Instantiate(agentsDic[0]));
}
}
// 削除
public void DeleteAgent(int id)
{
if (agentsDic.ContainsKey(id))
{
Destroy(agentsDic[id].gameObject);
agentsDic.Remove(id);
}
}
// Cubeからの距離
public float GetDistanceFromCube(int id)
{
if (agentsDic.ContainsKey(id))
{
return agentsDic[id].GetDistanceFromCube();
}
else return -1;
}
// 移動を停止
public void StopAgent(int id)
{
if (agentsDic.ContainsKey(id))
{
NavMeshAgent nav = agentsDic[id].GetComponent<NavMeshAgent>();
if(nav.speed == 0f)
{
nav.speed = 3.5f;
}
else
{
nav.speed = 0f;
}
}
}
// Start is called before the first frame update
void Start()
{
instance = this;
// 辞書に移す
for (int n = 0; n < agents.Length; n++)
{
agentsDic.Add(n, agents[n]);
}
}
}
管理クラスは静的フィールドにインスタンスを入れて、静的メソッドで他から取得できるようにしています。インスタンスは一つしか作らないことにします。
static AgentManager instance;
public static AgentManager GetInstance()
{
return instance;
}
// Start()
instance = this;
アタッチされた敵はディクショナリーに移します。このキーに指定する整数を受け取って敵を特定し、管理するオブジェクトが代わりにメソッドを呼ぶと良いと思います。
[SerializeField] Agent[] agents;
Dictionary agentsDic = new Dictionary<int, Agent>();
// Start()
for (int n = 0; n < agents.Length; n++)
{
agentsDic.Add(n, agents[n]);
}
例えば、移動を停止する処理をこちらに移して、操作するスクリプトからはキーを受け取って、そのキーが存在すればその敵のスピードを変えます。このスピードを変える部分は敵のスクリプトに書くといいかもしれません。
// 移動を停止
public void StopAgent(int id)
{
if (agentsDic.ContainsKey(id))
{
NavMeshAgent nav = agentsDic[id].GetComponent<NavMeshAgent>();
if(nav.speed == 0f)
{
nav.speed = 3.5f;
}
else
{
nav.speed = 0f;
}
}
}
また、Cubeからの距離を取得するときはキーの整数によって敵を特定して、その敵のスクリプトのCubeからの距離を取得するメソッドを代わりに呼びます。
// Cubeからの距離
public float GetDistanceFromCube(int id)
{
if (agentsDic.ContainsKey(id))
{
return agentsDic[id].GetDistanceFromCube();
}
else return -1;
}
すると、敵のスクリプトを一切変えずに管理クラスを作れました。しかし、すべての敵に目的地やCubeがアタッチされていないといけないので少し問題が起こりました。
新しい敵を後から登録するメソッドを管理クラスに作るときに、敵のプレハブをインスタンス化すると、目的地やCubeが外れているのでエラーになります。そこで、今回は敵が一人は必ずいることにして、一人目の敵を複製するようにしました。
// エージェントを登録
public void SetAgent(int id)
{
if(agentsDic.ContainsKey(id))
{
Debug.Log("IDが重複");
}
else
{
agentsDic.Add(id, Instantiate(agentsDic[0]));
}
}
管理クラスのオブジェクトにアタッチするとか、タグで検索するといいかもしれません。はじめから、プレハブをインスタンス化しても問題ないように作るべきでした。
// 敵を操作するスクリプト
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AI;
public class GameScript : MonoBehaviour
{
[SerializeField] Text text;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
// 1を押しながら
if (Input.GetKey(KeyCode.Alpha1))
{
// 移動を停止
if (Input.GetKeyDown(KeyCode.E))
{
AgentManager.GetInstance().StopAgent(0);
}
// Cubeからの距離を表示
else if (Input.GetKeyDown(KeyCode.R))
{
text.text = AgentManager.GetInstance().GetDistanceFromCube(0) + "";
}
// 削除
else if (Input.GetKeyDown(KeyCode.D))
{
AgentManager.GetInstance().DeleteAgent(0);
}
}
// 2を押しながら
else if (Input.GetKey(KeyCode.Alpha2))
{
// 移動を停止
if (Input.GetKeyDown(KeyCode.E))
{
AgentManager.GetInstance().StopAgent(1);
}
// Cubeからの距離を表示
else if (Input.GetKeyDown(KeyCode.R))
{
text.text = AgentManager.GetInstance().GetDistanceFromCube(1) + "";
}
// 削除
else if (Input.GetKeyDown(KeyCode.D))
{
AgentManager.GetInstance().DeleteAgent(1);
}
}
// 3を押しながら
else if (Input.GetKey(KeyCode.Alpha3))
{
// 移動を停止
if (Input.GetKeyDown(KeyCode.E))
{
AgentManager.GetInstance().StopAgent(2);
}
// Cubeからの距離を表示
else if (Input.GetKeyDown(KeyCode.R))
{
text.text = AgentManager.GetInstance().GetDistanceFromCube(2) + "";
}
// 削除
else if (Input.GetKeyDown(KeyCode.D))
{
AgentManager.GetInstance().DeleteAgent(2);
}
}
// 新しい敵を作る
else if(Input.GetKeyDown(KeyCode.Space))
{
AgentManager.GetInstance().SetAgent(2);
}
}
}