/// --------------------------------------------- /// Shared Add-On for Behavior Designer Pro /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.AddOns.Shared.Runtime.Pathfinding { using Opsive.GraphDesigner.Runtime.Variables; using UnityEngine; using UnityEngine.AI; /// /// Implements the Pathfindiner abstract class for the NavMeshAgent pathfinding implementation. /// public class NavMeshAgentPathfinder : Pathfinder { [Tooltip("Should the NavMeshAgent rotation be updated?")] [SerializeField] protected SharedVariable m_UpdateRotation = true; [Tooltip("Sets the minimum amount of time in between destination updates. This allows for throttling the number of path searches.")] [SerializeField] protected SharedVariable m_DestinationUpdateInterval = 0; [Tooltip("Specifies the stopping distance of the NavMeshAgent.")] [SerializeField] protected SharedVariable m_StoppingDistance = 0.2f; public NavMeshAgent m_NavMeshAgent; private bool m_StartUpdateRotation; private float m_SetDestinationTime; public override Vector3 Velocity { get => m_NavMeshAgent.velocity; } public override float RemainingDistance { get => m_NavMeshAgent.pathPending ? float.PositiveInfinity : m_NavMeshAgent.remainingDistance; } public override Vector3 Destination { get => m_NavMeshAgent.destination; } public override float Speed { get => m_NavMeshAgent.speed; set => m_NavMeshAgent.speed = value; } public float StoppingDistance { get => m_StoppingDistance.Value; set => m_StoppingDistance.Value = value; } /// /// Initializes the Pathfinder. /// /// The parent GameObject. public override void Initialize(GameObject gameObject) { m_NavMeshAgent = gameObject.GetComponent(); if (m_NavMeshAgent == null) { Debug.LogError($"Error: Unable to find the NavMeshAgent component on the {gameObject} GameObject."); return; } if (m_StoppingDistance.Value <= 0) { Debug.LogWarning("Warning: The NavMeshAgent stopping distance is set to 0. This should be a positive value to ensure the agent is able to arrive at their destination."); m_StoppingDistance.Value = 0.2f; } } /// /// The task has started. /// public override void OnStart() { m_NavMeshAgent.isStopped = false; m_NavMeshAgent.stoppingDistance = m_StoppingDistance.Value; m_StartUpdateRotation = m_NavMeshAgent.updateRotation; m_SetDestinationTime = -m_DestinationUpdateInterval.Value; UpdateRotation(m_UpdateRotation.Value); } /// /// Specifies if the rotation should be updated. /// /// Should the rotation be updated? private void UpdateRotation(bool update) { m_NavMeshAgent.updateRotation = update; m_NavMeshAgent.updateUpAxis = update; } /// /// Set a new pathfinding destination. /// /// The destination to set. /// True if the destination is valid. public override bool SetDesination(Vector3 destination) { if (!m_NavMeshAgent.isStopped && m_NavMeshAgent.destination == destination) { return true; } // Prevent the destination from being set too often. if (m_SetDestinationTime + m_DestinationUpdateInterval.Value > Time.time) { return true; } m_NavMeshAgent.isStopped = false; m_SetDestinationTime = Time.time; return m_NavMeshAgent.SetDestination(destination); } /// /// Does the agent have a pathfinding path? /// /// True if the agent has a pathfinding path. public override bool HasPath() { return m_NavMeshAgent.hasPath && RemainingDistance > m_NavMeshAgent.stoppingDistance && !m_NavMeshAgent.isStopped; } /// /// Returns true if the position is a valid pathfinding position. /// /// The position to sample. The position will be updated to the valid sampled position. /// True if the position is a valid pathfinding position. public override bool SamplePosition(ref Vector3 position) { NavMeshHit hit; if (NavMesh.SamplePosition(position, out hit, m_NavMeshAgent.height * 2, NavMesh.AllAreas)) { position = hit.position; return true; } return false; } /// /// Has the agent arrived at the destination? /// /// True if the agent has arrived at the destination. public override bool HasArrived() { // The path hasn't been computed yet if the path is pending. float remainingDistance; if (m_NavMeshAgent.pathPending) { remainingDistance = float.PositiveInfinity; } else { remainingDistance = m_NavMeshAgent.remainingDistance; } return remainingDistance <= m_NavMeshAgent.stoppingDistance; } /// /// The agent should stop moving. /// public override void Stop() { if (m_NavMeshAgent != null && m_NavMeshAgent.isActiveAndEnabled) { m_NavMeshAgent.isStopped = true; } } /// /// The task has stopped. /// public override void OnEnd() { // UpdateRotation(m_StartUpdateRotation); } /// /// Resets the values back to their default. /// public override void Reset() { base.Reset(); m_UpdateRotation = true; m_StoppingDistance = 2f; } } }