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
|
using UnityEditor;
using UnityEngine;
namespace Pathfinding {
[CustomEditor(typeof(AIBase), true)]
[CanEditMultipleObjects]
public class BaseAIEditor : EditorBase {
float lastSeenCustomGravity = float.NegativeInfinity;
bool debug = false;
protected void AutoRepathInspector () {
var mode = FindProperty("autoRepath.mode");
PropertyField(mode, "Recalculate paths automatically");
if (!mode.hasMultipleDifferentValues) {
var modeValue = (AutoRepathPolicy.Mode)mode.enumValueIndex;
EditorGUI.indentLevel++;
if (modeValue == AutoRepathPolicy.Mode.EveryNSeconds) {
FloatField("autoRepath.period", min: 0f);
} else if (modeValue == AutoRepathPolicy.Mode.Dynamic) {
var maxInterval = FindProperty("autoRepath.maximumPeriod");
FloatField(maxInterval, min: 0f);
Slider("autoRepath.sensitivity", 1.0f, 20.0f);
if (PropertyField("autoRepath.visualizeSensitivity")) {
EditorGUILayout.HelpBox("When the game is running the sensitivity will be visualized as a magenta circle. The path will be recalculated immediately if the destination is outside the circle and more quickly if it is close to the edge.", MessageType.None);
}
EditorGUILayout.HelpBox("The path will be recalculated at least every " + maxInterval.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) {
var ai = target as IAstarAI;
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.Toggle("Reached Destination", ai.reachedDestination);
EditorGUILayout.Toggle("Reached End Of Path", ai.reachedEndOfPath);
if (ai is AIBase aiBase && aiBase.rvoDensityBehavior.enabled) {
EditorGUILayout.Toggle("Destination is crowded", aiBase.rvoDensityBehavior.lastJobDensityResult);
}
EditorGUILayout.Toggle("Path Pending", ai.pathPending);
EditorGUILayout.Vector3Field("Destination", ai.destination);
EditorGUILayout.LabelField("Remaining distance", ai.remainingDistance.ToString("0.00"));
EditorGUI.EndDisabledGroup();
}
}
protected override void Inspector () {
var isAIPath = typeof(AIPath).IsAssignableFrom(target.GetType());
Section("Shape");
FloatField("radius", min: 0.01f);
FloatField("height", min: 0.01f);
Section("Pathfinding");
AutoRepathInspector();
Section("Movement");
PropertyField("canMove");
FloatField("maxSpeed", min: 0f);
if (isAIPath) {
EditorGUI.BeginChangeCheck();
var acceleration = FindProperty("maxAcceleration");
int acc = acceleration.hasMultipleDifferentValues ? -1 : (acceleration.floatValue >= 0 ? 1 : 0);
var nacc = EditorGUILayout.Popup("Max Acceleration", acc, new [] { "Default", "Custom" });
if (EditorGUI.EndChangeCheck()) {
if (nacc == 0) acceleration.floatValue = -2.5f;
else if (acceleration.floatValue < 0) acceleration.floatValue = 10;
}
if (!acceleration.hasMultipleDifferentValues && nacc == 1) {
EditorGUI.indentLevel++;
PropertyField(acceleration.propertyPath);
EditorGUI.indentLevel--;
acceleration.floatValue = Mathf.Max(acceleration.floatValue, 0.01f);
}
Popup("orientation", new [] { new GUIContent("ZAxisForward (for 3D games)"), new GUIContent("YAxisForward (for 2D games)") });
} else {
FloatField("acceleration", min: 0f);
// The RichAI script doesn't really support any orientation other than Z axis forward, so don't expose it in the inspector
FindProperty("orientation").enumValueIndex = (int)OrientationMode.ZAxisForward;
}
if (PropertyField("enableRotation")) {
EditorGUI.indentLevel++;
FloatField("rotationSpeed", min: 0f);
if (PropertyField("slowWhenNotFacingTarget")) {
EditorGUI.indentLevel++;
PropertyField("preventMovingBackwards");
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
}
if (isAIPath) {
FloatField("pickNextWaypointDist", min: 0f);
FloatField("slowdownDistance", min: 0f);
} else {
FloatField("slowdownTime", min: 0f);
FloatField("wallForce", min: 0f);
FloatField("wallDist", min: 0f);
PropertyField("funnelSimplification");
}
FloatField("endReachedDistance", min: 0f);
PropertyField("whenCloseToDestination");
if (isAIPath) {
PropertyField("alwaysDrawGizmos");
PropertyField("constrainInsideGraph");
}
var mono = target as MonoBehaviour;
mono.TryGetComponent<Pathfinding.RVO.RVOController>(out Pathfinding.RVO.RVOController rvoController);
if (rvoController && rvoController.enabled) {
if (PropertyField("rvoDensityBehavior.enabled", "Stop when destination is crowded")) {
EditorGUI.indentLevel++;
PropertyField("rvoDensityBehavior.densityThreshold");
PropertyField("rvoDensityBehavior.returnAfterBeingPushedAway");
EditorGUI.indentLevel--;
}
} else {
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.Toggle(new GUIContent("Stop when destination is crowded", "Requires an attached RVOController"), false);
EditorGUI.EndDisabledGroup();
}
mono.TryGetComponent<Rigidbody>(out Rigidbody rigid);
mono.TryGetComponent<Rigidbody2D>(out Rigidbody2D rigid2D);
mono.TryGetComponent<CharacterController>(out CharacterController controller);
var canUseGravity = (controller != null && controller.enabled) || ((rigid == null || rigid.isKinematic) && (rigid2D == null || rigid2D.isKinematic));
var gravity = FindProperty("gravity");
var groundMask = FindProperty("groundMask");
if (canUseGravity) {
EditorGUI.BeginChangeCheck();
int grav = gravity.hasMultipleDifferentValues ? -1 : (gravity.vector3Value == Vector3.zero ? 0 : (float.IsNaN(gravity.vector3Value.x) ? 1 : 2));
var ngrav = EditorGUILayout.Popup("Gravity", grav, new [] { "None", "Use Project Settings", "Custom" });
if (EditorGUI.EndChangeCheck()) {
if (ngrav == 0) gravity.vector3Value = Vector3.zero;
else if (ngrav == 1) gravity.vector3Value = new Vector3(float.NaN, float.NaN, float.NaN);
else if (float.IsNaN(gravity.vector3Value.x) || gravity.vector3Value == Vector3.zero) gravity.vector3Value = Physics.gravity;
lastSeenCustomGravity = float.NegativeInfinity;
}
if (!gravity.hasMultipleDifferentValues) {
// A sort of delayed Vector3 field (to prevent the field from dissappearing if you happen to enter zeroes into x, y and z for a short time)
// Note: cannot use != in this case because that will not give the correct result in case of NaNs
if (!(gravity.vector3Value == Vector3.zero)) lastSeenCustomGravity = Time.realtimeSinceStartup;
if (Time.realtimeSinceStartup - lastSeenCustomGravity < 2f) {
EditorGUI.indentLevel++;
if (!float.IsNaN(gravity.vector3Value.x)) {
PropertyField(gravity.propertyPath);
}
if (controller == null || !controller.enabled) {
PropertyField(groundMask.propertyPath, "Raycast Ground Mask");
}
EditorGUI.indentLevel--;
}
}
} else {
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.Popup(new GUIContent(gravity.displayName, "Disabled because a non-kinematic rigidbody is attached"), 0, new [] { new GUIContent("Handled by Rigidbody") });
EditorGUI.EndDisabledGroup();
}
DebugInspector();
if ((rigid != null || rigid2D != null) && (controller != null && controller.enabled)) {
EditorGUILayout.HelpBox("You are using both a Rigidbody and a Character Controller. Those components are not really designed for that. Please use only one of them.", MessageType.Warning);
}
var isRichAI = typeof(RichAI).IsAssignableFrom(target.GetType());
if (isRichAI && Application.isPlaying && AstarPath.active != null && AstarPath.active.graphs.Length > 0 && AstarPath.active.data.recastGraph == null && AstarPath.active.data.navmesh == null) {
EditorGUILayout.HelpBox("This script only works with a navmesh or recast graph. If you are using some other graph type you might want to use another movement script.", MessageType.Warning);
}
}
}
}
|