還原基本步重新寫一寫 NavMeshAgent.
在1000 agent, 0gc 還免強達到 60fps 我覺得已經足夠的了.
- Pathfinding AI 在 Player 某個特定半徑上停下.
- 在 Player 不移動或改變很少的時候不更新
- 在 Agent 不超過某特定半徑範圍下不更新
- 訂立最少更新時間間隔
- 黃線 – 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 } }