diff options
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs')
-rw-r--r-- | Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs new file mode 100644 index 0000000..03697d1 --- /dev/null +++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/ExampleScripts/Interactable.cs @@ -0,0 +1,323 @@ +using System.Collections; +using System.Collections.Generic; +using Pathfinding.ECS; +using UnityEngine; + +namespace Pathfinding.Examples { + /// <summary> + /// Example script for handling interactable objects in the example scenes. + /// + /// It implements a very simple and lightweight state machine. + /// + /// Note: This is an example script intended for the A* Pathfinding Project's example scenes. + /// If you need a proper state machine for your game, you may be better served by other state machine solutions on the Unity Asset Store. + /// + /// It works by keeping a linear list of states, each with an associated action. + /// When an agent iteracts with this object, it immediately does the first action in the list. + /// Once that action is done, it will do the next action and so on. + /// + /// Some actions may cancel the whole interaction. For example the MoveTo action will cancel the interaction if the agent + /// suddenly had its destination to something else. Presumably because the agent was interrupted by something. + /// + /// If this component is added to the same GameObject as a <see cref="NodeLink2"/> component, the interactable will automatically trigger when the agent traverses the link. + /// Some components behave differently when used during an off-mesh link component. + /// For example the <see cref="MoveToAction"/> will move the agent without taking the navmesh into account (becoming a thin wrapper for <see cref="AgentOffMeshLinkTraversalContext.MoveTowards"/>). + /// </summary> + [HelpURL("https://arongranberg.com/astar/documentation/stable/interactable.html")] + public class Interactable : VersionedMonoBehaviour, IOffMeshLinkHandler, IOffMeshLinkStateMachine { + public enum CoroutineAction { + Tick, + Cancel, + } + + [System.Serializable] + public abstract class InteractableAction { + public virtual IEnumerator<CoroutineAction> Execute (IAstarAI ai) { + return Execute(); + } + +#if MODULE_ENTITIES + public virtual IEnumerator<CoroutineAction> Execute (Pathfinding.ECS.AgentOffMeshLinkTraversalContext context) { + return Execute(); + } +#endif + + public virtual IEnumerator<CoroutineAction> Execute () { + throw new System.NotImplementedException("This action has no implementation"); + } + } + + + [System.Serializable] + public class AnimatorPlay : InteractableAction { + public string stateName; + public float normalizedTime = 0; + public Animator animator; + + public override IEnumerator<CoroutineAction> Execute () { + animator.Play(stateName, -1, normalizedTime); + yield break; + } + } + + [System.Serializable] + public class AnimatorSetBoolAction : InteractableAction { + public string propertyName; + public bool value; + public Animator animator; + + public override IEnumerator<CoroutineAction> Execute () { + animator.SetBool(propertyName, value); + yield break; + } + } + + [System.Serializable] + public class ActivateParticleSystem : InteractableAction { + public ParticleSystem particleSystem; + + public override IEnumerator<CoroutineAction> Execute () { + particleSystem.Play(); + yield break; + } + } + + [System.Serializable] + public class DelayAction : InteractableAction { + public float delay; + + public override IEnumerator<CoroutineAction> Execute () { + float time = Time.time + delay; + while (Time.time < time) yield return CoroutineAction.Tick; + yield break; + } + } + + [System.Serializable] + public class SetObjectActiveAction : InteractableAction { + public GameObject target; + public bool active; + + public override IEnumerator<CoroutineAction> Execute () { + target.SetActive(active); + yield break; + } + } + + [System.Serializable] + public class InstantiatePrefab : InteractableAction { + public GameObject prefab; + public Transform position; + + public override IEnumerator<CoroutineAction> Execute () { + if (prefab != null && position != null) { + GameObject.Instantiate(prefab, position.position, position.rotation); + } + yield break; + } + } + + [System.Serializable] + public class CallFunction : InteractableAction { + public UnityEngine.Events.UnityEvent function; + + public override IEnumerator<CoroutineAction> Execute () { + function.Invoke(); + yield break; + } + } + + [System.Serializable] + public class TeleportAgentAction : InteractableAction { + public Transform destination; + + public override IEnumerator<CoroutineAction> Execute (IAstarAI ai) { + ai.Teleport(destination.position); + yield break; + } + +#if MODULE_ENTITIES + public override IEnumerator<CoroutineAction> Execute (AgentOffMeshLinkTraversalContext context) { + context.Teleport(destination.position); + yield break; + } +#endif + } + + [System.Serializable] + public class TeleportAgentOnLinkAction : InteractableAction { + public enum Destination { + /// <summary>The side of the link that the agent starts traversing it from</summary> + RelativeStartOfLink, + /// <summary>The side of the link that is opposite the one the agent starts traversing it from</summary> + RelativeEndOfLink, + } + + public Destination destination = Destination.RelativeEndOfLink; + + public override IEnumerator<CoroutineAction> Execute() => throw new System.NotImplementedException("This action only works for agents traversing off-mesh links."); + +#if MODULE_ENTITIES + public override IEnumerator<CoroutineAction> Execute (AgentOffMeshLinkTraversalContext context) { + context.Teleport(destination == Destination.RelativeStartOfLink ? context.link.relativeStart : context.link.relativeEnd); + yield break; + } +#endif + } + + [System.Serializable] + public class SetTransformAction : InteractableAction { + public Transform transform; + public Transform source; + public bool setPosition = true; + public bool setRotation; + public bool setScale; + + public override IEnumerator<CoroutineAction> Execute () { + if (setPosition) transform.position = source.position; + if (setRotation) transform.rotation = source.rotation; + if (setScale) transform.localScale = source.localScale; + yield break; + } + } + + [System.Serializable] + public class MoveToAction : InteractableAction { + public Transform destination; + public bool useRotation; + public bool waitUntilReached; + + public override IEnumerator<CoroutineAction> Execute (IAstarAI ai) { + var dest = destination.position; +#if MODULE_ENTITIES + if (useRotation && ai is FollowerEntity follower) { + follower.SetDestination(dest, destination.rotation * Vector3.forward); + } else +#endif + { + if (useRotation) Debug.LogError("useRotation is only supported for FollowerEntity agents", ai as MonoBehaviour); + ai.destination = dest; + } + + if (waitUntilReached) { + if (ai is AIBase || ai is AILerp) { + // Only the FollowerEntity component is good enough to set the reachedDestination property to false immediately. + // The other movement scripts need to wait for the new path to be available, which is somewhat annoying. + ai.SearchPath(); + while (ai.pathPending) yield return CoroutineAction.Tick; + } + + while (!ai.reachedDestination) { + if (ai.destination != dest) { + // Something else must have changed the destination + yield return CoroutineAction.Cancel; + } + if (ai.reachedEndOfPath) { + // We have reached the end of the path, but not the destination + // This must mean that we cannot get any closer + // TODO: More accurate 'cannot move forwards' check + yield return CoroutineAction.Cancel; + } + yield return CoroutineAction.Tick; + } + } + } + +#if MODULE_ENTITIES + public override IEnumerator<CoroutineAction> Execute (AgentOffMeshLinkTraversalContext context) { + while (!context.MoveTowards(destination.position, destination.rotation, true, true).reached) { + yield return CoroutineAction.Tick; + } + yield break; + } +#endif + } + + [System.Serializable] + public class InteractAction : InteractableAction { + public Interactable interactable; + + public override IEnumerator<CoroutineAction> Execute (IAstarAI ai) { + var it = interactable.InteractCoroutine(ai); + while (it.MoveNext()) { + yield return it.Current; + } + } + } + + [SerializeReference] + public List<InteractableAction> actions; + + public void Interact (IAstarAI ai) { + StartCoroutine(InteractCoroutine(ai)); + } + +#if MODULE_ENTITIES + IOffMeshLinkStateMachine IOffMeshLinkHandler.GetOffMeshLinkStateMachine(AgentOffMeshLinkTraversalContext context) => this; + + IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink (AgentOffMeshLinkTraversalContext context) { + var it = InteractCoroutine(context); + while (it.MoveNext()) { + yield return null; + } + } + + public IEnumerator<CoroutineAction> InteractCoroutine (Pathfinding.ECS.AgentOffMeshLinkTraversalContext context) { + if (actions.Count == 0) { + Debug.LogWarning("No actions have been set up for this interactable", this); + yield break; + } + + var actionIndex = 0; + while (actionIndex < actions.Count) { + var action = actions[actionIndex]; + if (action == null) { + actionIndex++; + continue; + } + + var enumerator = action.Execute(context); + while (enumerator.MoveNext()) { + yield return enumerator.Current; + if (enumerator.Current == CoroutineAction.Cancel) yield break; + } + + actionIndex++; + } + } +#endif + + public IEnumerator<CoroutineAction> InteractCoroutine (IAstarAI ai) { + if (actions.Count == 0) { + Debug.LogWarning("No actions have been set up for this interactable", this); + yield break; + } + + var actionIndex = 0; + while (actionIndex < actions.Count) { + var action = actions[actionIndex]; + if (action == null) { + actionIndex++; + continue; + } + + var enumerator = action.Execute(ai); + while (enumerator.MoveNext()) { + yield return enumerator.Current; + if (enumerator.Current == CoroutineAction.Cancel) yield break; + } + + actionIndex++; + } + } + + void OnEnable () { + // Allow the interactable to be triggered by an agent traversing an off-mesh link + if (TryGetComponent<NodeLink2>(out var link)) link.onTraverseOffMeshLink = this; + } + + void OnDisable () { + if (TryGetComponent<NodeLink2>(out var link) && link.onTraverseOffMeshLink == (IOffMeshLinkHandler)this) link.onTraverseOffMeshLink = null; + } + } +} |