summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/Scenes/HexagonalTurnBased/TurnBasedManager.cs
blob: 4523399c02910c174b0618f0a3e9567334ada06b (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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pathfinding;
using Pathfinding.Util;
using UnityEngine.EventSystems;

namespace Pathfinding.Examples {
	/// <summary>Helper script in the example scene 'Turn Based'</summary>
	[HelpURL("https://arongranberg.com/astar/documentation/stable/turnbasedmanager.html")]
	public class TurnBasedManager : VersionedMonoBehaviour {
		TurnBasedAI selected;

		public float movementSpeed;
		public GameObject nodePrefab;
		public LayerMask layerMask;

		List<GameObject> possibleMoves = new List<GameObject>();
		EventSystem eventSystem;

		public State state = State.SelectUnit;

		public enum State {
			SelectUnit,
			SelectTarget,
			Move
		}

		protected override void Awake () {
			base.Awake();
			eventSystem = UnityCompatibility.FindAnyObjectByType<EventSystem>();
		}

		void Update () {
			var mousePos = Input.mousePosition;

			// If the game view is not active, the mouse position can be infinite
			if (!float.IsFinite(mousePos.x)) return;

			Ray ray = Camera.main.ScreenPointToRay(mousePos);

			// Ignore any input while the mouse is over a UI element
			if (eventSystem.IsPointerOverGameObject()) {
				return;
			}

			if (state == State.SelectTarget) {
				HandleButtonUnderRay(ray);
			}

			if (state == State.SelectUnit || state == State.SelectTarget) {
				if (Input.GetKeyDown(KeyCode.Mouse0)) {
					var unitUnderMouse = GetByRay<TurnBasedAI>(ray);

					if (unitUnderMouse != null) {
						Select(unitUnderMouse);
						DestroyPossibleMoves();
						GeneratePossibleMoves(selected);
						state = State.SelectTarget;
					}
				}
			}
		}

		// TODO: Move to separate class
		void HandleButtonUnderRay (Ray ray) {
			var button = GetByRay<Astar3DButton>(ray);

			if (button != null && Input.GetKeyDown(KeyCode.Mouse0)) {
				button.OnClick();

				DestroyPossibleMoves();
				state = State.Move;
				StartCoroutine(MoveToNode(selected, button.node));
			}
		}

		T GetByRay<T>(Ray ray) where T : class {
			RaycastHit hit;

			if (Physics.Raycast(ray, out hit, float.PositiveInfinity, layerMask)) {
				return hit.transform.GetComponentInParent<T>();
			}
			return null;
		}

		void Select (TurnBasedAI unit) {
			selected = unit;
		}

		IEnumerator MoveToNode (TurnBasedAI unit, GraphNode node) {
			var path = ABPath.Construct(unit.transform.position, (Vector3)node.position);

			path.traversalProvider = unit.traversalProvider;

			// Schedule the path for calculation
			AstarPath.StartPath(path);

			// Wait for the path calculation to complete
			yield return StartCoroutine(path.WaitForPath());

			if (path.error) {
				// Not obvious what to do here, but show the possible moves again
				// and let the player choose another target node
				// Likely a node was blocked between the possible moves being
				// generated and the player choosing which node to move to
				Debug.LogError("Path failed:\n" + path.errorLog);
				state = State.SelectTarget;
				GeneratePossibleMoves(selected);
				yield break;
			}

			// Set the target node so other scripts know which
			// node is the end point in the path
			unit.targetNode = path.path[path.path.Count - 1];

			yield return StartCoroutine(MoveAlongPath(unit, path, movementSpeed));

			unit.blocker.BlockAtCurrentPosition();

			// Select a new unit to move
			state = State.SelectUnit;
		}

		/// <summary>[MoveAlongPath]</summary>
		/// <summary>Interpolates the unit along the path</summary>
		static IEnumerator MoveAlongPath (TurnBasedAI unit, ABPath path, float speed) {
			if (path.error || path.vectorPath.Count == 0)
				throw new System.ArgumentException("Cannot follow an empty path");

			// Very simple movement, just interpolate using a catmull-rom spline
			float distanceAlongSegment = 0;
			for (int i = 0; i < path.vectorPath.Count - 1; i++) {
				var p0 = path.vectorPath[Mathf.Max(i-1, 0)];
				// Start of current segment
				var p1 = path.vectorPath[i];
				// End of current segment
				var p2 = path.vectorPath[i+1];
				var p3 = path.vectorPath[Mathf.Min(i+2, path.vectorPath.Count-1)];

				// Approximate the length of the spline
				var segmentLength = Vector3.Distance(p1, p2);

				// Move the agent forward each frame, until we reach the end of the segment
				while (distanceAlongSegment < segmentLength) {
					// Use a Catmull-rom spline to smooth the path. See https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull%E2%80%93Rom_spline
					var interpolatedPoint = AstarSplines.CatmullRom(p0, p1, p2, p3, distanceAlongSegment / segmentLength);
					unit.transform.position = interpolatedPoint;
					yield return null;
					distanceAlongSegment += Time.deltaTime * speed;
				}

				distanceAlongSegment -= segmentLength;
			}

			// Move the agent to the final point in the path
			unit.transform.position = path.vectorPath[path.vectorPath.Count - 1];
		}
		/// <summary>[MoveAlongPath]</summary>

		void DestroyPossibleMoves () {
			foreach (var go in possibleMoves) {
				GameObject.Destroy(go);
			}
			possibleMoves.Clear();
		}

		/// <summary>[GeneratePossibleMoves]</summary>
		void GeneratePossibleMoves (TurnBasedAI unit) {
			var path = ConstantPath.Construct(unit.transform.position, unit.movementPoints * 1000 + 1);

			path.traversalProvider = unit.traversalProvider;

			// Schedule the path for calculation
			AstarPath.StartPath(path);

			// Force the path request to complete immediately
			// This assumes the graph is small enough that
			// this will not cause any lag
			path.BlockUntilCalculated();

			foreach (var node in path.allNodes) {
				if (node != path.startNode) {
					// Create a new node prefab to indicate a node that can be reached
					// NOTE: If you are going to use this in a real game, you might want to
					// use an object pool to avoid instantiating new GameObjects all the time
					var go = GameObject.Instantiate(nodePrefab, (Vector3)node.position, Quaternion.identity) as GameObject;
					possibleMoves.Add(go);

					go.GetComponent<Astar3DButton>().node = node;
				}
			}
		}
		/// <summary>[GeneratePossibleMoves]</summary>
	}
}