XPath.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. using UnityEngine;
  2. namespace Pathfinding {
  3. /// <summary>
  4. /// Extended Path.
  5. ///
  6. /// This is the same as a standard path but it is possible to customize when the target should be considered reached.
  7. /// Can be used to for example signal a path as complete when it is within a specific distance from the target.
  8. ///
  9. /// Note: More customizations does make it slower to calculate than an ABPath but not by very much.
  10. ///
  11. /// See: Pathfinding.PathEndingCondition
  12. /// </summary>
  13. public class XPath : ABPath {
  14. /// <summary>
  15. /// Ending Condition for the path.
  16. /// The ending condition determines when the path has been completed.
  17. /// Can be used to for example signal a path as complete when it is within a specific distance from the target.
  18. ///
  19. /// If ending conditions are used that are not centered around the endpoint of the path
  20. /// you should also switch the <see cref="heuristic"/> to None to make sure that optimal paths are still found.
  21. /// This has quite a large performance impact so you might want to try to run it with the default
  22. /// heuristic and see if the path is optimal in enough cases.
  23. /// </summary>
  24. public PathEndingCondition endingCondition;
  25. public XPath () {}
  26. public new static XPath Construct (Vector3 start, Vector3 end, OnPathDelegate callback = null) {
  27. var p = PathPool.GetPath<XPath>();
  28. p.Setup(start, end, callback);
  29. p.endingCondition = new ABPathEndingCondition(p);
  30. return p;
  31. }
  32. protected override void Reset () {
  33. base.Reset();
  34. endingCondition = null;
  35. }
  36. #if !ASTAR_NO_GRID_GRAPH
  37. protected override bool EndPointGridGraphSpecialCase (GraphNode endNode) {
  38. // Don't use the grid graph special case for this path type
  39. return false;
  40. }
  41. #endif
  42. /// <summary>The start node need to be special cased and checked here if it is a valid target</summary>
  43. protected override void CompletePathIfStartIsValidTarget () {
  44. var pNode = pathHandler.GetPathNode(startNode);
  45. if (endingCondition.TargetFound(pNode)) {
  46. ChangeEndNode(startNode);
  47. Trace(pNode);
  48. CompleteState = PathCompleteState.Complete;
  49. }
  50. }
  51. /// <summary>
  52. /// Changes the <see cref="endNode"/> to target and resets some temporary flags on the previous node.
  53. /// Also sets <see cref="endPoint"/> to the position of target.
  54. /// </summary>
  55. void ChangeEndNode (GraphNode target) {
  56. // Reset temporary flags on the previous end node, otherwise they might be
  57. // left in the graph and cause other paths to calculate paths incorrectly
  58. if (endNode != null && endNode != startNode) {
  59. var pathNode = pathHandler.GetPathNode(endNode);
  60. pathNode.flag1 = pathNode.flag2 = false;
  61. }
  62. endNode = target;
  63. endPoint = (Vector3)target.position;
  64. }
  65. protected override void CalculateStep (long targetTick) {
  66. int counter = 0;
  67. // Continue to search as long as we haven't encountered an error and we haven't found the target
  68. while (CompleteState == PathCompleteState.NotCalculated) {
  69. searchedNodes++;
  70. // Close the current node, if the current node is the target node then the path is finished
  71. if (endingCondition.TargetFound(currentR)) {
  72. CompleteState = PathCompleteState.Complete;
  73. break;
  74. }
  75. // Loop through all walkable neighbours of the node and add them to the open list.
  76. currentR.node.Open(this, currentR, pathHandler);
  77. // Any nodes left to search?
  78. if (pathHandler.heap.isEmpty) {
  79. FailWithError("Searched whole area but could not find target");
  80. return;
  81. }
  82. // Select the node with the lowest F score and remove it from the open list
  83. currentR = pathHandler.heap.Remove();
  84. // Check for time every 500 nodes, roughly every 0.5 ms usually
  85. if (counter > 500) {
  86. // 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
  87. if (System.DateTime.UtcNow.Ticks >= targetTick) {
  88. //Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
  89. return;
  90. }
  91. counter = 0;
  92. if (searchedNodes > 1000000) {
  93. throw new System.Exception("Probable infinite loop. Over 1,000,000 nodes searched");
  94. }
  95. }
  96. counter++;
  97. }
  98. if (CompleteState == PathCompleteState.Complete) {
  99. ChangeEndNode(currentR.node);
  100. Trace(currentR);
  101. }
  102. }
  103. }
  104. /// <summary>
  105. /// Customized ending condition for a path.
  106. /// This class can be used to implement a custom ending condition for e.g an Pathfinding.XPath.
  107. /// Inherit from this class and override the <see cref="TargetFound"/> function to implement you own ending condition logic.
  108. ///
  109. /// For example, you might want to create an Ending Condition which stops when a node is close enough to a given point.
  110. /// Then what you do is that you create your own class, let's call it MyEndingCondition and override the function TargetFound to specify our own logic.
  111. /// We want to inherit from ABPathEndingCondition because only ABPaths have end points defined.
  112. ///
  113. /// <code>
  114. /// public class MyEndingCondition : ABPathEndingCondition {
  115. ///
  116. /// // Maximum world distance to the target node before terminating the path
  117. /// public float maxDistance = 10;
  118. ///
  119. /// // Reuse the constructor in the superclass
  120. /// public MyEndingCondition (ABPath p) : base (p) {}
  121. ///
  122. /// public override bool TargetFound (PathNode node) {
  123. /// return ((Vector3)node.node.position - abPath.originalEndPoint).sqrMagnitude <= maxDistance*maxDistance;
  124. /// }
  125. /// }
  126. /// </code>
  127. ///
  128. /// One part at a time. We need to cast the node's position to a Vector3 since internally, it is stored as an integer coordinate (Int3).
  129. /// Then we subtract the Pathfinding.Path.originalEndPoint from it to get their difference.
  130. /// The original end point is always the exact point specified when calling the path.
  131. /// As a last step we check the squared magnitude (squared distance, it is much faster than the non-squared distance) and check if it is lower or equal to our maxDistance squared.
  132. /// There you have it, it is as simple as that.
  133. /// Then you simply assign it to the endingCondition variable on, for example an XPath which uses the EndingCondition.
  134. ///
  135. /// <code>
  136. /// XPath myXPath = XPath.Construct(startPoint, endPoint);
  137. /// MyEndingCondition ec = new MyEndingCondition();
  138. /// ec.maxDistance = 100; // Or some other value
  139. /// myXPath.endingCondition = ec;
  140. ///
  141. /// // Calculate the path!
  142. /// seeker.StartPath (ec);
  143. /// </code>
  144. ///
  145. /// Where seeker is a <see cref="Seeker"/> component, and myXPath is an Pathfinding.XPath.
  146. ///
  147. /// Note: The above was written without testing. I hope I haven't made any mistakes, if you try it out, and it doesn't seem to work. Please post a comment in the forums.
  148. ///
  149. /// Version: Method structure changed in 3.2
  150. /// Version: Updated in version 3.6.8
  151. ///
  152. /// See: Pathfinding.XPath
  153. /// See: Pathfinding.ConstantPath
  154. /// </summary>
  155. public abstract class PathEndingCondition {
  156. /// <summary>Path which this ending condition is used on</summary>
  157. protected Path path;
  158. protected PathEndingCondition () {}
  159. public PathEndingCondition (Path p) {
  160. if (p == null) throw new System.ArgumentNullException("p");
  161. this.path = p;
  162. }
  163. /// <summary>Has the ending condition been fulfilled.</summary>
  164. /// <param name="node">The current node.</param>
  165. public abstract bool TargetFound(PathNode node);
  166. }
  167. /// <summary>Ending condition which emulates the default one for the ABPath</summary>
  168. public class ABPathEndingCondition : PathEndingCondition {
  169. /// <summary>
  170. /// Path which this ending condition is used on.
  171. /// Same as <see cref="path"/> but downcasted to ABPath
  172. /// </summary>
  173. protected ABPath abPath;
  174. public ABPathEndingCondition (ABPath p) {
  175. if (p == null) throw new System.ArgumentNullException("p");
  176. abPath = p;
  177. path = p;
  178. }
  179. /// <summary>Has the ending condition been fulfilled.</summary>
  180. /// <param name="node">The current node.
  181. /// This is per default the same as asking if node == p.endNode</param>
  182. public override bool TargetFound (PathNode node) {
  183. return node.node == abPath.endNode;
  184. }
  185. }
  186. /// <summary>Ending condition which stops a fixed distance from the target point</summary>
  187. public class EndingConditionProximity : ABPathEndingCondition {
  188. /// <summary>Maximum world distance to the target node before terminating the path</summary>
  189. public float maxDistance = 10;
  190. public EndingConditionProximity (ABPath p, float maxDistance) : base(p) {
  191. this.maxDistance = maxDistance;
  192. }
  193. public override bool TargetFound (PathNode node) {
  194. return ((Vector3)node.node.position - abPath.originalEndPoint).sqrMagnitude <= maxDistance*maxDistance;
  195. }
  196. }
  197. }