#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