146 lines
5.0 KiB
C#
146 lines
5.0 KiB
C#
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<NavMeshAgent>();
|
|
_aiController = GetComponent<CharacterAIController>();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (_agent != null)
|
|
{
|
|
_agent.stoppingDistance = stoppingDistance;
|
|
_agent.avoidancePriority = avoidancePriority;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Navigate to a position. Only recalculates path if target moved significantly.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Stop navigation immediately.</summary>
|
|
public void Stop()
|
|
{
|
|
if (_aiController != null)
|
|
{
|
|
_aiController.Stop();
|
|
}
|
|
else if (_agent != null && _agent.enabled)
|
|
{
|
|
_agent.ResetPath();
|
|
}
|
|
|
|
_hasDestination = false;
|
|
}
|
|
|
|
/// <summary>Check if destination is approximately reached.</summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Set avoidance priority (carriers get higher priority = lower number).</summary>
|
|
public void SetAvoidancePriority(int priority)
|
|
{
|
|
avoidancePriority = priority;
|
|
if (_agent != null) _agent.avoidancePriority = priority;
|
|
}
|
|
|
|
/// <summary>Set movement speed.</summary>
|
|
public void SetSpeed(float speed)
|
|
{
|
|
if (_agent != null) _agent.speed = speed;
|
|
}
|
|
|
|
/// <summary>Warp to a position on the NavMesh.</summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Force path recalculation on next SetDestination call.</summary>
|
|
public void InvalidatePath()
|
|
{
|
|
_lastTargetPosition = Vector3.zero;
|
|
_hasDestination = false;
|
|
}
|
|
}
|