MultiTargetPath.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. namespace Pathfinding {
  4. /// <summary>
  5. /// A path which searches from one point to a number of different targets in one search or from a number of different start points to a single target.
  6. ///
  7. /// This is faster than searching with an ABPath for each target if pathsForAll is true.
  8. /// This path type can be used for example when you want an agent to find the closest target of a few different options.
  9. ///
  10. /// When pathsForAll is true, it will calculate a path to each target point, but it can share a lot of calculations for the different paths so
  11. /// it is faster than requesting them separately.
  12. ///
  13. /// When pathsForAll is false, it will perform a search using the heuristic set to None and stop as soon as it finds the first target.
  14. /// This may be faster or slower than requesting each path separately.
  15. /// It will run a Dijkstra search where it searches all nodes around the start point until the closest target is found.
  16. /// Note that this is usually faster if some target points are very close to the start point and some are very far away, but
  17. /// it can be slower if all target points are relatively far away because then it will have to search a much larger
  18. /// region since it will not use any heuristics.
  19. ///
  20. /// See: Seeker.StartMultiTargetPath
  21. /// See: MultiTargetPathExample.cs (view in online documentation for working links) "Example of how to use multi-target-paths"
  22. ///
  23. /// Version: Since 3.7.1 the vectorPath and path fields are always set to the shortest path even when pathsForAll is true.
  24. /// </summary>
  25. public class MultiTargetPath : ABPath {
  26. /// <summary>Callbacks to call for each individual path</summary>
  27. public OnPathDelegate[] callbacks;
  28. /// <summary>Nearest nodes to the <see cref="targetPoints"/></summary>
  29. public GraphNode[] targetNodes;
  30. /// <summary>Number of target nodes left to find</summary>
  31. protected int targetNodeCount;
  32. /// <summary>Indicates if the target has been found. Also true if the target cannot be reached (is in another area)</summary>
  33. public bool[] targetsFound;
  34. /// <summary>Target points specified when creating the path. These are snapped to the nearest nodes</summary>
  35. public Vector3[] targetPoints;
  36. /// <summary>Target points specified when creating the path. These are not snapped to the nearest nodes</summary>
  37. public Vector3[] originalTargetPoints;
  38. /// <summary>Stores all vector paths to the targets. Elements are null if no path was found</summary>
  39. public List<Vector3>[] vectorPaths;
  40. /// <summary>Stores all paths to the targets. Elements are null if no path was found</summary>
  41. public List<GraphNode>[] nodePaths;
  42. /// <summary>If true, a path to all targets will be returned, otherwise just the one to the closest one.</summary>
  43. public bool pathsForAll = true;
  44. /// <summary>The closest target index (if any target was found)</summary>
  45. public int chosenTarget = -1;
  46. /// <summary>
  47. /// Current target for Sequential <see cref="heuristicMode"/>.
  48. /// Refers to an item in the targetPoints array
  49. /// </summary>
  50. int sequentialTarget;
  51. /// <summary>
  52. /// How to calculate the heuristic.
  53. /// The <see cref="<see cref="hTarget"/> heuristic target"/> can be calculated in different ways,
  54. /// by taking the Average position of all targets, or taking the mid point of them (i.e center of the AABB encapsulating all targets).
  55. ///
  56. /// The one which works best seems to be Sequential, it sets <see cref="hTarget"/> to the target furthest away, and when that target is found, it moves on to the next one.
  57. /// Some modes have the option to be 'moving' (e.g 'MovingAverage'), that means that it is updated every time a target is found.
  58. /// The H score is calculated according to AstarPath.heuristic
  59. ///
  60. /// Note: If pathsForAll is false then this option is ignored and it is always treated as being set to None
  61. /// </summary>
  62. public HeuristicMode heuristicMode = HeuristicMode.Sequential;
  63. public enum HeuristicMode {
  64. None,
  65. Average,
  66. MovingAverage,
  67. Midpoint,
  68. MovingMidpoint,
  69. Sequential
  70. }
  71. /// <summary>False if the path goes from one point to multiple targets. True if it goes from multiple start points to one target point</summary>
  72. public bool inverted { get; protected set; }
  73. /// <summary>
  74. /// Default constructor.
  75. /// Do not use this. Instead use the static Construct method which can handle path pooling.
  76. /// </summary>
  77. public MultiTargetPath () {}
  78. public static MultiTargetPath Construct (Vector3[] startPoints, Vector3 target, OnPathDelegate[] callbackDelegates, OnPathDelegate callback = null) {
  79. MultiTargetPath p = Construct(target, startPoints, callbackDelegates, callback);
  80. p.inverted = true;
  81. return p;
  82. }
  83. public static MultiTargetPath Construct (Vector3 start, Vector3[] targets, OnPathDelegate[] callbackDelegates, OnPathDelegate callback = null) {
  84. var p = PathPool.GetPath<MultiTargetPath>();
  85. p.Setup(start, targets, callbackDelegates, callback);
  86. return p;
  87. }
  88. protected void Setup (Vector3 start, Vector3[] targets, OnPathDelegate[] callbackDelegates, OnPathDelegate callback) {
  89. inverted = false;
  90. this.callback = callback;
  91. callbacks = callbackDelegates;
  92. if (callbacks != null && callbacks.Length != targets.Length) throw new System.ArgumentException("The targets array must have the same length as the callbackDelegates array");
  93. targetPoints = targets;
  94. originalStartPoint = start;
  95. startPoint = start;
  96. startIntPoint = (Int3)start;
  97. if (targets.Length == 0) {
  98. FailWithError("No targets were assigned to the MultiTargetPath");
  99. return;
  100. }
  101. endPoint = targets[0];
  102. originalTargetPoints = new Vector3[targetPoints.Length];
  103. for (int i = 0; i < targetPoints.Length; i++) {
  104. originalTargetPoints[i] = targetPoints[i];
  105. }
  106. }
  107. protected override void Reset () {
  108. base.Reset();
  109. pathsForAll = true;
  110. chosenTarget = -1;
  111. sequentialTarget = 0;
  112. inverted = true;
  113. heuristicMode = HeuristicMode.Sequential;
  114. }
  115. protected override void OnEnterPool () {
  116. if (vectorPaths != null)
  117. for (int i = 0; i < vectorPaths.Length; i++)
  118. if (vectorPaths[i] != null) Util.ListPool<Vector3>.Release(vectorPaths[i]);
  119. vectorPaths = null;
  120. vectorPath = null;
  121. if (nodePaths != null)
  122. for (int i = 0; i < nodePaths.Length; i++)
  123. if (nodePaths[i] != null) Util.ListPool<GraphNode>.Release(nodePaths[i]);
  124. nodePaths = null;
  125. path = null;
  126. callbacks = null;
  127. targetNodes = null;
  128. targetsFound = null;
  129. targetPoints = null;
  130. originalTargetPoints = null;
  131. base.OnEnterPool();
  132. }
  133. /// <summary>Set chosenTarget to the index of the shortest path</summary>
  134. void ChooseShortestPath () {
  135. //
  136. // When pathsForAll is false there will only be one non-null path
  137. chosenTarget = -1;
  138. if (nodePaths != null) {
  139. uint bestG = int.MaxValue;
  140. for (int i = 0; i < nodePaths.Length; i++) {
  141. var currentPath = nodePaths[i];
  142. if (currentPath != null) {
  143. // Get the G score of the first or the last node in the path
  144. // depending on if the paths are reversed or not
  145. var g = pathHandler.GetPathNode(currentPath[inverted ? 0 : currentPath.Count-1]).G;
  146. if (chosenTarget == -1 || g < bestG) {
  147. chosenTarget = i;
  148. bestG = g;
  149. }
  150. }
  151. }
  152. }
  153. }
  154. void SetPathParametersForReturn (int target) {
  155. path = nodePaths[target];
  156. vectorPath = vectorPaths[target];
  157. if (inverted) {
  158. startNode = targetNodes[target];
  159. startPoint = targetPoints[target];
  160. originalStartPoint = originalTargetPoints[target];
  161. } else {
  162. endNode = targetNodes[target];
  163. endPoint = targetPoints[target];
  164. originalEndPoint = originalTargetPoints[target];
  165. }
  166. }
  167. protected override void ReturnPath () {
  168. if (error) {
  169. // Call all callbacks
  170. if (callbacks != null) {
  171. for (int i = 0; i < callbacks.Length; i++)
  172. if (callbacks[i] != null) callbacks[i] (this);
  173. }
  174. if (callback != null) callback(this);
  175. return;
  176. }
  177. bool anySucceded = false;
  178. // Set the end point to the start point
  179. // since the path is reversed
  180. // (the start point will be set individually for each path)
  181. if (inverted) {
  182. endPoint = startPoint;
  183. endNode = startNode;
  184. originalEndPoint = originalStartPoint;
  185. }
  186. for (int i = 0; i < nodePaths.Length; i++) {
  187. if (nodePaths[i] != null) {
  188. // Note that we use the lowercase 'completeState' here.
  189. // The property (CompleteState) will ensure that the complete state is never
  190. // changed away from the error state but in this case we don't want that behaviour.
  191. completeState = PathCompleteState.Complete;
  192. anySucceded = true;
  193. } else {
  194. completeState = PathCompleteState.Error;
  195. }
  196. if (callbacks != null && callbacks[i] != null) {
  197. SetPathParametersForReturn(i);
  198. callbacks[i] (this);
  199. // In case a modifier changed the vectorPath, update the array of all vectorPaths
  200. vectorPaths[i] = vectorPath;
  201. }
  202. }
  203. if (anySucceded) {
  204. completeState = PathCompleteState.Complete;
  205. SetPathParametersForReturn(chosenTarget);
  206. } else {
  207. completeState = PathCompleteState.Error;
  208. }
  209. if (callback != null) {
  210. callback(this);
  211. }
  212. }
  213. protected void FoundTarget (PathNode nodeR, int i) {
  214. nodeR.flag1 = false; // Reset bit 8
  215. Trace(nodeR);
  216. vectorPaths[i] = vectorPath;
  217. nodePaths[i] = path;
  218. vectorPath = Util.ListPool<Vector3>.Claim();
  219. path = Util.ListPool<GraphNode>.Claim();
  220. targetsFound[i] = true;
  221. targetNodeCount--;
  222. // Since we have found one target
  223. // and the heuristic is always set to None when
  224. // pathsForAll is false, we will have found the shortest path
  225. if (!pathsForAll) {
  226. CompleteState = PathCompleteState.Complete;
  227. targetNodeCount = 0;
  228. return;
  229. }
  230. // If there are no more targets to find, return here and avoid calculating a new hTarget
  231. if (targetNodeCount <= 0) {
  232. CompleteState = PathCompleteState.Complete;
  233. return;
  234. }
  235. RecalculateHTarget(false);
  236. }
  237. protected void RebuildOpenList () {
  238. BinaryHeap heap = pathHandler.heap;
  239. for (int j = 0; j < heap.numberOfItems; j++) {
  240. PathNode nodeR = heap.GetNode(j);
  241. nodeR.H = CalculateHScore(nodeR.node);
  242. heap.SetF(j, nodeR.F);
  243. }
  244. pathHandler.heap.Rebuild();
  245. }
  246. protected override void Prepare () {
  247. nnConstraint.tags = enabledTags;
  248. var startNNInfo = AstarPath.active.GetNearest(startPoint, nnConstraint);
  249. startNode = startNNInfo.node;
  250. if (startNode == null) {
  251. FailWithError("Could not find start node for multi target path");
  252. return;
  253. }
  254. if (!CanTraverse(startNode)) {
  255. FailWithError("The node closest to the start point could not be traversed");
  256. return;
  257. }
  258. // Tell the NNConstraint which node was found as the start node if it is a PathNNConstraint and not a normal NNConstraint
  259. var pathNNConstraint = nnConstraint as PathNNConstraint;
  260. if (pathNNConstraint != null) {
  261. pathNNConstraint.SetStart(startNNInfo.node);
  262. }
  263. vectorPaths = new List<Vector3>[targetPoints.Length];
  264. nodePaths = new List<GraphNode>[targetPoints.Length];
  265. targetNodes = new GraphNode[targetPoints.Length];
  266. targetsFound = new bool[targetPoints.Length];
  267. targetNodeCount = targetPoints.Length;
  268. bool anyWalkable = false;
  269. bool anySameArea = false;
  270. bool anyNotNull = false;
  271. for (int i = 0; i < targetPoints.Length; i++) {
  272. var endNNInfo = AstarPath.active.GetNearest(targetPoints[i], nnConstraint);
  273. targetNodes[i] = endNNInfo.node;
  274. targetPoints[i] = endNNInfo.position;
  275. if (targetNodes[i] != null) {
  276. anyNotNull = true;
  277. endNode = targetNodes[i];
  278. }
  279. bool notReachable = false;
  280. if (endNNInfo.node != null && CanTraverse(endNNInfo.node)) {
  281. anyWalkable = true;
  282. } else {
  283. notReachable = true;
  284. }
  285. if (endNNInfo.node != null && endNNInfo.node.Area == startNode.Area) {
  286. anySameArea = true;
  287. } else {
  288. notReachable = true;
  289. }
  290. if (notReachable) {
  291. // Signal that the pathfinder should not look for this node because we have already found it
  292. targetsFound[i] = true;
  293. targetNodeCount--;
  294. }
  295. }
  296. startPoint = startNNInfo.position;
  297. startIntPoint = (Int3)startPoint;
  298. if (!anyNotNull) {
  299. FailWithError("Couldn't find a valid node close to the any of the end points");
  300. return;
  301. }
  302. if (!anyWalkable) {
  303. FailWithError("No target nodes could be traversed");
  304. return;
  305. }
  306. if (!anySameArea) {
  307. FailWithError("There are no valid paths to the targets");
  308. return;
  309. }
  310. RecalculateHTarget(true);
  311. }
  312. void RecalculateHTarget (bool firstTime) {
  313. // When pathsForAll is false
  314. // then no heuristic should be used
  315. if (!pathsForAll) {
  316. heuristic = Heuristic.None;
  317. heuristicScale = 0.0F;
  318. return;
  319. }
  320. // Calculate a new hTarget and rebuild the open list if necessary
  321. // Rebuilding the open list is necessary when the H score for nodes changes
  322. switch (heuristicMode) {
  323. case HeuristicMode.None:
  324. heuristic = Heuristic.None;
  325. heuristicScale = 0F;
  326. break;
  327. case HeuristicMode.Average:
  328. if (!firstTime) return;
  329. // No break
  330. // The first time the implementation
  331. // for Average and MovingAverage is identical
  332. // so we just use fallthrough
  333. goto case HeuristicMode.MovingAverage;
  334. case HeuristicMode.MovingAverage:
  335. // Pick the average position of all nodes that have not been found yet
  336. var avg = Vector3.zero;
  337. int count = 0;
  338. for (int j = 0; j < targetPoints.Length; j++) {
  339. if (!targetsFound[j]) {
  340. avg += (Vector3)targetNodes[j].position;
  341. count++;
  342. }
  343. }
  344. // Should use asserts, but they were first added in Unity 5.1
  345. // so I cannot use them because I want to keep compatibility with 4.6
  346. // (as of 2015)
  347. if (count == 0) throw new System.Exception("Should not happen");
  348. avg /= count;
  349. hTarget = (Int3)avg;
  350. break;
  351. case HeuristicMode.Midpoint:
  352. if (!firstTime) return;
  353. // No break
  354. // The first time the implementation
  355. // for Midpoint and MovingMidpoint is identical
  356. // so we just use fallthrough
  357. goto case HeuristicMode.MovingMidpoint;
  358. case HeuristicMode.MovingMidpoint:
  359. Vector3 min = Vector3.zero;
  360. Vector3 max = Vector3.zero;
  361. bool set = false;
  362. // Pick the median of all points that have
  363. // not been found yet
  364. for (int j = 0; j < targetPoints.Length; j++) {
  365. if (!targetsFound[j]) {
  366. if (!set) {
  367. min = (Vector3)targetNodes[j].position;
  368. max = (Vector3)targetNodes[j].position;
  369. set = true;
  370. } else {
  371. min = Vector3.Min((Vector3)targetNodes[j].position, min);
  372. max = Vector3.Max((Vector3)targetNodes[j].position, max);
  373. }
  374. }
  375. }
  376. var midpoint = (Int3)((min+max)*0.5F);
  377. hTarget = midpoint;
  378. break;
  379. case HeuristicMode.Sequential:
  380. // The first time the hTarget should always be recalculated
  381. // But other times we can skip it if we have not yet found the current target
  382. // since then the hTarget would just be set to the same value again
  383. if (!firstTime && !targetsFound[sequentialTarget]) {
  384. return;
  385. }
  386. float dist = 0;
  387. // Pick the target which is furthest away and has not been found yet
  388. for (int j = 0; j < targetPoints.Length; j++) {
  389. if (!targetsFound[j]) {
  390. float d = (targetNodes[j].position-startNode.position).sqrMagnitude;
  391. if (d > dist) {
  392. dist = d;
  393. hTarget = (Int3)targetPoints[j];
  394. sequentialTarget = j;
  395. }
  396. }
  397. }
  398. break;
  399. }
  400. // Rebuild the open list since all the H scores have changed
  401. // However the first time we can skip this since
  402. // no nodes are added to the heap yet
  403. if (!firstTime) {
  404. RebuildOpenList();
  405. }
  406. }
  407. protected override void Initialize () {
  408. // Reset the start node to prevent
  409. // old info from previous paths to be used
  410. PathNode startRNode = pathHandler.GetPathNode(startNode);
  411. startRNode.node = startNode;
  412. startRNode.pathID = pathID;
  413. startRNode.parent = null;
  414. startRNode.cost = 0;
  415. startRNode.G = GetTraversalCost(startNode);
  416. startRNode.H = CalculateHScore(startNode);
  417. for (int j = 0; j < targetNodes.Length; j++) {
  418. if (startNode == targetNodes[j]) {
  419. // The start node is equal to the target node
  420. // so we can immediately mark the path as calculated
  421. FoundTarget(startRNode, j);
  422. } else if (targetNodes[j] != null) {
  423. // Mark the node with a flag so that we can quickly check if we have found a target node
  424. pathHandler.GetPathNode(targetNodes[j]).flag1 = true;
  425. }
  426. }
  427. // If all paths have either been invalidated or found already because they were at the same node as the start node
  428. if (targetNodeCount <= 0) {
  429. CompleteState = PathCompleteState.Complete;
  430. return;
  431. }
  432. //if (recalcStartEndCosts) {
  433. // startNode.InitialOpen (open,hTarget,startIntPoint,this,true);
  434. //} else {
  435. startNode.Open(this, startRNode, pathHandler);
  436. //}
  437. searchedNodes++;
  438. //any nodes left to search?
  439. if (pathHandler.heap.isEmpty) {
  440. FailWithError("No open points, the start node didn't open any nodes");
  441. return;
  442. }
  443. // Take the first node off the heap
  444. currentR = pathHandler.heap.Remove();
  445. }
  446. protected override void Cleanup () {
  447. // Make sure that the shortest path is set
  448. // after the path has been calculated
  449. ChooseShortestPath();
  450. ResetFlags();
  451. }
  452. /// <summary>Reset flag1 on all nodes after the pathfinding has completed (no matter if an error occurs or if the path is canceled)</summary>
  453. void ResetFlags () {
  454. // Reset all flags
  455. if (targetNodes != null) {
  456. for (int i = 0; i < targetNodes.Length; i++) {
  457. if (targetNodes[i] != null) pathHandler.GetPathNode(targetNodes[i]).flag1 = false;
  458. }
  459. }
  460. }
  461. protected override void CalculateStep (long targetTick) {
  462. int counter = 0;
  463. // Continue to search as long as we haven't encountered an error and we haven't found the target
  464. while (CompleteState == PathCompleteState.NotCalculated) {
  465. // @Performance Just for debug info
  466. searchedNodes++;
  467. // The node might be the target node for one of the paths
  468. if (currentR.flag1) {
  469. // Close the current node, if the current node is the target node then the path is finnished
  470. for (int i = 0; i < targetNodes.Length; i++) {
  471. if (!targetsFound[i] && currentR.node == targetNodes[i]) {
  472. FoundTarget(currentR, i);
  473. if (CompleteState != PathCompleteState.NotCalculated) {
  474. break;
  475. }
  476. }
  477. }
  478. if (targetNodeCount <= 0) {
  479. CompleteState = PathCompleteState.Complete;
  480. break;
  481. }
  482. }
  483. // Loop through all walkable neighbours of the node and add them to the open list.
  484. currentR.node.Open(this, currentR, pathHandler);
  485. // Any nodes left to search?
  486. if (pathHandler.heap.isEmpty) {
  487. CompleteState = PathCompleteState.Complete;
  488. break;
  489. }
  490. // Select the node with the lowest F score and remove it from the open list
  491. AstarProfiler.StartFastProfile(7);
  492. currentR = pathHandler.heap.Remove();
  493. AstarProfiler.EndFastProfile(7);
  494. // Check for time every 500 nodes, roughly every 0.5 ms usually
  495. if (counter > 500) {
  496. // 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
  497. if (System.DateTime.UtcNow.Ticks >= targetTick) {
  498. // Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
  499. return;
  500. }
  501. counter = 0;
  502. }
  503. counter++;
  504. }
  505. }
  506. protected override void Trace (PathNode node) {
  507. base.Trace(node);
  508. if (inverted) {
  509. // Reverse the paths
  510. int half = path.Count/2;
  511. for (int i = 0; i < half; i++) {
  512. GraphNode tmp = path[i];
  513. path[i] = path[path.Count-i-1];
  514. path[path.Count-i-1] = tmp;
  515. }
  516. for (int i = 0; i < half; i++) {
  517. Vector3 tmp = vectorPath[i];
  518. vectorPath[i] = vectorPath[vectorPath.Count-i-1];
  519. vectorPath[vectorPath.Count-i-1] = tmp;
  520. }
  521. }
  522. }
  523. protected override string DebugString (PathLog logMode) {
  524. if (logMode == PathLog.None || (!error && logMode == PathLog.OnlyErrors)) {
  525. return "";
  526. }
  527. System.Text.StringBuilder text = pathHandler.DebugStringBuilder;
  528. text.Length = 0;
  529. DebugStringPrefix(logMode, text);
  530. if (!error) {
  531. text.Append("\nShortest path was ");
  532. text.Append(chosenTarget == -1 ? "undefined" : nodePaths[chosenTarget].Count.ToString());
  533. text.Append(" nodes long");
  534. if (logMode == PathLog.Heavy) {
  535. text.Append("\nPaths (").Append(targetsFound.Length).Append("):");
  536. for (int i = 0; i < targetsFound.Length; i++) {
  537. text.Append("\n\n Path ").Append(i).Append(" Found: ").Append(targetsFound[i]);
  538. if (nodePaths[i] != null) {
  539. text.Append("\n Length: ");
  540. text.Append(nodePaths[i].Count);
  541. GraphNode node = nodePaths[i][nodePaths[i].Count-1];
  542. if (node != null) {
  543. PathNode nodeR = pathHandler.GetPathNode(endNode);
  544. if (nodeR != null) {
  545. text.Append("\n End Node");
  546. text.Append("\n G: ");
  547. text.Append(nodeR.G);
  548. text.Append("\n H: ");
  549. text.Append(nodeR.H);
  550. text.Append("\n F: ");
  551. text.Append(nodeR.F);
  552. text.Append("\n Point: ");
  553. text.Append(((Vector3)endPoint).ToString());
  554. text.Append("\n Graph: ");
  555. text.Append(endNode.GraphIndex);
  556. } else {
  557. text.Append("\n End Node: Null");
  558. }
  559. }
  560. }
  561. }
  562. text.Append("\nStart Node");
  563. text.Append("\n Point: ");
  564. text.Append(((Vector3)endPoint).ToString());
  565. text.Append("\n Graph: ");
  566. text.Append(startNode.GraphIndex);
  567. text.Append("\nBinary Heap size at completion: ");
  568. text.AppendLine(pathHandler.heap == null ? "Null" : (pathHandler.heap.numberOfItems-2).ToString()); // -2 because numberOfItems includes the next item to be added and item zero is not used
  569. }
  570. }
  571. DebugStringSuffix(logMode, text);
  572. return text.ToString();
  573. }
  574. }
  575. }