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;
}
}
}