summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Editor/Inspectors/FollowerEntityEditor.cs
blob: 7742f2955d19d70aea74c34b6fdd6fef8c7be51e (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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#if MODULE_ENTITIES
using UnityEditor;
using UnityEngine;
using Pathfinding.RVO;
using Pathfinding.ECS;
using System.Linq;

namespace Pathfinding {
	[CustomEditor(typeof(FollowerEntity), true)]
	[CanEditMultipleObjects]
	public class FollowerEntityEditor : EditorBase {
		bool debug = false;
		bool tagPenaltiesOpen;

		protected override void OnDisable () {
			base.OnDisable();
			EditorPrefs.SetBool("FollowerEntity.debug", debug);
			EditorPrefs.SetBool("FollowerEntity.tagPenaltiesOpen", tagPenaltiesOpen);
		}

		protected override void OnEnable () {
			base.OnEnable();
			debug = EditorPrefs.GetBool("FollowerEntity.debug", false);
			tagPenaltiesOpen = EditorPrefs.GetBool("FollowerEntity.tagPenaltiesOpen", false);
		}

		public override bool RequiresConstantRepaint () {
			// When the debug inspector is open we want to update it every frame
			// as the agent can move
			return debug && Application.isPlaying;
		}

		protected void AutoRepathInspector () {
			var mode = FindProperty("autoRepathBacking.mode");

			PropertyField(mode, "Recalculate paths automatically");
			if (!mode.hasMultipleDifferentValues) {
				var modeValue = (AutoRepathPolicy.Mode)mode.enumValueIndex;
				EditorGUI.indentLevel++;
				var period = FindProperty("autoRepathBacking.period");
				if (modeValue == AutoRepathPolicy.Mode.EveryNSeconds || modeValue == AutoRepathPolicy.Mode.Dynamic) {
					FloatField(period, min: 0f);
				}
				if (modeValue == AutoRepathPolicy.Mode.Dynamic) {
					EditorGUILayout.HelpBox("The path will be recalculated at least every " + period.floatValue.ToString("0.0") + " seconds, but more often if the destination changes quickly", MessageType.None);
				}
				EditorGUI.indentLevel--;
			}
		}

		protected void DebugInspector () {
			debug = EditorGUILayout.Foldout(debug, "Debug info");
			if (debug) {
				EditorGUI.indentLevel++;
				EditorGUI.BeginDisabledGroup(true);
				if (targets.Length == 1) {
					var ai = target as FollowerEntity;
					EditorGUILayout.Toggle("Reached Destination", ai.reachedDestination);
					EditorGUILayout.Toggle("Reached End Of Path", ai.reachedEndOfPath);
					EditorGUILayout.Toggle("Has Path", ai.hasPath);
					EditorGUILayout.Toggle("Path Pending", ai.pathPending);
					if (ai.isTraversingOffMeshLink) {
						EditorGUILayout.Toggle("Traversing off-mesh link", true);
					}
					EditorGUI.EndDisabledGroup();

					var newDestination = EditorGUILayout.Vector3Field("Destination", ai.destination);
					if (ai.entityExists) ai.destination = newDestination;

					EditorGUI.BeginDisabledGroup(true);
					EditorGUILayout.LabelField("Remaining distance", ai.remainingDistance.ToString("0.00"));
					EditorGUILayout.LabelField("Speed", ai.velocity.magnitude.ToString("0.00"));
				} else {
					int nReachedDestination = 0;
					int nReachedEndOfPath = 0;
					int nPending = 0;
					for (int i = 0; i < targets.Length; i++) {
						var ai = targets[i] as IAstarAI;
						if (ai.reachedDestination) nReachedDestination++;
						if (ai.reachedEndOfPath) nReachedEndOfPath++;
						if (ai.pathPending) nPending++;
					}
					EditorGUILayout.LabelField("Reached Destination", nReachedDestination + " of " + targets.Length);
					EditorGUILayout.LabelField("Reached End Of Path", nReachedEndOfPath + " of " + targets.Length);
					EditorGUILayout.LabelField("Path Pending", nPending + " of " + targets.Length);
				}
				EditorGUI.EndDisabledGroup();
				EditorGUI.indentLevel--;
			}
		}

		void PathfindingSettingsInspector () {
			bool anyCustomTraversalProvider = this.targets.Any(s => (s as FollowerEntity).pathfindingSettings.traversalProvider != null);
			if (anyCustomTraversalProvider) {
				EditorGUILayout.HelpBox("Custom traversal provider active", MessageType.None);
			}

			PropertyField("managedState.pathfindingSettings.graphMask", "Traversable Graphs");

			tagPenaltiesOpen = EditorGUILayout.Foldout(tagPenaltiesOpen, new GUIContent("Tags", "Settings for each tag"));
			if (tagPenaltiesOpen) {
				EditorGUI.indentLevel++;
				var traversableTags = this.targets.Select(s => (s as FollowerEntity).pathfindingSettings.traversableTags).ToArray();
				SeekerEditor.TagsEditor(FindProperty("managedState.pathfindingSettings.tagPenalties"), traversableTags);
				for (int i = 0; i < targets.Length; i++) {
					(targets[i] as FollowerEntity).pathfindingSettings.traversableTags = traversableTags[i];
				}
				EditorGUI.indentLevel--;
			}
		}

		protected override void Inspector () {
			Undo.RecordObjects(targets, "Modify FollowerEntity settings");
			EditorGUI.BeginChangeCheck();
			Section("Shape");
			FloatField("shape.radius", min: 0.01f);
			FloatField("shape.height", min: 0.01f);
			Popup("orientationBacking", new [] { new GUIContent("ZAxisForward (for 3D games)"), new GUIContent("YAxisForward (for 2D games)") }, "Orientation");

			Section("Movement");
			FloatField("movement.follower.speed", min: 0f);
			FloatField("movement.follower.rotationSpeed", min: 0f);
			var maxRotationSpeed = FindProperty("movement.follower.rotationSpeed");
			FloatField("movement.follower.maxRotationSpeed", min: maxRotationSpeed.hasMultipleDifferentValues ? 0f : maxRotationSpeed.floatValue);
			if (ByteAsToggle("movement.follower.allowRotatingOnSpotBacking", "Allow Rotating On The Spot")) {
				EditorGUI.indentLevel++;
				FloatField("movement.follower.maxOnSpotRotationSpeed", min: 0f);
				FloatField("movement.follower.slowdownTimeWhenTurningOnSpot", min: 0f);
				EditorGUI.indentLevel--;
			}
			Slider("movement.positionSmoothing", left: 0f, right: 0.5f);
			Slider("movement.rotationSmoothing", left: 0f, right: 0.5f);
			FloatField("movement.follower.slowdownTime", min: 0f);
			FloatField("movement.stopDistance", min: 0f);
			FloatField("movement.follower.leadInRadiusWhenApproachingDestination", min: 0f);
			FloatField("movement.follower.desiredWallDistance", min: 0f);

			if (PropertyField("managedState.enableGravity", "Gravity")) {
				EditorGUI.indentLevel++;
				PropertyField("movement.groundMask", "Raycast Ground Mask");
				EditorGUI.indentLevel--;
			}
			var movementPlaneSource = FindProperty("movementPlaneSourceBacking");
			PropertyField(movementPlaneSource, "Movement Plane Source");
			if (AstarPath.active != null && AstarPath.active.data.graphs != null) {
				var possiblySpherical = AstarPath.active.data.navmesh != null && !AstarPath.active.data.navmesh.RecalculateNormals;
				if (!possiblySpherical && !movementPlaneSource.hasMultipleDifferentValues && (MovementPlaneSource)movementPlaneSource.intValue == MovementPlaneSource.Raycast) {
					EditorGUILayout.HelpBox("Using raycasts as the movement plane source is only recommended if you have a spherical or otherwise non-planar world. It has a performance overhead.", MessageType.Info);
				}
				if (!possiblySpherical && !movementPlaneSource.hasMultipleDifferentValues && (MovementPlaneSource)movementPlaneSource.intValue == MovementPlaneSource.NavmeshNormal) {
					EditorGUILayout.HelpBox("Using the navmesh normal as the movement plane source is only recommended if you have a spherical or otherwise non-planar world. It has a performance overhead.", MessageType.Info);
				}
			}

			Section("Pathfinding");
			PathfindingSettingsInspector();
			AutoRepathInspector();


			if (SectionEnableable("Local Avoidance", "managedState.enableLocalAvoidance")) {
				if (Application.isPlaying && RVOSimulator.active == null && !EditorUtility.IsPersistent(target)) {
					EditorGUILayout.HelpBox("There is no enabled RVOSimulator component in the scene. A single global RVOSimulator component is required for local avoidance.", MessageType.Warning);
				}
				FloatField("managedState.rvoSettings.agentTimeHorizon", min: 0f, max: 20.0f);
				FloatField("managedState.rvoSettings.obstacleTimeHorizon", min: 0f, max: 20.0f);
				PropertyField("managedState.rvoSettings.maxNeighbours");
				ClampInt("managedState.rvoSettings.maxNeighbours", min: 0, max: SimulatorBurst.MaxNeighbourCount);
				PropertyField("managedState.rvoSettings.layer");
				PropertyField("managedState.rvoSettings.collidesWith");
				Slider("managedState.rvoSettings.priority", left: 0f, right: 1.0f);
				PropertyField("managedState.rvoSettings.locked");
			}

			Section("Debug");
			PropertyField("movement.debugFlags", "Movement Debug Rendering");
			PropertyField("managedState.rvoSettings.debug", "Local Avoidance Debug Rendering");
			DebugInspector();

			if (EditorGUI.EndChangeCheck()) {
				for (int i = 0; i < targets.Length; i++) {
					var script = targets[i] as FollowerEntity;
					script.SyncWithEntity();
				}
			}
		}
	}
}
#else
using UnityEditor;
using UnityEngine;
using Pathfinding.ECS;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;

namespace Pathfinding {
	// This inspector is only used if the Entities package is not installed
	[CustomEditor(typeof(FollowerEntity), true)]
	[CanEditMultipleObjects]
	public class FollowerEntityEditor : EditorBase {
		static AddRequest addRequest;

		protected override void Inspector () {
			if (addRequest != null) {
				if (addRequest.Status == StatusCode.Success) {
					addRequest = null;

					// If we get this far, unity did not successfully reload the assemblies.
					// Who knows what went wrong. Quite possibly restarting Unity will resolve the issue.
					EditorUtility.DisplayDialog("Installed Entities package", "The entities package has been installed. You may have to restart the editor for changes to take effect.", "Ok");
				} else if (addRequest.Status == StatusCode.Failure) {
					EditorGUILayout.HelpBox("Failed to install the Entities package. Please install it manually using the Package Manager." + (addRequest.Error != null ? "\n" + addRequest.Error.message : ""), MessageType.Error);
				} else {
					EditorGUILayout.HelpBox("Installing entities package...", MessageType.None);
				}
			} else {
				EditorGUILayout.HelpBox("This component requires the Entities package to be installed. Please install it using the Package Manager.", MessageType.Error);
				if (GUILayout.Button("Install entities package")) {
					addRequest = Client.Add("com.unity.entities");
				}
			}
		}
	}
}
#endif