RecastTileUpdateHandler.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. using UnityEngine;
  2. namespace Pathfinding {
  3. /// <summary>
  4. /// Helper for easier fast updates to recast graphs.
  5. ///
  6. /// When updating recast graphs, you might just have plonked down a few
  7. /// GraphUpdateScene objects or issued a few GraphUpdateObjects to the
  8. /// system. This works fine if you are only issuing a few but the problem
  9. /// is that they don't have any coordination in between themselves. So if
  10. /// you have 10 GraphUpdateScene objects in one tile, they will all update
  11. /// that tile (10 times in total) instead of just updating it once which
  12. /// is all that is required (meaning it will be 10 times slower than just
  13. /// updating one tile). This script exists to help with updating only the
  14. /// tiles that need updating and only updating them once instead of
  15. /// multiple times.
  16. ///
  17. /// It is coupled with the RecastTileUpdate component, which works a bit
  18. /// like the GraphUpdateScene component, just with fewer options. You can
  19. /// attach the RecastTileUpdate to any GameObject to have it schedule an
  20. /// update for the tile(s) that contain the GameObject. E.g if you are
  21. /// creating a new building somewhere, you can attach the RecastTileUpdate
  22. /// component to it to make it update the graph when it is instantiated.
  23. ///
  24. /// If a single tile contains multiple RecastTileUpdate components and
  25. /// many try to update the graph at the same time, only one tile update
  26. /// will be done, which greatly improves performance.
  27. ///
  28. /// If you have objects that are instantiated at roughly the same time
  29. /// but not exactly the same frame, you can use the maxThrottlingDelay
  30. /// field. It will delay updates up to that number of seconds to allow
  31. /// more updates to be batched together.
  32. ///
  33. /// Note: You should only have one instance of this script in the scene
  34. /// if you only have a single recast graph. If you have more than one
  35. /// graph you can have more than one instance of this script but you need
  36. /// to manually call the SetGraph method to configure it with the correct
  37. /// graph.
  38. ///
  39. /// Note: This does not use navmesh cutting. If you only ever add
  40. /// obstacles, but never add any new walkable surfaces then you might
  41. /// want to use navmesh cutting instead. See navmeshcutting (view in online documentation for working links).
  42. /// </summary>
  43. [AddComponentMenu("Pathfinding/Navmesh/RecastTileUpdateHandler")]
  44. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_recast_tile_update_handler.php")]
  45. public class RecastTileUpdateHandler : MonoBehaviour {
  46. /// <summary>Graph that handles the updates</summary>
  47. RecastGraph graph;
  48. /// <summary>True for a tile if it needs updating</summary>
  49. bool[] dirtyTiles;
  50. /// <summary>True if any elements in dirtyTiles are true</summary>
  51. bool anyDirtyTiles = false;
  52. /// <summary>Earliest update request we are handling right now</summary>
  53. float earliestDirty = float.NegativeInfinity;
  54. /// <summary>All tile updates will be performed within (roughly) this number of seconds</summary>
  55. public float maxThrottlingDelay = 0.5f;
  56. public void SetGraph (RecastGraph graph) {
  57. this.graph = graph;
  58. if (graph == null)
  59. return;
  60. dirtyTiles = new bool[graph.tileXCount*graph.tileZCount];
  61. anyDirtyTiles = false;
  62. }
  63. /// <summary>Requests an update to all tiles which touch the specified bounds</summary>
  64. public void ScheduleUpdate (Bounds bounds) {
  65. if (graph == null) {
  66. // If no graph has been set, use the first graph available
  67. if (AstarPath.active != null) {
  68. SetGraph(AstarPath.active.data.recastGraph);
  69. }
  70. if (graph == null) {
  71. Debug.LogError("Received tile update request (from RecastTileUpdate), but no RecastGraph could be found to handle it");
  72. return;
  73. }
  74. }
  75. // Make sure that tiles which do not strictly
  76. // contain this bounds object but which still
  77. // might need to be updated are actually updated
  78. int voxelCharacterRadius = Mathf.CeilToInt(graph.characterRadius/graph.cellSize);
  79. int borderSize = voxelCharacterRadius + 3;
  80. // Expand borderSize voxels on each side
  81. bounds.Expand(new Vector3(borderSize, 0, borderSize)*graph.cellSize*2);
  82. var touching = graph.GetTouchingTiles(bounds);
  83. if (touching.Width * touching.Height > 0) {
  84. if (!anyDirtyTiles) {
  85. earliestDirty = Time.time;
  86. anyDirtyTiles = true;
  87. }
  88. for (int z = touching.ymin; z <= touching.ymax; z++) {
  89. for (int x = touching.xmin; x <= touching.xmax; x++) {
  90. dirtyTiles[z*graph.tileXCount + x] = true;
  91. }
  92. }
  93. }
  94. }
  95. void OnEnable () {
  96. RecastTileUpdate.OnNeedUpdates += ScheduleUpdate;
  97. }
  98. void OnDisable () {
  99. RecastTileUpdate.OnNeedUpdates -= ScheduleUpdate;
  100. }
  101. void Update () {
  102. if (anyDirtyTiles && Time.time - earliestDirty >= maxThrottlingDelay && graph != null) {
  103. UpdateDirtyTiles();
  104. }
  105. }
  106. /// <summary>Update all dirty tiles now</summary>
  107. public void UpdateDirtyTiles () {
  108. if (graph == null) {
  109. new System.InvalidOperationException("No graph is set on this object");
  110. }
  111. if (graph.tileXCount * graph.tileZCount != dirtyTiles.Length) {
  112. Debug.LogError("Graph has changed dimensions. Clearing queued graph updates and resetting.");
  113. SetGraph(graph);
  114. return;
  115. }
  116. for (int z = 0; z < graph.tileZCount; z++) {
  117. for (int x = 0; x < graph.tileXCount; x++) {
  118. if (dirtyTiles[z*graph.tileXCount + x]) {
  119. dirtyTiles[z*graph.tileXCount + x] = false;
  120. var bounds = graph.GetTileBounds(x, z);
  121. // Shrink it a bit to make sure other tiles
  122. // are not included because of rounding errors
  123. bounds.extents *= 0.5f;
  124. var guo = new GraphUpdateObject(bounds);
  125. guo.nnConstraint.graphMask = 1 << (int)graph.graphIndex;
  126. AstarPath.active.UpdateGraphs(guo);
  127. }
  128. }
  129. }
  130. anyDirtyTiles = false;
  131. }
  132. }
  133. }