はじめに
NavmeshAgentを使ったAIのEnemyを作った際に、どうにかして空中で生成された時に上手く追尾させたり、ジャンプ等の物理演算(Rigidbody)を使いたかったのでメモ程度に書く。
空中で生成と言っても、空中追尾のようなものはできないので注意。
参考にしたリンク
今回のジャンプ、空中、Rigidbody併用をするにあたって参考にしたリンク様達です。
tsubakit1.hateblo.jp
gomafrontier.com
やったこと
・とりあえずNavmeshAgentの値をすべて0にする。
先程紹介したリンクは、どちらもNavmeshAgentとRigidbodyを併用する方法を紹介しているのですが、テラシュールさん(一番上)の方法(IsKinematicsをオンにしたりしなかったり制御する)だと色々と不都合が生じたので、ゴマちゃんの「NavmeshAgentの経路探索のみを利用して移動はRigidbodyで行う」という方法を採用しました。
なので、NavmeshAgentのSpeed等の値は使用しません。
これは、NavmeshAgentの挙動はRigidbodyやTransformなどとは全く違う位置情報であり、たとえAddforceやTransformによってキャラクターの動きを制御していたとしても、NavmeshAgentの動きによって上書きされてしまう為です。
(NavmeshAgentの位置情報が優先的に更新されるため、Rigidbodyの重力が働かない等)
・NavmeshAgentを動的に有効化する
先程の画像を見れば分かる通り、NavmeshAgentはInspector上では無効化した状態にしてあります、これは、空中や、Navigation情報がBakeされていない場所でオブジェクトが生成された場合に、NavmeshAgentが有効化されていると
Failed to create agent because it is not close enough to the NavMesh
という黄色エラーが発生し、経路探索をしてくれなくなる為です。
なので、NavmeshAgentはInspector上(またはScript上)で最初は無効化しておき、Navigation範囲内で有効化する必要があります。
これらを踏まえて当該コードを載せておきます。
参考リンク:
robamemo.hatenablog.com
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(Rigidbody))]
public class NavEnemyMove : MonoBehaviour
{
[SerializeField]
[Tooltip("追従対象")] private GameObject player;
[SerializeField]
[Tooltip("速度")] private float moveSpeed;
[SerializeField]
[Tooltip("地面との判定")] private float fDistance;
protected Rigidbody rb;
protected NavMeshAgent navMeshAgent;
protected NavMeshHit navHit;
void Start()
{
rb = this.GetComponent<Rigidbody>();
navMeshAgent = this.GetComponent<NavMeshAgent>();
player = GameObject.Find("Player");
}
void Update()
{
Vector3 rayPosition = transform.position + new Vector3(0.0f, 0.0f, 0.0f);
Ray ray = new Ray(rayPosition, Vector3.down);
bool bGround = Physics.Raycast(ray, fDistance);
Debug.DrawRay(rayPosition, Vector3.down * fDistance, Color.red);
if (bGround)
{
if (NavMesh.SamplePosition(transform.position, out navHit, 1.0f, NavMesh.AllAreas))
{
Vector3 corPos = navHit.position;
corPos.y = navHit.position.y + 0.5f;
transform.position = corPos;
}
if (navMeshAgent.enabled == false) navMeshAgent.enabled = true;
DoMove(player.transform.position);
}
}
private void DoMove(Vector3 targetPosition)
{
if (navMeshAgent.pathStatus != NavMeshPathStatus.PathInvalid)
{
if (navMeshAgent && navMeshAgent.enabled)
{
navMeshAgent.SetDestination(targetPosition);
foreach (var pos in navMeshAgent.path.corners)
{
var diff2d = new Vector2(
Mathf.Abs(pos.x - transform.position.x),
Mathf.Abs(pos.z - transform.position.z));
if (0.1f <= diff2d.magnitude)
{
targetPosition = pos;
break;
}
}
Debug.DrawLine(transform.position, targetPosition, Color.red);
}
Quaternion moveRotation = Quaternion.LookRotation(targetPosition - transform.position, Vector3.up);
moveRotation.z = 0;
moveRotation.x = 0;
transform.rotation = Quaternion.Lerp(transform.rotation, moveRotation, 0.1f);
float forward_x = transform.forward.x * moveSpeed;
float forward_z = transform.forward.z * moveSpeed;
rb.velocity = new Vector3(forward_x, rb.velocity.y, forward_z);
}
}
}
DoMove()以下の処理はほぼ全部丸パクリなので言うことはないですが、NavmeshAgentがもし生成できない状態で動かれては困るので、
if (navMeshAgent.pathStatus != NavMeshPathStatus.PathInvalid)
によって生成可能ではない場合はDoMoveの処理が走らないようにしています。
また、このコードでは実装していませんが、NavmeshAgentを動的に制御する事によって、ジャンプフラグ等の代わりにできるかなと思います。
NavmeshAgentを利用して空中を追尾するとかも、これを発展させればできるのかなと思います。また、コードに不備等ありましたらご指摘お願いします。
おわりに
NavmeshAgentなんもわからん。