#if MODULE_ENTITIES using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; namespace Pathfinding.ECS { using Pathfinding; using Pathfinding.Util; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; /// /// Holds unmanaged information about an off-mesh link that the agent is currently traversing. /// This component is added to the agent when it starts traversing an off-mesh link. /// It is removed when the agent has finished traversing the link. /// /// See: /// public struct AgentOffMeshLinkTraversal : IComponentData { /// \copydocref{OffMeshLinks.OffMeshLinkTracer.relativeStart} public float3 relativeStart; /// \copydocref{OffMeshLinks.OffMeshLinkTracer.relativeEnd} public float3 relativeEnd; /// \copydocref{OffMeshLinks.OffMeshLinkTracer.relativeStart}. Deprecated: Use relativeStart instead [System.Obsolete("Use relativeStart instead")] public float3 firstPosition => relativeStart; /// \copydocref{OffMeshLinks.OffMeshLinkTracer.relativeEnd}. Deprecated: Use relativeEnd instead [System.Obsolete("Use relativeEnd instead")] public float3 secondPosition => relativeEnd; /// \copydocref{OffMeshLinks.OffMeshLinkTracer.isReverse} public bool isReverse; public AgentOffMeshLinkTraversal (OffMeshLinks.OffMeshLinkTracer linkInfo) { relativeStart = linkInfo.relativeStart; relativeEnd = linkInfo.relativeEnd; isReverse = linkInfo.isReverse; } } /// /// Holds managed information about an off-mesh link that the agent is currently traversing. /// This component is added to the agent when it starts traversing an off-mesh link. /// It is removed when the agent has finished traversing the link. /// /// See: /// public class ManagedAgentOffMeshLinkTraversal : IComponentData, System.ICloneable, ICleanupComponentData { /// Internal context used to pass component data to the coroutine public AgentOffMeshLinkTraversalContext context; /// Coroutine which is used to traverse the link public System.Collections.IEnumerator coroutine; public IOffMeshLinkHandler handler; public IOffMeshLinkStateMachine stateMachine; public ManagedAgentOffMeshLinkTraversal() {} public ManagedAgentOffMeshLinkTraversal (AgentOffMeshLinkTraversalContext context, IOffMeshLinkHandler handler) { this.context = context; this.handler = handler; this.coroutine = null; this.stateMachine = null; } public object Clone () { // This will set coroutine and stateMachine to null. // This is correct, as the coroutine cannot be cloned, and the state machine may be unique for a specific agent return new ManagedAgentOffMeshLinkTraversal((AgentOffMeshLinkTraversalContext)context.Clone(), handler); } } public struct MovementTarget { internal bool isReached; public bool reached => isReached; public MovementTarget (bool isReached) { this.isReached = isReached; } } /// /// Context with helpers for traversing an off-mesh link. /// /// This will be passed to the code that is responsible for traversing the off-mesh link. /// /// Warning: This context should never be accessed outside of an implementation of the interface. /// public class AgentOffMeshLinkTraversalContext : System.ICloneable { internal unsafe AgentOffMeshLinkTraversal* linkInfoPtr; internal unsafe MovementControl* movementControlPtr; internal unsafe MovementSettings* movementSettingsPtr; internal unsafe LocalTransform* transformPtr; internal unsafe AgentMovementPlane* movementPlanePtr; /// The entity that is traversing the off-mesh link public Entity entity; /// Some internal state of the agent [Unity.Properties.DontCreateProperty] public ManagedState managedState; /// /// The off-mesh link that is being traversed. /// /// See: /// [Unity.Properties.DontCreateProperty] internal OffMeshLinks.OffMeshLinkConcrete concreteLink; protected bool disabledRVO; protected float backupRotationSmoothing = float.NaN; /// /// Delta time since the last link simulation. /// /// During high time scales, the simulation may run multiple substeps per frame. /// /// This is not the same as Time.deltaTime. Inside the link coroutine, you should always use this field instead of Time.deltaTime. /// public float deltaTime; protected GameObject gameObjectCache; /// /// GameObject associated with the agent. /// /// In most cases, an agent is associated with an agent, but this is not always the case. /// For example, if you have created an entity without using the component, this property may return null. /// /// Note: When directly modifying the agent's transform during a link traversal, you should use the property instead of modifying the GameObject's transform. /// public virtual GameObject gameObject { get { if (gameObjectCache == null) { var follower = BatchedEvents.Find(entity, (follower, entity) => follower.entity == entity); if (follower != null) gameObjectCache = follower.gameObject; } return gameObjectCache; } } /// ECS LocalTransform component attached to the agent public ref LocalTransform transform { get { unsafe { return ref *transformPtr; } } } /// The movement settings for the agent public ref MovementSettings movementSettings { get { unsafe { return ref *movementSettingsPtr; } } } /// /// How the agent should move. /// /// The agent will move according to this data, every frame. /// public ref MovementControl movementControl { get { unsafe { return ref *movementControlPtr; } } } /// Information about the off-mesh link that the agent is traversing public OffMeshLinks.OffMeshLinkTracer link { get { unsafe { return new OffMeshLinks.OffMeshLinkTracer(concreteLink, linkInfoPtr->relativeStart, linkInfoPtr->relativeEnd, linkInfoPtr->isReverse); } } } /// /// Information about the off-mesh link that the agent is traversing. /// /// Deprecated: Use the property instead /// [System.Obsolete("Use the link property instead")] public AgentOffMeshLinkTraversal linkInfo { get { unsafe { return *linkInfoPtr; } } } /// /// The plane in which the agent is moving. /// /// In a 3D game, this will typically be the XZ plane, but in a 2D game /// it will typically be the XY plane. Games on spherical planets could have planes that are aligned with the surface of the planet. /// public ref NativeMovementPlane movementPlane { get { unsafe { return ref movementPlanePtr->value; } } } public AgentOffMeshLinkTraversalContext (OffMeshLinks.OffMeshLinkConcrete link) { this.concreteLink = link; } /// /// Internal method to set the data of the context. /// /// This is used by the job system to set the data of the context. /// You should almost never need to use this. /// public virtual unsafe void SetInternalData (Entity entity, ref LocalTransform transform, ref AgentMovementPlane movementPlane, ref MovementControl movementControl, ref MovementSettings movementSettings, ref AgentOffMeshLinkTraversal linkInfo, ManagedState state, float deltaTime) { this.linkInfoPtr = (AgentOffMeshLinkTraversal*)UnsafeUtility.AddressOf(ref linkInfo); this.movementControlPtr = (MovementControl*)UnsafeUtility.AddressOf(ref movementControl); this.movementSettingsPtr = (MovementSettings*)UnsafeUtility.AddressOf(ref movementSettings); this.transformPtr = (LocalTransform*)UnsafeUtility.AddressOf(ref transform); this.movementPlanePtr = (AgentMovementPlane*)UnsafeUtility.AddressOf(ref movementPlane); this.managedState = state; this.deltaTime = deltaTime; this.entity = entity; } /// /// Disables local avoidance for the agent. /// /// Agents that traverse links are already marked as 'unstoppable' by the local avoidance system, /// but calling this method will make other agents ignore them completely while traversing the link. /// public void DisableLocalAvoidance () { if (managedState.enableLocalAvoidance) { disabledRVO = true; managedState.enableLocalAvoidance = false; } } /// /// Disables rotation smoothing for the agent. /// /// This disables the effect of while the agent is traversing the link. /// Having rotation smoothing enabled can make the agent rotate towards its target rotation more slowly, /// which is sometimes not desirable. /// /// Rotation smoothing will automatically be restored when the agent finishes traversing the link (if it was enabled before). /// /// The method automatically disables rotation smoothing when called. /// public void DisableRotationSmoothing () { if (float.IsNaN(backupRotationSmoothing) && movementSettings.rotationSmoothing > 0) { backupRotationSmoothing = movementSettings.rotationSmoothing; movementSettings.rotationSmoothing = 0; } } /// /// Restores the agent's settings to what it was before the link traversal started. /// /// This undos the changes made by and . /// /// This method is automatically called when the agent finishes traversing the link. /// public virtual void Restore () { if (disabledRVO) { managedState.enableLocalAvoidance = true; disabledRVO = false; } if (!float.IsNaN(backupRotationSmoothing)) { movementSettings.rotationSmoothing = backupRotationSmoothing; backupRotationSmoothing = float.NaN; } } /// Teleports the agent to the given position public virtual void Teleport (float3 position) { transform.Position = position; } /// /// Thrown when the off-mesh link traversal should be aborted. /// /// See: /// public class AbortOffMeshLinkTraversal : System.Exception {} /// /// Aborts traversing the off-mesh link. /// /// This will immediately stop your off-mesh link traversal coroutine. /// /// This is useful if your agent was traversing an off-mesh link, but you have detected that it cannot continue. /// Maybe the ladder it was climbing was destroyed, or the bridge it was walking on collapsed. /// /// Note: If you instead want to immediately make the agent move to the end of the link, you can call , and then use 'yield break;' from your coroutine. /// /// If true, the agent will be teleported back to the start of the link (from the perspective of the agent). Its rotation will remain unchanged. public virtual void Abort (bool teleportToStart = true) { if (teleportToStart) Teleport(link.relativeStart); // Cancel the current path, as otherwise the agent will instantly try to traverse the off-mesh link again. managedState.pathTracer.SetFromSingleNode(managedState.pathTracer.startNode, transform.Position, movementPlane); throw new AbortOffMeshLinkTraversal(); } /// /// Move towards a point while ignoring the navmesh. /// This method should be called repeatedly until the returned property is true. /// /// Returns: A struct which can be used to check if the target has been reached. /// /// Note: This method completely ignores the navmesh. It also overrides local avoidance, if enabled (other agents will still avoid it, but this agent will not avoid other agents). /// /// TODO: The gravity property is not yet implemented. Gravity is always applied. /// /// The position to move towards. /// The rotation to rotate towards. /// If true, gravity will be applied to the agent. /// If true, the agent will slow down as it approaches the target. public virtual MovementTarget MoveTowards (float3 position, quaternion rotation, bool gravity, bool slowdown) { // If rotation smoothing was enabled, it could cause a very slow convergence to the target rotation. // Therefore, we disable it here. // The agent will try to remove its remaining rotation smoothing offset as quickly as possible. // After the off-mesh link is traversed, the rotation smoothing will be automatically restored. DisableRotationSmoothing(); var dirInPlane = movementPlane.ToPlane(position - transform.Position); var remainingDistance = math.length(dirInPlane); var maxSpeed = movementSettings.follower.Speed(slowdown ? remainingDistance : float.PositiveInfinity); var speed = movementSettings.follower.Accelerate(movementControl.speed, movementSettings.follower.slowdownTime, deltaTime); speed = math.min(speed, maxSpeed); var targetRot = movementPlane.ToPlane(rotation); var currentRot = movementPlane.ToPlane(transform.Rotation); var remainingRot = Mathf.Abs(AstarMath.DeltaAngle(currentRot, targetRot)); movementControl = new MovementControl { targetPoint = position, endOfPath = position, speed = speed, maxSpeed = speed * 1.1f, hierarchicalNodeIndex = -1, overrideLocalAvoidance = true, targetRotation = targetRot, targetRotationHint = targetRot, targetRotationOffset = 0, rotationSpeed = math.radians(movementSettings.follower.rotationSpeed), }; return new MovementTarget { isReached = remainingDistance <= (slowdown ? 0.01f : speed * (1/30f)) && remainingRot < math.radians(1), }; } public virtual object Clone () { var clone = (AgentOffMeshLinkTraversalContext)MemberwiseClone(); clone.entity = Entity.Null; clone.gameObjectCache = null; clone.managedState = null; unsafe { linkInfoPtr = null; movementControlPtr = null; movementSettingsPtr = null; transformPtr = null; movementPlanePtr = null; } return clone; } } } // ctx.MoveTowards (position, rotation, rvo = Auto | Disabled | AutoUnstoppable, gravity = auto|disabled) -> { reached() } // MovementTarget { ... } // while (!movementTarget.reached) { // ctx.SetMovementTarget(movementTarget); // yield return null; // } // yield return ctx.MoveTo(position, rotation) // ctx.TeleportTo(position, rotation) #endif