しあよなのヲタ話

ライブレポとか、雑記

NavMeshAgentとRigidbodyを共存させ、空中生成を可能にした話

はじめに

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))]    // NavMeshAgentを自動的にAdd Component
[RequireComponent(typeof(Rigidbody))]       // RigidBodyを自動的にAdd Component

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;
                // 位置をNavMesh内に補正
                transform.position = corPos;
            }

            //エージェント有効化
            if (navMeshAgent.enabled == false) navMeshAgent.enabled = true;


            DoMove(player.transform.position);
        }


    }

    private void DoMove(Vector3 targetPosition)
    {
        if (navMeshAgent.pathStatus != NavMeshPathStatus.PathInvalid)
        {//Navmeshの準備が出来たら始動

            //navMeshAgentの操作
            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なんもわからん。