DynamicGridObstacle.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. namespace Pathfinding {
  4. /// <summary>
  5. /// Attach this script to any obstacle with a collider to enable dynamic updates of the graphs around it.
  6. /// When the object has moved or rotated at least <see cref="updateError"/> world units
  7. /// then it will call AstarPath.UpdateGraphs and update the graph around it.
  8. ///
  9. /// Make sure that any children colliders do not extend beyond the bounds of the collider attached to the
  10. /// GameObject that the DynamicGridObstacle component is attached to since this script only updates the graph
  11. /// around the bounds of the collider on the same GameObject.
  12. ///
  13. /// An update will be triggered whenever the bounding box of the attached collider has changed (moved/expanded/etc.) by at least <see cref="updateError"/> world units or if
  14. /// the GameObject has rotated enough so that the outmost point of the object has moved at least <see cref="updateError"/> world units.
  15. ///
  16. /// This script works with both 2D colliders and normal 3D colliders.
  17. ///
  18. /// Note: This script works best with a GridGraph, PointGraph or LayerGridGraph
  19. /// You can use this with recast graphs as well. However since recast graph updates are much slower it is recommended to use the <see cref="Pathfinding.NavmeshCut"/> component if at all possible.
  20. ///
  21. /// See: AstarPath.UpdateGraphs
  22. /// See: graph-updates (view in online documentation for working links)
  23. /// See: navmeshcutting (view in online documentation for working links)
  24. /// </summary>
  25. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_dynamic_grid_obstacle.php")]
  26. public class DynamicGridObstacle : GraphModifier {
  27. /// <summary>Collider to get bounds information from</summary>
  28. Collider coll;
  29. /// <summary>2D Collider to get bounds information from</summary>
  30. Collider2D coll2D;
  31. /// <summary>Cached transform component</summary>
  32. Transform tr;
  33. /// <summary>The minimum change in world units along one of the axis of the bounding box of the collider to trigger a graph update</summary>
  34. public float updateError = 1;
  35. /// <summary>
  36. /// Time in seconds between bounding box checks.
  37. /// If AstarPath.batchGraphUpdates is enabled, it is not beneficial to have a checkTime much lower
  38. /// than AstarPath.graphUpdateBatchingInterval because that will just add extra unnecessary graph updates.
  39. ///
  40. /// In real time seconds (based on Time.realtimeSinceStartup).
  41. /// </summary>
  42. public float checkTime = 0.2F;
  43. /// <summary>Bounds of the collider the last time the graphs were updated</summary>
  44. Bounds prevBounds;
  45. /// <summary>Rotation of the collider the last time the graphs were updated</summary>
  46. Quaternion prevRotation;
  47. /// <summary>True if the collider was enabled last time the graphs were updated</summary>
  48. bool prevEnabled;
  49. float lastCheckTime = -9999;
  50. Queue<GraphUpdateObject> pendingGraphUpdates = new Queue<GraphUpdateObject>();
  51. Bounds bounds {
  52. get {
  53. if (coll != null) {
  54. return coll.bounds;
  55. } else {
  56. var b = coll2D.bounds;
  57. // Make sure the bounding box stretches close to infinitely along the Z axis (which is the axis perpendicular to the 2D plane).
  58. // We don't want any change along the Z axis to make a difference.
  59. b.extents += new Vector3(0, 0, 10000);
  60. return b;
  61. }
  62. }
  63. }
  64. bool colliderEnabled {
  65. get {
  66. return coll != null ? coll.enabled : coll2D.enabled;
  67. }
  68. }
  69. protected override void Awake () {
  70. base.Awake();
  71. coll = GetComponent<Collider>();
  72. coll2D = GetComponent<Collider2D>();
  73. tr = transform;
  74. if (coll == null && coll2D == null && Application.isPlaying) {
  75. throw new System.Exception("A collider or 2D collider must be attached to the GameObject(" + gameObject.name + ") for the DynamicGridObstacle to work");
  76. }
  77. prevBounds = bounds;
  78. prevRotation = tr.rotation;
  79. // Make sure we update the graph as soon as we find that the collider is enabled
  80. prevEnabled = false;
  81. }
  82. public override void OnPostScan () {
  83. // Make sure we find the collider
  84. // AstarPath.Awake may run before Awake on this component
  85. if (coll == null) Awake();
  86. // In case the object was in the scene from the start and the graphs
  87. // were scanned then we ignore the first update since it is unnecessary.
  88. if (coll != null) prevEnabled = colliderEnabled;
  89. }
  90. void Update () {
  91. if (!Application.isPlaying) return;
  92. if (coll == null && coll2D == null) {
  93. Debug.LogError("Removed collider from DynamicGridObstacle", this);
  94. enabled = false;
  95. return;
  96. }
  97. // Check if the previous graph updates have been completed yet.
  98. // We don't want to update the graph again until the last graph updates are done.
  99. // This is particularly important for recast graphs for which graph updates can take a long time.
  100. while (pendingGraphUpdates.Count > 0 && pendingGraphUpdates.Peek().stage != GraphUpdateStage.Pending) {
  101. pendingGraphUpdates.Dequeue();
  102. }
  103. if (AstarPath.active == null || AstarPath.active.isScanning || Time.realtimeSinceStartup - lastCheckTime < checkTime || !Application.isPlaying || pendingGraphUpdates.Count > 0) {
  104. return;
  105. }
  106. lastCheckTime = Time.realtimeSinceStartup;
  107. if (colliderEnabled) {
  108. // The current bounds of the collider
  109. Bounds newBounds = bounds;
  110. var newRotation = tr.rotation;
  111. Vector3 minDiff = prevBounds.min - newBounds.min;
  112. Vector3 maxDiff = prevBounds.max - newBounds.max;
  113. var extents = newBounds.extents.magnitude;
  114. // This is the distance that a point furthest out on the bounding box
  115. // would have moved due to the changed rotation of the object
  116. var errorFromRotation = extents*Quaternion.Angle(prevRotation, newRotation)*Mathf.Deg2Rad;
  117. // If the difference between the previous bounds and the new bounds is greater than some value, update the graphs
  118. if (minDiff.sqrMagnitude > updateError*updateError || maxDiff.sqrMagnitude > updateError*updateError ||
  119. errorFromRotation > updateError || !prevEnabled) {
  120. // Update the graphs as soon as possible
  121. DoUpdateGraphs();
  122. }
  123. } else {
  124. // Collider has just been disabled
  125. if (prevEnabled) {
  126. DoUpdateGraphs();
  127. }
  128. }
  129. }
  130. /// <summary>
  131. /// Revert graphs when disabled.
  132. /// When the DynamicObstacle is disabled or destroyed, a last graph update should be done to revert nodes to their original state
  133. /// </summary>
  134. protected override void OnDisable () {
  135. base.OnDisable();
  136. if (AstarPath.active != null && Application.isPlaying) {
  137. var guo = new GraphUpdateObject(prevBounds);
  138. pendingGraphUpdates.Enqueue(guo);
  139. AstarPath.active.UpdateGraphs(guo);
  140. prevEnabled = false;
  141. }
  142. // Stop caring about pending graph updates if this object is disabled.
  143. // This avoids a memory leak since `Update` will never be called again to remove pending updates
  144. // that have been completed.
  145. pendingGraphUpdates.Clear();
  146. }
  147. /// <summary>
  148. /// Update the graphs around this object.
  149. /// Note: The graphs will not be updated immediately since the pathfinding threads need to be paused first.
  150. /// If you want to guarantee that the graphs have been updated then call AstarPath.active.FlushGraphUpdates()
  151. /// after the call to this method.
  152. /// </summary>
  153. public void DoUpdateGraphs () {
  154. if (coll == null && coll2D == null) return;
  155. // Required to ensure we get the most up to date bounding box from the physics engine
  156. UnityEngine.Physics.SyncTransforms();
  157. UnityEngine.Physics2D.SyncTransforms();
  158. if (!colliderEnabled) {
  159. // If the collider is not enabled, then col.bounds will empty
  160. // so just update prevBounds
  161. var guo = new GraphUpdateObject(prevBounds);
  162. pendingGraphUpdates.Enqueue(guo);
  163. AstarPath.active.UpdateGraphs(guo);
  164. } else {
  165. Bounds newBounds = bounds;
  166. Bounds merged = newBounds;
  167. merged.Encapsulate(prevBounds);
  168. // Check what seems to be fastest, to update the union of prevBounds and newBounds in a single request
  169. // or to update them separately, the smallest volume is usually the fastest
  170. if (BoundsVolume(merged) < BoundsVolume(newBounds) + BoundsVolume(prevBounds)) {
  171. // Send an update request to update the nodes inside the 'merged' volume
  172. var guo = new GraphUpdateObject(merged);
  173. pendingGraphUpdates.Enqueue(guo);
  174. AstarPath.active.UpdateGraphs(guo);
  175. } else {
  176. // Send two update request to update the nodes inside the 'prevBounds' and 'newBounds' volumes
  177. var guo1 = new GraphUpdateObject(prevBounds);
  178. var guo2 = new GraphUpdateObject(newBounds);
  179. pendingGraphUpdates.Enqueue(guo1);
  180. pendingGraphUpdates.Enqueue(guo2);
  181. AstarPath.active.UpdateGraphs(guo1);
  182. AstarPath.active.UpdateGraphs(guo2);
  183. }
  184. #if ASTARDEBUG
  185. Debug.DrawLine(prevBounds.min, prevBounds.max, Color.yellow);
  186. Debug.DrawLine(newBounds.min, newBounds.max, Color.red);
  187. #endif
  188. prevBounds = newBounds;
  189. }
  190. prevEnabled = colliderEnabled;
  191. prevRotation = tr.rotation;
  192. // Set this here as well since the DoUpdateGraphs method can be called from other scripts
  193. lastCheckTime = Time.realtimeSinceStartup;
  194. }
  195. /// <summary>Volume of a Bounds object. X*Y*Z</summary>
  196. static float BoundsVolume (Bounds b) {
  197. return System.Math.Abs(b.size.x * b.size.y * b.size.z);
  198. }
  199. }
  200. }