RandomPath.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. using UnityEngine;
  2. namespace Pathfinding {
  3. /// <summary>
  4. /// Finds a path in a random direction from the start node.
  5. ///
  6. /// Terminates and returns when G \>= length (passed to the constructor) + RandomPath.spread or when there are no more nodes left to search.
  7. ///
  8. /// <code>
  9. ///
  10. /// // Call a RandomPath call like this, assumes that a Seeker is attached to the GameObject
  11. ///
  12. /// // The path will be returned when the path is over a specified length (or more accurately when the traversal cost is greater than a specified value).
  13. /// // A score of 1000 is approximately equal to the cost of moving one world unit.
  14. /// int theGScoreToStopAt = 50000;
  15. ///
  16. /// // Create a path object
  17. /// RandomPath path = RandomPath.Construct(transform.position, theGScoreToStopAt);
  18. /// // Determines the variation in path length that is allowed
  19. /// path.spread = 5000;
  20. ///
  21. /// // Get the Seeker component which must be attached to this GameObject
  22. /// Seeker seeker = GetComponent<Seeker>();
  23. ///
  24. /// // Start the path and return the result to MyCompleteFunction (which is a function you have to define, the name can of course be changed)
  25. /// seeker.StartPath(path, MyCompleteFunction);
  26. ///
  27. /// </code>
  28. /// </summary>
  29. public class RandomPath : ABPath {
  30. /// <summary>
  31. /// G score to stop searching at.
  32. /// The G score is rougly the distance to get from the start node to a node multiplied by 1000 (per default, see Pathfinding.Int3.Precision), plus any penalties
  33. /// </summary>
  34. public int searchLength;
  35. /// <summary>
  36. /// All G scores between <see cref="searchLength"/> and <see cref="searchLength"/>+<see cref="spread"/> are valid end points, a random one of them is chosen as the final point.
  37. /// On grid graphs a low spread usually works (but keep it higher than nodeSize*1000 since that it the default cost of moving between two nodes), on NavMesh graphs
  38. /// I would recommend a higher spread so it can evaluate more nodes
  39. /// </summary>
  40. public int spread = 5000;
  41. /// <summary>If an <see cref="aim"/> is set, the higher this value is, the more it will try to reach <see cref="aim"/></summary>
  42. public float aimStrength;
  43. /// <summary>Currently chosen end node</summary>
  44. PathNode chosenNodeR;
  45. /// <summary>
  46. /// The node with the highest G score which is still lower than <see cref="searchLength"/>.
  47. /// Used as a backup if a node with a G score higher than <see cref="searchLength"/> could not be found
  48. /// </summary>
  49. PathNode maxGScoreNodeR;
  50. /// <summary>The G score of <see cref="maxGScoreNodeR"/></summary>
  51. int maxGScore;
  52. /// <summary>
  53. /// An aim can be used to guide the pathfinder to not take totally random paths.
  54. /// For example you might want your AI to continue in generally the same direction as before, then you can specify
  55. /// aim to be transform.postion + transform.forward*10 which will make it more often take paths nearer that point
  56. /// See: <see cref="aimStrength"/>
  57. /// </summary>
  58. public Vector3 aim;
  59. int nodesEvaluatedRep;
  60. /// <summary>Random number generator</summary>
  61. readonly System.Random rnd = new System.Random();
  62. public override bool FloodingPath {
  63. get {
  64. return true;
  65. }
  66. }
  67. protected override bool hasEndPoint {
  68. get {
  69. return false;
  70. }
  71. }
  72. protected override void Reset () {
  73. base.Reset();
  74. searchLength = 5000;
  75. spread = 5000;
  76. aimStrength = 0.0f;
  77. chosenNodeR = null;
  78. maxGScoreNodeR = null;
  79. maxGScore = 0;
  80. aim = Vector3.zero;
  81. nodesEvaluatedRep = 0;
  82. }
  83. public RandomPath () {}
  84. public static RandomPath Construct (Vector3 start, int length, OnPathDelegate callback = null) {
  85. var p = PathPool.GetPath<RandomPath>();
  86. p.Setup(start, length, callback);
  87. return p;
  88. }
  89. protected RandomPath Setup (Vector3 start, int length, OnPathDelegate callback) {
  90. this.callback = callback;
  91. searchLength = length;
  92. originalStartPoint = start;
  93. originalEndPoint = Vector3.zero;
  94. startPoint = start;
  95. endPoint = Vector3.zero;
  96. startIntPoint = (Int3)start;
  97. return this;
  98. }
  99. /// <summary>
  100. /// Calls callback to return the calculated path.
  101. /// See: <see cref="callback"/>
  102. /// </summary>
  103. protected override void ReturnPath () {
  104. if (path != null && path.Count > 0) {
  105. endNode = path[path.Count-1];
  106. endPoint = (Vector3)endNode.position;
  107. originalEndPoint = endPoint;
  108. hTarget = endNode.position;
  109. }
  110. if (callback != null) {
  111. callback(this);
  112. }
  113. }
  114. protected override void Prepare () {
  115. nnConstraint.tags = enabledTags;
  116. var startNNInfo = AstarPath.active.GetNearest(startPoint, nnConstraint);
  117. startPoint = startNNInfo.position;
  118. endPoint = startPoint;
  119. startIntPoint = (Int3)startPoint;
  120. hTarget = (Int3)aim;//startIntPoint;
  121. startNode = startNNInfo.node;
  122. endNode = startNode;
  123. #if ASTARDEBUG
  124. Debug.DrawLine((Vector3)startNode.position, startPoint, Color.blue);
  125. Debug.DrawLine((Vector3)endNode.position, endPoint, Color.blue);
  126. #endif
  127. if (startNode == null || endNode == null) {
  128. FailWithError("Couldn't find close nodes to the start point");
  129. return;
  130. }
  131. if (!CanTraverse(startNode)) {
  132. FailWithError("The node closest to the start point could not be traversed");
  133. return;
  134. }
  135. heuristicScale = aimStrength;
  136. }
  137. protected override void Initialize () {
  138. //Adjust the costs for the end node
  139. /*if (hasEndPoint && recalcStartEndCosts) {
  140. * endNodeCosts = endNode.InitialOpen (open,hTarget,(Int3)endPoint,this,false);
  141. * callback += ResetCosts; /* \todo Might interfere with other paths since other paths might be calculated before #callback is called *
  142. * }*/
  143. //Node.activePath = this;
  144. PathNode startRNode = pathHandler.GetPathNode(startNode);
  145. startRNode.node = startNode;
  146. if (searchLength+spread <= 0) {
  147. CompleteState = PathCompleteState.Complete;
  148. Trace(startRNode);
  149. return;
  150. }
  151. startRNode.pathID = pathID;
  152. startRNode.parent = null;
  153. startRNode.cost = 0;
  154. startRNode.G = GetTraversalCost(startNode);
  155. startRNode.H = CalculateHScore(startNode);
  156. /*if (recalcStartEndCosts) {
  157. * startNode.InitialOpen (open,hTarget,startIntPoint,this,true);
  158. * } else {*/
  159. startNode.Open(this, startRNode, pathHandler);
  160. //}
  161. searchedNodes++;
  162. //any nodes left to search?
  163. if (pathHandler.heap.isEmpty) {
  164. FailWithError("No open points, the start node didn't open any nodes");
  165. return;
  166. }
  167. currentR = pathHandler.heap.Remove();
  168. }
  169. protected override void CalculateStep (long targetTick) {
  170. int counter = 0;
  171. // Continue to search as long as we haven't encountered an error and we haven't found the target
  172. while (CompleteState == PathCompleteState.NotCalculated) {
  173. searchedNodes++;
  174. // Close the current node, if the current node is the target node then the path is finished
  175. if (currentR.G >= searchLength) {
  176. if (currentR.G <= searchLength+spread) {
  177. nodesEvaluatedRep++;
  178. if (rnd.NextDouble() <= 1.0f/nodesEvaluatedRep) {
  179. chosenNodeR = currentR;
  180. }
  181. } else {
  182. // If no node was in the valid range of G scores, then fall back to picking one right outside valid range
  183. if (chosenNodeR == null) chosenNodeR = currentR;
  184. CompleteState = PathCompleteState.Complete;
  185. break;
  186. }
  187. } else if (currentR.G > maxGScore) {
  188. maxGScore = (int)currentR.G;
  189. maxGScoreNodeR = currentR;
  190. }
  191. // Loop through all walkable neighbours of the node and add them to the open list.
  192. currentR.node.Open(this, currentR, pathHandler);
  193. // Any nodes left to search?
  194. if (pathHandler.heap.isEmpty) {
  195. if (chosenNodeR != null) {
  196. CompleteState = PathCompleteState.Complete;
  197. } else if (maxGScoreNodeR != null) {
  198. chosenNodeR = maxGScoreNodeR;
  199. CompleteState = PathCompleteState.Complete;
  200. } else {
  201. FailWithError("Not a single node found to search");
  202. }
  203. break;
  204. }
  205. // Select the node with the lowest F score and remove it from the open list
  206. currentR = pathHandler.heap.Remove();
  207. // Check for time every 500 nodes, roughly every 0.5 ms usually
  208. if (counter > 500) {
  209. // Have we exceded the maxFrameTime, if so we should wait one frame before continuing the search since we don't want the game to lag
  210. if (System.DateTime.UtcNow.Ticks >= targetTick) {
  211. // Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
  212. return;
  213. }
  214. counter = 0;
  215. if (searchedNodes > 1000000) {
  216. throw new System.Exception("Probable infinite loop. Over 1,000,000 nodes searched");
  217. }
  218. }
  219. counter++;
  220. }
  221. if (CompleteState == PathCompleteState.Complete) {
  222. Trace(chosenNodeR);
  223. }
  224. }
  225. }
  226. }