summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIDestinationSetter.cs
blob: 5834f6f74a83438339c87771f5ec1723a4c3586c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using UnityEngine;
using Pathfinding.Util;
#if MODULE_ENTITIES
using Unity.Entities;
#endif

namespace Pathfinding {
	/// <summary>
	/// Sets the destination of an AI to the position of a specified object.
	/// This component should be attached to a GameObject together with a movement script such as AIPath, RichAI or AILerp.
	/// This component will then make the AI move towards the <see cref="target"/> set on this component.
	///
	/// Essentially the only thing this component does is to set the <see cref="Pathfinding.IAstarAI.destination"/> property to the position of the target every frame.
	/// There is some additional complexity to make sure that the destination is updated immediately before the AI searches for a path as well, in case the
	/// target moved since the last Update. There is also some complexity to reduce the performance impact, by using the <see cref="BatchedEvents"/> system to
	/// process all AIDestinationSetter components in a single batch.
	///
	/// When using ECS, this component is instead added as a managed component to the entity.
	/// The destination syncing is then handled by the <see cref="SyncDestinationTransformSystem"/> for better performance.
	///
	/// See: <see cref="Pathfinding.IAstarAI.destination"/>
	///
	/// [Open online documentation to see images]
	/// </summary>
	[UniqueComponent(tag = "ai.destination")]
	[AddComponentMenu("Pathfinding/AI/Behaviors/AIDestinationSetter")]
	[HelpURL("https://arongranberg.com/astar/documentation/stable/aidestinationsetter.html")]
	public class AIDestinationSetter : VersionedMonoBehaviour
#if MODULE_ENTITIES
		, IComponentData, IRuntimeBaker
#endif
	{
		/// <summary>The object that the AI should move to</summary>
		public Transform target;

		/// <summary>
		/// If true, the agent will try to align itself with the rotation of the <see cref="target"/>.
		///
		/// This can only be used together with the <see cref="FollowerEntity"/> movement script.
		/// Other movement scripts will ignore it.
		///
		/// [Open online documentation to see videos]
		///
		/// See: <see cref="FollowerEntity.SetDestination"/>
		/// </summary>
		public bool useRotation;

		IAstarAI ai;
#if MODULE_ENTITIES
		Entity entity;
		World world;
#endif

		void OnEnable () {
			ai = GetComponent<IAstarAI>();
#if MODULE_ENTITIES
			if (ai is FollowerEntity follower) {
				// This will call OnCreatedEntity on this component, if the entity has already been created.
				follower.RegisterRuntimeBaker(this);
			} else
#endif
			{
				// Update the destination right before searching for a path as well.
				// This is enough in theory, but this script will also update the destination every
				// frame as the destination is used for debugging and may be used for other things by other
				// scripts as well. So it makes sense that it is up to date every frame.
				if (ai != null) ai.onSearchPath += UpdateDestination;

				// Will make OnUpdate be called once every frame with all components.
				// This is significantly faster than letting Unity call the Update method
				// on each component.
				// See https://blog.unity.com/technology/1k-update-calls
				BatchedEvents.Add(this, BatchedEvents.Event.Update, OnUpdate, 0);
			}
		}

		void OnDisable () {
#if MODULE_ENTITIES
			if (world != null && world.IsCreated && world.EntityManager.Exists(entity)) {
				world.EntityManager.RemoveComponent<AIDestinationSetter>(entity);
			}
			if (ai != null && !(ai is FollowerEntity)) ai.onSearchPath -= UpdateDestination;
#else
			if (ai != null) ai.onSearchPath -= UpdateDestination;
#endif
			BatchedEvents.Remove(this);
		}

#if MODULE_ENTITIES
		void IRuntimeBaker.OnCreatedEntity (World world, Entity entity) {
			// Do nothing except add the component. Actual syncing is handled by the SyncDestinationTransformSystem.
			this.entity = entity;
			this.world = world;
			world.EntityManager.AddComponentData<AIDestinationSetter>(entity, this);
		}
#endif

		/// <summary>Updates the AI's destination every frame</summary>
		static void OnUpdate (AIDestinationSetter[] components, int count) {
			for (int i = 0; i < count; i++) {
				components[i].UpdateDestination();
			}
		}

		/// <summary>Updates the AI's destination immediately</summary>
		void UpdateDestination () {
			if (target != null && ai != null) ai.destination = target.position;
		}
	}
}