324 lines
10 KiB
C#
324 lines
10 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|