using UnityEngine;
using UnityEngine.AI;
///
/// AINavigationHelper — NavMeshAgent wrapper with path caching and mobile optimization.
/// Prevents unnecessary path recalculations and provides clean navigation API.
///
/// Only recalculates path when target moves >2m or path becomes invalid.
///
public class AINavigationHelper : MonoBehaviour
{
[Header("Navigation Settings")]
[SerializeField] private float pathRecalcThreshold = 2f; // Minimum target movement to recalc
[SerializeField] private float stoppingDistance = 1.5f;
[SerializeField] private float reachThreshold = 2f;
[Header("Avoidance")]
[SerializeField] private int avoidancePriority = 50; // 0=highest priority, 99=lowest
// ─── Components ──────────────────────────────────────────
private NavMeshAgent _agent;
private CharacterAIController _aiController;
// ─── Path Cache ──────────────────────────────────────────
private Vector3 _lastTargetPosition;
private bool _hasDestination;
// ─── Properties ──────────────────────────────────────────
public bool HasArrived => _hasDestination && _agent != null && _agent.enabled &&
!_agent.pathPending &&
_agent.remainingDistance <= reachThreshold;
public bool IsNavigating => _hasDestination && _agent != null && _agent.enabled &&
_agent.hasPath && _agent.remainingDistance > reachThreshold;
public float RemainingDistance => _agent != null && _agent.enabled ? _agent.remainingDistance : float.MaxValue;
private void Awake()
{
_agent = GetComponent();
_aiController = GetComponent();
}
private void Start()
{
if (_agent != null)
{
_agent.stoppingDistance = stoppingDistance;
_agent.avoidancePriority = avoidancePriority;
}
}
///
/// Navigate to a position. Only recalculates path if target moved significantly.
///
public void SetDestination(Vector3 target)
{
// Use CharacterAIController if available (it handles NavMesh warping)
if (_aiController != null)
{
float sqrDist = (target - _lastTargetPosition).sqrMagnitude;
if (_hasDestination && sqrDist < pathRecalcThreshold * pathRecalcThreshold)
return; // Target hasn't moved enough, keep current path
_aiController.NavigateTo(target);
_lastTargetPosition = target;
_hasDestination = true;
return;
}
// Direct NavMeshAgent fallback
if (_agent == null || !_agent.enabled) return;
float sqrDistDirect = (target - _lastTargetPosition).sqrMagnitude;
if (_hasDestination && sqrDistDirect < pathRecalcThreshold * pathRecalcThreshold)
return;
_agent.SetDestination(target);
_lastTargetPosition = target;
_hasDestination = true;
}
/// Stop navigation immediately.
public void Stop()
{
if (_aiController != null)
{
_aiController.Stop();
}
else if (_agent != null && _agent.enabled)
{
_agent.ResetPath();
}
_hasDestination = false;
}
/// Check if destination is approximately reached.
public bool HasReachedDestination()
{
if (!_hasDestination) return true;
if (_agent != null && _agent.enabled)
{
return !_agent.pathPending && _agent.remainingDistance <= reachThreshold;
}
// Fallback distance check
return Vector3.SqrMagnitude(transform.position - _lastTargetPosition) < reachThreshold * reachThreshold;
}
/// Set avoidance priority (carriers get higher priority = lower number).
public void SetAvoidancePriority(int priority)
{
avoidancePriority = priority;
if (_agent != null) _agent.avoidancePriority = priority;
}
/// Set movement speed.
public void SetSpeed(float speed)
{
if (_agent != null) _agent.speed = speed;
}
/// Warp to a position on the NavMesh.
public bool WarpTo(Vector3 position)
{
if (_agent == null) return false;
NavMeshHit hit;
if (NavMesh.SamplePosition(position, out hit, 5f, NavMesh.AllAreas))
{
_agent.Warp(hit.position);
return true;
}
return false;
}
/// Force path recalculation on next SetDestination call.
public void InvalidatePath()
{
_lastTargetPosition = Vector3.zero;
_hasDestination = false;
}
}