建物の床などナビメッシュが上下に重なってしまうところでは、プレイヤーがいる階のナビメッシュ上の点がただしく取れないときがあるので、階を判別して目的地を探す方法を変えてみました。
まず、階ごとに別々のエリアを設定します。
例えば1階の床のPlaneでは、Navigation Staticのチェックを入れて、Navigation Areaを1階にしました。
プレイヤーのスクリプトでは、WASDでの移動と、適当に高さによる階の判別をしています。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TestPlayer : MonoBehaviour
{
[SerializeField] Text text;
[SerializeField] float speed = 0.05f; // 移動速度
public int Floor { get; private set; } // プレイヤーのいる階
// Start is called before the first frame update
void Start()
{
text.text = "";
}
// Update is called once per frame
void Update()
{
// WASDで移動
if (Input.anyKey)
{
Vector3 dir = Vector3.zero;
if (Input.GetKey(KeyCode.A))
{
dir.x -= 1;
}
if (Input.GetKey(KeyCode.D))
{
dir.x += 1;
}
if (Input.GetKey(KeyCode.W))
{
dir.z += 1;
}
if (Input.GetKey(KeyCode.S))
{
dir.z -= 1;
}
if(Input.GetKey(KeyCode.LeftShift))
{
dir.y += 1;
}
if(Input.GetKey(KeyCode.LeftControl))
{
dir.y -= 1;
}
dir = Vector3.Normalize(dir);
Vector3 pos = transform.position;
pos = pos + dir * speed;
transform.position = pos;
}
// プレイヤーがいる階を判定
if (transform.position.y < 2.5f)
{
Floor = 1;
text.text = $"{Floor}階";
}
else if (transform.position.y >= 13.77f)
{
Floor = 7;
}
else if (transform.position.y >= 11.36f)
{
Floor = 6;
}
else if (transform.position.y >= 9.21f)
{
Floor = 5;
}
else if (transform.position.y >= 7.06f)
{
Floor = 4;
}
else if (transform.position.y >= 4.9f)
{
Floor = 3;
text.text = $"{Floor}階";
}
else if (transform.position.y >= 2.5f)
{
Floor = 2;
text.text = $"{Floor}階";
}
}
}
エージェントのいる階の判別ではコライダーを使いました。コライダーはトリガーにして、Rigidbodyを付けます。
そして、エージェントのスクリプトで目的地を設定するときに、プレイヤーとエージェントが同じ階にいるときには、エリアマスクをかけてからプレイヤーのいる地点を目的地に設定し、別の階にいるときはNavMesh.SamplePositionメソッドを使って、プレイヤーのいる点から最も近いナビメッシュ上の点をサンプルするようにしました。
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
public class TestAgent3 : MonoBehaviour
{
[SerializeField] float maxDistance; // サンプルする範囲
public int Floor { get; private set; } = 1; // エージェントのいる階
NavMeshAgent agent;
[SerializeField] Text text;
[SerializeField] Text centerText;
[SerializeField] GameObject cube; // 目印のCubeのプレハブ
// Start is called before the first frame update
void Start()
{
agent = GetComponent<NavMeshAgent>();
text.text = $"{Floor}階";
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
// 目的地を設定
SetDestination();
}
}
// 目印のCubeを置く
void DisplayPosition(Vector3 pos)
{
Destroy(GameObject.FindGameObjectWithTag("Cube"));
Instantiate(cube, pos, Quaternion.identity);
}
void SetDestination()
{
// プレイヤーのスクリプトを首都行く
GameObject player = GameObject.FindGameObjectWithTag("Player");
TestPlayer playerScript = player.GetComponent<TestPlayer>();
// 同じ階にいるか、エージェントが階段にいるとき
if (playerScript.Floor == Floor || Floor == -1)
{
// エリアマスクを設定
SetAreaMask(playerScript.Floor);
// プレイヤーの現在位置を目的地に設定
agent.SetDestination(player.transform.position);
centerText.text = "目的地を設定";
// 目印を置く
Vector3 pos = player.transform.position;
pos.y = playerScript.Floor == 1 ? 0f : 2.52f;
DisplayPosition(pos);
}
// 別の階にいるとき
else
{
// エリアマスクを設定
SetAllAreas();
// 最寄りのナビメッシュ上の点を目的地に設定
centerText.text = "位置をサンプル";
NavMeshHit hit;
if (NavMesh.SamplePosition(player.transform.position, out hit, maxDistance, GetAreaMask(playerScript.Floor)))
{
agent.destination = hit.position;
// 目印を置く
DisplayPosition(hit.position);
}
}
}
// 設定するエリアマスク
int GetAreaMask(int floor)
{
if (floor == 1)
{
return 1 << 3 | 1 << 5;
}
else if (floor == 2)
{
return 1 << 4 | 1 << 5;
}
else if (floor == 3)
{
return 1 << 6;
}
else if (floor == 4)
{
return 1 << 7;
}
else if (floor == 5)
{
return 1 << 8;
}
else if (floor == 6)
{
return 1 << 9;
}
else if (floor == 7)
{
return 1 << 10;
}else
{
return NavMesh.AllAreas;
}
}
int GetAreaMask()
{
return agent.areaMask;
}
// エリアマスクを設定
public void SetAreaMask(int floor)
{
agent.areaMask = GetAreaMask(floor);
}
public void SetAllAreas()
{
agent.areaMask = NavMesh.AllAreas;
}
// コライダーを出たとき
private void OnTriggerExit(Collider other)
{
if(other.name == "Collider1F")
{
// 1階のコライダーを階段の方へ出る
if(transform.position.x >= other.transform.position.x)
{
Floor = -1;
text.text = "階段";
}
// 1階のコライダーを1階の方へ出る
else
{
Floor = 1;
text.text = $"{Floor}階";
}
}
else if(other.name == "Collider2F")
{
// 1階のコライダーを階段の方へ出る
if (transform.position.x >= other.transform.position.x)
{
Floor = -1;
text.text = "階段";
}
// 1階のコライダーを2階の方へ出る
else
{
Floor = 2;
text.text = $"{Floor}階";
}
}
}
}
同じ階にいるときに他の階にいけるようにすると、プレイヤーがナビメッシュ上にいないときに他の階に目的地が設定されるので注意です。それを避けるためにプレイヤーの階と階段だけのエリアマスクを付けました。
位置をサンプルするときはエージェントのエリアマスクはすべてのエリアに設定しますが、サンプルするエリアはプレイヤーがいる階だけにします。同じ階のナビメッシュ上の点を探す
ちなみに、別の階にいるときにエージェントのエリアマスクをプレイヤーがいる階だけに設定すると、エージェントはその階へ瞬間移動してしまいました。