1000 NavMeshAgent test

1000 NavMeshAgent test

還原基本步重新寫一寫 NavMeshAgent.
在1000 agent, 0gc 還免強達到 60fps 我覺得已經足夠的了.

  • Pathfinding AI 在 Player 某個特定半徑上停下.
  • 在 Player 不移動或改變很少的時候不更新
  • 在 Agent 不超過某特定半徑範圍下不更新
  • 訂立最少更新時間間隔
The agent’s decision debug view
  • 黃線 – Agent 希望到達的目的地
  • 黃圈 / 紅圈 – Agent 具體停留的距離, 不超過不進行更新
  • 灰圈 – Target 上次紀錄的地點半徑, 不超過不更新.


運行結果如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

namespace Kit.Avatar
{
    [RequireComponent(typeof(NavMeshAgent))]
    public class NPCOrbitMovement : MonoBehaviour
    {
        #region Variables
        [SerializeField]
        private NavMeshAgent m_NavMeshAgent = null;
        [SerializeField] bool m_DebugGizmos = false;

        [Help("The min period for AI's to remake decision."), SerializeField]
        private float m_ThinkInterval = 0.02f;
        private float m_LastThinkTime = 0f;

        [Tooltip("The target we tracking."), SerializeField]
        private Transform m_Target = null;
        [SerializeField]
        private Constraints m_Constraints = new Constraints();
        [System.Serializable]
        private class Constraints
        {
            [Tooltip("The agent will attempt to keep the distance between target, not too far, not too close.")]
            public float maintainDistance = 2f;
            [Tooltip("when target position changed," +
                "\nif position remain inside this radius," +
                "\nwill concider as no change at all.")]
            public float agentPosDeviation = 0.25f;
            public Color minRangeColor = Color.red;
            public Color maxRangeColor = Color.yellow;

            public float targetPosDeviation = 0.25f;
            public Color deviationColor = Color.gray;
            public void Validate()
            {
                maintainDistance = Mathf.Max(float.Epsilon, maintainDistance);
                agentPosDeviation = Mathf.Clamp(agentPosDeviation, float.Epsilon, maintainDistance * 2f);
            }
        }
        #endregion Variables

        #region System
        private void Reset()
        {
            m_NavMeshAgent = GetComponent<NavMeshAgent>();
        }
        private void OnValidate()
        {
            m_Constraints.Validate();
            if (!Application.isPlaying)
                Think(m_Target, true);
        }
        private void Awake()
        {
            if (m_NavMeshAgent == null)
                m_NavMeshAgent = GetComponent<NavMeshAgent>();
            m_Decision = new Decision(OnDestinationChanged);
        }

        private void OnDrawGizmos()
        {
            if (!m_DebugGizmos)
                return;
            if (!Application.isPlaying)
                Think(m_Target);
            m_Decision.DrawGizmos(m_Constraints);
        }

        private void FixedUpdate()
        {
            if (Time.timeSinceLevelLoad - m_LastThinkTime > m_ThinkInterval)
            {
                Think(m_Target);
            }
        }
        #endregion System

        #region Decision making
        private void Think(Transform _target, bool forceUpdate = false)
        {
            m_LastThinkTime = Time.timeSinceLevelLoad;
            m_Decision.MakeDecision(transform, _target, m_Constraints, forceUpdate);
        }

        private void OnDestinationChanged(Vector3 destination)
        {
            m_NavMeshAgent.SetDestination(destination);
        }

        private Decision m_Decision = new Decision(null);
        private class Decision
        {
            public Transform target;
            public Vector3 targetPos, myPos, myFacing, groundNormal, xzVector, xzDir, destination;
            public float minDistance, maxDistance, deviationDistance, targetPosDeviation;
            public delegate void DestinationChanged(Vector3 pos);
            public DestinationChanged Event_DestinationChanged;

            public Decision(DestinationChanged destinationChanged)
            {
                Event_DestinationChanged = destinationChanged;
            }
            public void MakeDecision(Transform _my, Transform _target, Constraints _constraints, bool forceUpdate)
            {
                if (!forceUpdate)
                {
                    if (_target == target &&
                        IsTargetNotMoving(_target.position, _constraints.targetPosDeviation))
                    {
                        return;
                    }
                }
                if (_target == null)
                {
                    Clear();
                    return;
                }

                // define new destination
                target = _target;
                groundNormal = Vector3.up;
                myPos = _my.position;
                myFacing = Vector3.ProjectOnPlane(_my.forward, groundNormal);
                targetPos = target.position;
                targetPosDeviation = _constraints.targetPosDeviation;

                // flatten local Y-axis
                xzVector = Vector3.ProjectOnPlane(myPos - targetPos, groundNormal);
                xzDir = xzVector == Vector3.zero ?
                    -myFacing : // bias when it's not difference, we step back.
                    xzVector.normalized;
                float halfDeviation = _constraints.agentPosDeviation * 0.5f;
                minDistance = _constraints.maintainDistance - halfDeviation;
                maxDistance = _constraints.maintainDistance + halfDeviation;
                deviationDistance = _constraints.maintainDistance - xzVector.magnitude;
                destination = myPos + xzDir * deviationDistance;
                Event_DestinationChanged?.Invoke(destination);
            }

            private bool IsTargetNotMoving(Vector3 newPos, float posDeviation)
            {
                float diffSqr = (targetPos - newPos).sqrMagnitude;
                float acceptDevSqr = posDeviation * posDeviation;
                return diffSqr < acceptDevSqr; // within acceptable deviation
            }

            public void Clear()
            {
                target = null;
                targetPos = myPos = myFacing = groundNormal = xzVector = xzDir = destination = default;
                minDistance = maxDistance = deviationDistance = targetPosDeviation = 0f;
            }

            public void DrawGizmos(Constraints c)
            {
                if (target == null)
                    return;
                /**** This code will not work without gizmos lib.
                GizmosExtend.DrawCircle(targetPos, groundNormal, c.deviationColor, targetPosDeviation);
                GizmosExtend.DrawCircle(targetPos, groundNormal, c.minRangeColor, minDistance);
                GizmosExtend.DrawCircle(targetPos, groundNormal, c.maxRangeColor, maxDistance);
                Color deviationColor = deviationDistance > 0 ? c.minRangeColor : c.maxRangeColor;
                GizmosExtend.DrawLine(myPos, destination, deviationColor);
                GizmosExtend.DrawPoint(destination, deviationColor, 0.3f);
                //****/
            }

        }
        #endregion Decision making
    }
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。

*

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料