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