using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; namespace Unity.BossRoom.Navigation { public sealed class DynamicNavPath : IDisposable { /// /// The tolerance to decide whether the path needs to be recalculated when the position of a target transform changed. /// const float k_RepathToleranceSqr = 9f; NavMeshAgent m_Agent; NavigationSystem m_NavigationSystem; /// /// The target position value which was used to calculate the current path. /// This get stored to make sure the path gets recalculated if the target /// Vector3 m_CurrentPathOriginalTarget; /// /// This field caches a NavMesh Path so that we don't have to allocate a new one each time. /// NavMeshPath m_NavMeshPath; /// /// The remaining path points to follow to reach the target position. /// List m_Path; /// /// The target position of this path. /// Vector3 m_PositionTarget; /// /// A moving transform target, the path will readjust when the target moves. If this is non-null, it takes precedence over m_PositionTarget. /// Transform m_TransformTarget; /// /// Creates a new instance of the . /// /// The NavMeshAgent of the object which uses this path. /// The navigation system which updates this path. public DynamicNavPath(NavMeshAgent agent, NavigationSystem navigationSystem) { m_Agent = agent; m_Path = new List(); m_NavMeshPath = new NavMeshPath(); m_NavigationSystem = navigationSystem; navigationSystem.OnNavigationMeshChanged += OnNavMeshChanged; } Vector3 TargetPosition => m_TransformTarget != null ? m_TransformTarget.position : m_PositionTarget; /// /// Set the target of this path to follow a moving transform. /// /// The transform to follow. public void FollowTransform(Transform target) { m_TransformTarget = target; } /// /// Set the target of this path to a static position target. /// /// The target position. public void SetTargetPosition(Vector3 target) { // If there is an nav mesh area close to the target use a point inside the nav mesh instead. if (NavMesh.SamplePosition(target, out NavMeshHit hit, 2f, NavMesh.AllAreas)) { target = hit.position; } m_PositionTarget = target; m_TransformTarget = null; RecalculatePath(); } /// /// Call this to recalculate the path when the navigation mesh or dynamic obstacles changed. /// void OnNavMeshChanged() { RecalculatePath(); } /// /// Clears the path. /// public void Clear() { m_Path.Clear(); } /// /// Gets the movement vector for moving this object while following the path. This function changes the state of the path and should only be called once per tick. /// /// The distance to move. /// Returns the movement vector. public Vector3 MoveAlongPath(float distance) { if (m_TransformTarget != null) { OnTargetPositionChanged(TargetPosition); } if (m_Path.Count == 0) { return Vector3.zero; } var currentPredictedPosition = m_Agent.transform.position; var remainingDistance = distance; while (remainingDistance > 0) { var toNextPathPoint = m_Path[0] - currentPredictedPosition; // If end point is closer then distance to move if (toNextPathPoint.sqrMagnitude < remainingDistance * remainingDistance) { currentPredictedPosition = m_Path[0]; m_Path.RemoveAt(0); remainingDistance -= toNextPathPoint.magnitude; } // Move towards point currentPredictedPosition += toNextPathPoint.normalized * remainingDistance; // There is definitely no remaining distance to cover here. break; } return currentPredictedPosition - m_Agent.transform.position; } void OnTargetPositionChanged(Vector3 newTarget) { if (m_Path.Count == 0) { RecalculatePath(); } if ((newTarget - m_CurrentPathOriginalTarget).sqrMagnitude > k_RepathToleranceSqr) { RecalculatePath(); } } /// /// Recalculates the cached navigationPath /// void RecalculatePath() { m_CurrentPathOriginalTarget = TargetPosition; m_Agent.CalculatePath(TargetPosition, m_NavMeshPath); m_Path.Clear(); var corners = m_NavMeshPath.corners; for (int i = 1; i < corners.Length; i++) // Skip the first corner because it is the starting point. { m_Path.Add(corners[i]); } } public void Dispose() { m_NavigationSystem.OnNavigationMeshChanged -= OnNavMeshChanged; } } }