AIBaseEditor.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. using UnityEditor;
  2. using UnityEngine;
  3. namespace Pathfinding {
  4. [CustomEditor(typeof(AIBase), true)]
  5. [CanEditMultipleObjects]
  6. public class BaseAIEditor : EditorBase {
  7. float lastSeenCustomGravity = float.NegativeInfinity;
  8. bool debug = false;
  9. protected void AutoRepathInspector () {
  10. var mode = FindProperty("autoRepath.mode");
  11. PropertyField(mode, "Recalculate paths automatically");
  12. if (!mode.hasMultipleDifferentValues) {
  13. var modeValue = (AutoRepathPolicy.Mode)mode.enumValueIndex;
  14. EditorGUI.indentLevel++;
  15. if (modeValue == AutoRepathPolicy.Mode.EveryNSeconds) {
  16. FloatField("autoRepath.period", min: 0f);
  17. } else if (modeValue == AutoRepathPolicy.Mode.Dynamic) {
  18. var maxInterval = FindProperty("autoRepath.maximumPeriod");
  19. FloatField(maxInterval, min: 0f);
  20. Slider("autoRepath.sensitivity", 1.0f, 20.0f);
  21. if (PropertyField("autoRepath.visualizeSensitivity")) {
  22. 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);
  23. }
  24. 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);
  25. }
  26. EditorGUI.indentLevel--;
  27. }
  28. }
  29. protected void DebugInspector () {
  30. debug = EditorGUILayout.Foldout(debug, "Debug info");
  31. if (debug) {
  32. var ai = target as IAstarAI;
  33. EditorGUI.BeginDisabledGroup(true);
  34. EditorGUILayout.Toggle("Reached Destination", ai.reachedDestination);
  35. EditorGUILayout.Toggle("Reached End Of Path", ai.reachedEndOfPath);
  36. EditorGUILayout.Toggle("Path Pending", ai.pathPending);
  37. EditorGUILayout.Vector3Field("Destination", ai.destination);
  38. EditorGUILayout.LabelField("Remaining distance", ai.remainingDistance.ToString("0.00"));
  39. EditorGUI.EndDisabledGroup();
  40. }
  41. }
  42. protected override void Inspector () {
  43. var isAIPath = typeof(AIPath).IsAssignableFrom(target.GetType());
  44. Section("Shape");
  45. FloatField("radius", min: 0.01f);
  46. FloatField("height", min: 0.01f);
  47. Section("Pathfinding");
  48. AutoRepathInspector();
  49. Section("Movement");
  50. PropertyField("canMove");
  51. FloatField("maxSpeed", min: 0f);
  52. if (isAIPath) {
  53. EditorGUI.BeginChangeCheck();
  54. var acceleration = FindProperty("maxAcceleration");
  55. int acc = acceleration.hasMultipleDifferentValues ? -1 : (acceleration.floatValue >= 0 ? 1 : 0);
  56. var nacc = EditorGUILayout.Popup("Max Acceleration", acc, new [] { "Default", "Custom" });
  57. if (EditorGUI.EndChangeCheck()) {
  58. if (nacc == 0) acceleration.floatValue = -2.5f;
  59. else if (acceleration.floatValue < 0) acceleration.floatValue = 10;
  60. }
  61. if (!acceleration.hasMultipleDifferentValues && nacc == 1) {
  62. EditorGUI.indentLevel++;
  63. PropertyField(acceleration.propertyPath);
  64. EditorGUI.indentLevel--;
  65. acceleration.floatValue = Mathf.Max(acceleration.floatValue, 0.01f);
  66. }
  67. Popup("orientation", new [] { new GUIContent("ZAxisForward (for 3D games)"), new GUIContent("YAxisForward (for 2D games)") });
  68. } else {
  69. FloatField("acceleration", min: 0f);
  70. // The RichAI script doesn't really support any orientation other than Z axis forward, so don't expose it in the inspector
  71. FindProperty("orientation").enumValueIndex = (int)OrientationMode.ZAxisForward;
  72. }
  73. if (PropertyField("enableRotation")) {
  74. EditorGUI.indentLevel++;
  75. FloatField("rotationSpeed", min: 0f);
  76. PropertyField("slowWhenNotFacingTarget");
  77. EditorGUI.indentLevel--;
  78. }
  79. if (isAIPath) {
  80. FloatField("pickNextWaypointDist", min: 0f);
  81. FloatField("slowdownDistance", min: 0f);
  82. } else {
  83. FloatField("slowdownTime", min: 0f);
  84. FloatField("wallForce", min: 0f);
  85. FloatField("wallDist", min: 0f);
  86. PropertyField("funnelSimplification");
  87. }
  88. FloatField("endReachedDistance", min: 0f);
  89. if (isAIPath) {
  90. PropertyField("alwaysDrawGizmos");
  91. PropertyField("whenCloseToDestination");
  92. PropertyField("constrainInsideGraph");
  93. }
  94. var mono = target as MonoBehaviour;
  95. mono.TryGetComponent<Rigidbody>(out Rigidbody rigid);
  96. mono.TryGetComponent<Rigidbody2D>(out Rigidbody2D rigid2D);
  97. mono.TryGetComponent<CharacterController>(out CharacterController controller);
  98. var canUseGravity = (controller != null && controller.enabled) || ((rigid == null || rigid.isKinematic) && (rigid2D == null || rigid2D.isKinematic));
  99. var gravity = FindProperty("gravity");
  100. var groundMask = FindProperty("groundMask");
  101. if (canUseGravity) {
  102. EditorGUI.BeginChangeCheck();
  103. int grav = gravity.hasMultipleDifferentValues ? -1 : (gravity.vector3Value == Vector3.zero ? 0 : (float.IsNaN(gravity.vector3Value.x) ? 1 : 2));
  104. var ngrav = EditorGUILayout.Popup("Gravity", grav, new [] { "None", "Use Project Settings", "Custom" });
  105. if (EditorGUI.EndChangeCheck()) {
  106. if (ngrav == 0) gravity.vector3Value = Vector3.zero;
  107. else if (ngrav == 1) gravity.vector3Value = new Vector3(float.NaN, float.NaN, float.NaN);
  108. else if (float.IsNaN(gravity.vector3Value.x) || gravity.vector3Value == Vector3.zero) gravity.vector3Value = Physics.gravity;
  109. lastSeenCustomGravity = float.NegativeInfinity;
  110. }
  111. if (!gravity.hasMultipleDifferentValues) {
  112. // 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)
  113. // Note: cannot use != in this case because that will not give the correct result in case of NaNs
  114. if (!(gravity.vector3Value == Vector3.zero)) lastSeenCustomGravity = Time.realtimeSinceStartup;
  115. if (Time.realtimeSinceStartup - lastSeenCustomGravity < 2f) {
  116. EditorGUI.indentLevel++;
  117. if (!float.IsNaN(gravity.vector3Value.x)) {
  118. PropertyField(gravity.propertyPath);
  119. }
  120. if (controller == null || !controller.enabled) {
  121. PropertyField(groundMask.propertyPath, "Raycast Ground Mask");
  122. }
  123. EditorGUI.indentLevel--;
  124. }
  125. }
  126. } else {
  127. EditorGUI.BeginDisabledGroup(true);
  128. EditorGUILayout.Popup(new GUIContent(gravity.displayName, "Disabled because a non-kinematic rigidbody is attached"), 0, new [] { new GUIContent("Handled by Rigidbody") });
  129. EditorGUI.EndDisabledGroup();
  130. }
  131. DebugInspector();
  132. if ((rigid != null || rigid2D != null) && (controller != null && controller.enabled)) {
  133. 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);
  134. }
  135. var isRichAI = typeof(RichAI).IsAssignableFrom(target.GetType());
  136. if (isRichAI && Application.isPlaying && AstarPath.active != null && AstarPath.active.graphs.Length > 0 && AstarPath.active.data.recastGraph == null && AstarPath.active.data.navmesh == null) {
  137. 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);
  138. }
  139. }
  140. }
  141. }