Seeker.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. #if UNITY_5_5_OR_NEWER
  4. using UnityEngine.Profiling;
  5. #endif
  6. namespace Pathfinding {
  7. /// <summary>
  8. /// Handles path calls for a single unit.
  9. /// \ingroup relevant
  10. /// This is a component which is meant to be attached to a single unit (AI, Robot, Player, whatever) to handle its pathfinding calls.
  11. /// It also handles post-processing of paths using modifiers.
  12. ///
  13. /// [Open online documentation to see images]
  14. ///
  15. /// See: calling-pathfinding (view in online documentation for working links)
  16. /// See: modifiers (view in online documentation for working links)
  17. /// </summary>
  18. [AddComponentMenu("Pathfinding/Seeker")]
  19. [HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_seeker.php")]
  20. public class Seeker : VersionedMonoBehaviour {
  21. /// <summary>
  22. /// Enables drawing of the last calculated path using Gizmos.
  23. /// The path will show up in green.
  24. ///
  25. /// See: OnDrawGizmos
  26. /// </summary>
  27. public bool drawGizmos = true;
  28. /// <summary>
  29. /// Enables drawing of the non-postprocessed path using Gizmos.
  30. /// The path will show up in orange.
  31. ///
  32. /// Requires that <see cref="drawGizmos"/> is true.
  33. ///
  34. /// This will show the path before any post processing such as smoothing is applied.
  35. ///
  36. /// See: drawGizmos
  37. /// See: OnDrawGizmos
  38. /// </summary>
  39. public bool detailedGizmos;
  40. /// <summary>Path modifier which tweaks the start and end points of a path</summary>
  41. [HideInInspector]
  42. public StartEndModifier startEndModifier = new StartEndModifier();
  43. /// <summary>
  44. /// The tags which the Seeker can traverse.
  45. ///
  46. /// Note: This field is a bitmask.
  47. /// See: bitmasks (view in online documentation for working links)
  48. /// </summary>
  49. [HideInInspector]
  50. public int traversableTags = -1;
  51. /// <summary>
  52. /// Penalties for each tag.
  53. /// Tag 0 which is the default tag, will have added a penalty of tagPenalties[0].
  54. /// These should only be positive values since the A* algorithm cannot handle negative penalties.
  55. ///
  56. /// Note: This array should always have a length of 32 otherwise the system will ignore it.
  57. ///
  58. /// See: Pathfinding.Path.tagPenalties
  59. /// </summary>
  60. [HideInInspector]
  61. public int[] tagPenalties = new int[32];
  62. /// <summary>
  63. /// Graphs that this Seeker can use.
  64. /// This field determines which graphs will be considered when searching for the start and end nodes of a path.
  65. /// It is useful in numerous situations, for example if you want to make one graph for small units and one graph for large units.
  66. ///
  67. /// This is a bitmask so if you for example want to make the agent only use graph index 3 then you can set this to:
  68. /// <code> seeker.graphMask = 1 << 3; </code>
  69. ///
  70. /// See: bitmasks (view in online documentation for working links)
  71. ///
  72. /// Note that this field only stores which graph indices that are allowed. This means that if the graphs change their ordering
  73. /// then this mask may no longer be correct.
  74. ///
  75. /// If you know the name of the graph you can use the <see cref="Pathfinding.GraphMask.FromGraphName"/> method:
  76. /// <code>
  77. /// GraphMask mask1 = GraphMask.FromGraphName("My Grid Graph");
  78. /// GraphMask mask2 = GraphMask.FromGraphName("My Other Grid Graph");
  79. ///
  80. /// NNConstraint nn = NNConstraint.Default;
  81. ///
  82. /// nn.graphMask = mask1 | mask2;
  83. ///
  84. /// // Find the node closest to somePoint which is either in 'My Grid Graph' OR in 'My Other Grid Graph'
  85. /// var info = AstarPath.active.GetNearest(somePoint, nn);
  86. /// </code>
  87. ///
  88. /// Some overloads of the <see cref="StartPath"/> methods take a graphMask parameter. If those overloads are used then they
  89. /// will override the graph mask for that path request.
  90. ///
  91. /// [Open online documentation to see images]
  92. ///
  93. /// See: multiple-agent-types (view in online documentation for working links)
  94. /// </summary>
  95. [HideInInspector]
  96. public GraphMask graphMask = GraphMask.everything;
  97. /// <summary>Used for serialization backwards compatibility</summary>
  98. [UnityEngine.Serialization.FormerlySerializedAs("graphMask")]
  99. int graphMaskCompatibility = -1;
  100. /// <summary>
  101. /// Callback for when a path is completed.
  102. /// Movement scripts should register to this delegate.\n
  103. /// A temporary callback can also be set when calling StartPath, but that delegate will only be called for that path
  104. /// </summary>
  105. public OnPathDelegate pathCallback;
  106. /// <summary>Called before pathfinding is started</summary>
  107. public OnPathDelegate preProcessPath;
  108. /// <summary>Called after a path has been calculated, right before modifiers are executed.</summary>
  109. public OnPathDelegate postProcessPath;
  110. /// <summary>Used for drawing gizmos</summary>
  111. [System.NonSerialized]
  112. List<Vector3> lastCompletedVectorPath;
  113. /// <summary>Used for drawing gizmos</summary>
  114. [System.NonSerialized]
  115. List<GraphNode> lastCompletedNodePath;
  116. /// <summary>The current path</summary>
  117. [System.NonSerialized]
  118. protected Path path;
  119. /// <summary>Previous path. Used to draw gizmos</summary>
  120. [System.NonSerialized]
  121. private Path prevPath;
  122. /// <summary>Cached delegate to avoid allocating one every time a path is started</summary>
  123. private readonly OnPathDelegate onPathDelegate;
  124. /// <summary>Temporary callback only called for the current path. This value is set by the StartPath functions</summary>
  125. private OnPathDelegate tmpPathCallback;
  126. /// <summary>The path ID of the last path queried</summary>
  127. protected uint lastPathID;
  128. /// <summary>Internal list of all modifiers</summary>
  129. readonly List<IPathModifier> modifiers = new List<IPathModifier>();
  130. public enum ModifierPass {
  131. PreProcess,
  132. // An obsolete item occupied index 1 previously
  133. PostProcess = 2,
  134. }
  135. public Seeker () {
  136. onPathDelegate = OnPathComplete;
  137. }
  138. /// <summary>Initializes a few variables</summary>
  139. protected override void Awake () {
  140. base.Awake();
  141. startEndModifier.Awake(this);
  142. }
  143. /// <summary>
  144. /// Path that is currently being calculated or was last calculated.
  145. /// You should rarely have to use this. Instead get the path when the path callback is called.
  146. ///
  147. /// See: pathCallback
  148. /// </summary>
  149. public Path GetCurrentPath () {
  150. return path;
  151. }
  152. /// <summary>
  153. /// Stop calculating the current path request.
  154. /// If this Seeker is currently calculating a path it will be canceled.
  155. /// The callback (usually to a method named OnPathComplete) will soon be called
  156. /// with a path that has the 'error' field set to true.
  157. ///
  158. /// This does not stop the character from moving, it just aborts
  159. /// the path calculation.
  160. /// </summary>
  161. /// <param name="pool">If true then the path will be pooled when the pathfinding system is done with it.</param>
  162. public void CancelCurrentPathRequest (bool pool = true) {
  163. if (!IsDone()) {
  164. path.FailWithError("Canceled by script (Seeker.CancelCurrentPathRequest)");
  165. if (pool) {
  166. // Make sure the path has had its reference count incremented and decremented once.
  167. // If this is not done the system will think no pooling is used at all and will not pool the path.
  168. // The particular object that is used as the parameter (in this case 'path') doesn't matter at all
  169. // it just has to be *some* object.
  170. path.Claim(path);
  171. path.Release(path);
  172. }
  173. }
  174. }
  175. /// <summary>
  176. /// Cleans up some variables.
  177. /// Releases any eventually claimed paths.
  178. /// Calls OnDestroy on the <see cref="startEndModifier"/>.
  179. ///
  180. /// See: ReleaseClaimedPath
  181. /// See: startEndModifier
  182. /// </summary>
  183. public void OnDestroy () {
  184. ReleaseClaimedPath();
  185. startEndModifier.OnDestroy(this);
  186. }
  187. /// <summary>
  188. /// Releases the path used for gizmos (if any).
  189. /// The seeker keeps the latest path claimed so it can draw gizmos.
  190. /// In some cases this might not be desireable and you want it released.
  191. /// In that case, you can call this method to release it (not that path gizmos will then not be drawn).
  192. ///
  193. /// If you didn't understand anything from the description above, you probably don't need to use this method.
  194. ///
  195. /// See: pooling (view in online documentation for working links)
  196. /// </summary>
  197. public void ReleaseClaimedPath () {
  198. if (prevPath != null) {
  199. prevPath.Release(this, true);
  200. prevPath = null;
  201. }
  202. }
  203. /// <summary>Called by modifiers to register themselves</summary>
  204. public void RegisterModifier (IPathModifier modifier) {
  205. modifiers.Add(modifier);
  206. // Sort the modifiers based on their specified order
  207. modifiers.Sort((a, b) => a.Order.CompareTo(b.Order));
  208. }
  209. /// <summary>Called by modifiers when they are disabled or destroyed</summary>
  210. public void DeregisterModifier (IPathModifier modifier) {
  211. modifiers.Remove(modifier);
  212. }
  213. /// <summary>
  214. /// Post Processes the path.
  215. /// This will run any modifiers attached to this GameObject on the path.
  216. /// This is identical to calling RunModifiers(ModifierPass.PostProcess, path)
  217. /// See: RunModifiers
  218. /// \since Added in 3.2
  219. /// </summary>
  220. public void PostProcess (Path path) {
  221. RunModifiers(ModifierPass.PostProcess, path);
  222. }
  223. /// <summary>Runs modifiers on a path</summary>
  224. public void RunModifiers (ModifierPass pass, Path path) {
  225. if (pass == ModifierPass.PreProcess) {
  226. if (preProcessPath != null) preProcessPath(path);
  227. for (int i = 0; i < modifiers.Count; i++) modifiers[i].PreProcess(path);
  228. } else if (pass == ModifierPass.PostProcess) {
  229. Profiler.BeginSample("Running Path Modifiers");
  230. // Call delegates if they exist
  231. if (postProcessPath != null) postProcessPath(path);
  232. // Loop through all modifiers and apply post processing
  233. for (int i = 0; i < modifiers.Count; i++) modifiers[i].Apply(path);
  234. Profiler.EndSample();
  235. }
  236. }
  237. /// <summary>
  238. /// Is the current path done calculating.
  239. /// Returns true if the current <see cref="path"/> has been returned or if the <see cref="path"/> is null.
  240. ///
  241. /// Note: Do not confuse this with Pathfinding.Path.IsDone. They usually return the same value, but not always
  242. /// since the path might be completely calculated, but it has not yet been processed by the Seeker.
  243. ///
  244. /// \since Added in 3.0.8
  245. /// Version: Behaviour changed in 3.2
  246. /// </summary>
  247. public bool IsDone () {
  248. return path == null || path.PipelineState >= PathState.Returned;
  249. }
  250. /// <summary>
  251. /// Called when a path has completed.
  252. /// This should have been implemented as optional parameter values, but that didn't seem to work very well with delegates (the values weren't the default ones)
  253. /// See: OnPathComplete(Path,bool,bool)
  254. /// </summary>
  255. void OnPathComplete (Path path) {
  256. OnPathComplete(path, true, true);
  257. }
  258. /// <summary>
  259. /// Called when a path has completed.
  260. /// Will post process it and return it by calling <see cref="tmpPathCallback"/> and <see cref="pathCallback"/>
  261. /// </summary>
  262. void OnPathComplete (Path p, bool runModifiers, bool sendCallbacks) {
  263. if (p != null && p != path && sendCallbacks) {
  264. return;
  265. }
  266. if (this == null || p == null || p != path)
  267. return;
  268. if (!path.error && runModifiers) {
  269. // This will send the path for post processing to modifiers attached to this Seeker
  270. RunModifiers(ModifierPass.PostProcess, path);
  271. }
  272. if (sendCallbacks) {
  273. p.Claim(this);
  274. lastCompletedNodePath = p.path;
  275. lastCompletedVectorPath = p.vectorPath;
  276. // This will send the path to the callback (if any) specified when calling StartPath
  277. if (tmpPathCallback != null) {
  278. tmpPathCallback(p);
  279. }
  280. // This will send the path to any script which has registered to the callback
  281. if (pathCallback != null) {
  282. pathCallback(p);
  283. }
  284. // Recycle the previous path to reduce the load on the GC
  285. if (prevPath != null) {
  286. prevPath.Release(this, true);
  287. }
  288. prevPath = p;
  289. // If not drawing gizmos, then storing prevPath is quite unecessary
  290. // So clear it and set prevPath to null
  291. if (!drawGizmos) ReleaseClaimedPath();
  292. }
  293. }
  294. /// <summary>
  295. /// Returns a new path instance.
  296. /// The path will be taken from the path pool if path recycling is turned on.\n
  297. /// This path can be sent to <see cref="StartPath(Path,OnPathDelegate,int)"/> with no change, but if no change is required <see cref="StartPath(Vector3,Vector3,OnPathDelegate)"/> does just that.
  298. /// <code>
  299. /// var seeker = GetComponent<Seeker>();
  300. /// Path p = seeker.GetNewPath (transform.position, transform.position+transform.forward*100);
  301. /// // Disable heuristics on just this path for example
  302. /// p.heuristic = Heuristic.None;
  303. /// seeker.StartPath (p, OnPathComplete);
  304. /// </code>
  305. /// Deprecated: Use ABPath.Construct(start, end, null) instead.
  306. /// </summary>
  307. [System.Obsolete("Use ABPath.Construct(start, end, null) instead")]
  308. public ABPath GetNewPath (Vector3 start, Vector3 end) {
  309. // Construct a path with start and end points
  310. return ABPath.Construct(start, end, null);
  311. }
  312. /// <summary>
  313. /// Call this function to start calculating a path.
  314. /// Since this method does not take a callback parameter, you should set the <see cref="pathCallback"/> field before calling this method.
  315. /// </summary>
  316. /// <param name="start">The start point of the path</param>
  317. /// <param name="end">The end point of the path</param>
  318. public Path StartPath (Vector3 start, Vector3 end) {
  319. return StartPath(start, end, null);
  320. }
  321. /// <summary>
  322. /// Call this function to start calculating a path.
  323. ///
  324. /// callback will be called when the path has completed.
  325. /// Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed)
  326. /// </summary>
  327. /// <param name="start">The start point of the path</param>
  328. /// <param name="end">The end point of the path</param>
  329. /// <param name="callback">The function to call when the path has been calculated</param>
  330. public Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback) {
  331. return StartPath(ABPath.Construct(start, end, null), callback);
  332. }
  333. /// <summary>
  334. /// Call this function to start calculating a path.
  335. ///
  336. /// callback will be called when the path has completed.
  337. /// Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed)
  338. /// </summary>
  339. /// <param name="start">The start point of the path</param>
  340. /// <param name="end">The end point of the path</param>
  341. /// <param name="callback">The function to call when the path has been calculated</param>
  342. /// <param name="graphMask">Mask used to specify which graphs should be searched for close nodes. See #Pathfinding.NNConstraint.graphMask. This will override #graphMask for this path request.</param>
  343. public Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback, GraphMask graphMask) {
  344. return StartPath(ABPath.Construct(start, end, null), callback, graphMask);
  345. }
  346. /// <summary>
  347. /// Call this function to start calculating a path.
  348. ///
  349. /// The callback will be called when the path has been calculated (which may be several frames into the future).
  350. /// The callback will not be called if a new path request is started before this path request has been calculated.
  351. ///
  352. /// Version: Since 3.8.3 this method works properly if a MultiTargetPath is used.
  353. /// It now behaves identically to the StartMultiTargetPath(MultiTargetPath) method.
  354. ///
  355. /// Version: Since 4.1.x this method will no longer overwrite the graphMask on the path unless it is explicitly passed as a parameter (see other overloads of this method).
  356. /// </summary>
  357. /// <param name="p">The path to start calculating</param>
  358. /// <param name="callback">The function to call when the path has been calculated</param>
  359. public Path StartPath (Path p, OnPathDelegate callback = null) {
  360. // Set the graph mask only if the user has not changed it from the default value.
  361. // This is not perfect as the user may have wanted it to be precisely -1
  362. // however it is the best detection that I can do.
  363. // The non-default check is primarily for compatibility reasons to avoid breaking peoples existing code.
  364. // The StartPath overloads with an explicit graphMask field should be used instead to set the graphMask.
  365. if (p.nnConstraint.graphMask == -1) p.nnConstraint.graphMask = graphMask;
  366. StartPathInternal(p, callback);
  367. return p;
  368. }
  369. /// <summary>
  370. /// Call this function to start calculating a path.
  371. ///
  372. /// The callback will be called when the path has been calculated (which may be several frames into the future).
  373. /// The callback will not be called if a new path request is started before this path request has been calculated.
  374. ///
  375. /// Version: Since 3.8.3 this method works properly if a MultiTargetPath is used.
  376. /// It now behaves identically to the StartMultiTargetPath(MultiTargetPath) method.
  377. /// </summary>
  378. /// <param name="p">The path to start calculating</param>
  379. /// <param name="callback">The function to call when the path has been calculated</param>
  380. /// <param name="graphMask">Mask used to specify which graphs should be searched for close nodes. See #Pathfinding.GraphMask. This will override #graphMask for this path request.</param>
  381. public Path StartPath (Path p, OnPathDelegate callback, GraphMask graphMask) {
  382. p.nnConstraint.graphMask = graphMask;
  383. StartPathInternal(p, callback);
  384. return p;
  385. }
  386. /// <summary>Internal method to start a path and mark it as the currently active path</summary>
  387. void StartPathInternal (Path p, OnPathDelegate callback) {
  388. p.callback += onPathDelegate;
  389. p.enabledTags = traversableTags;
  390. p.tagPenalties = tagPenalties;
  391. // Cancel a previously requested path is it has not been processed yet and also make sure that it has not been recycled and used somewhere else
  392. if (path != null && path.PipelineState <= PathState.Processing && path.CompleteState != PathCompleteState.Error && lastPathID == path.pathID) {
  393. path.FailWithError("Canceled path because a new one was requested.\n"+
  394. "This happens when a new path is requested from the seeker when one was already being calculated.\n" +
  395. "For example if a unit got a new order, you might request a new path directly instead of waiting for the now" +
  396. " invalid path to be calculated. Which is probably what you want.\n" +
  397. "If you are getting this a lot, you might want to consider how you are scheduling path requests.");
  398. // No callback will be sent for the canceled path
  399. }
  400. // Set p as the active path
  401. path = p;
  402. tmpPathCallback = callback;
  403. // Save the path id so we can make sure that if we cancel a path (see above) it should not have been recycled yet.
  404. lastPathID = path.pathID;
  405. // Pre process the path
  406. RunModifiers(ModifierPass.PreProcess, path);
  407. // Send the request to the pathfinder
  408. AstarPath.StartPath(path);
  409. }
  410. /// <summary>Draws gizmos for the Seeker</summary>
  411. public void OnDrawGizmos () {
  412. if (lastCompletedNodePath == null || !drawGizmos) {
  413. return;
  414. }
  415. if (detailedGizmos) {
  416. Gizmos.color = new Color(0.7F, 0.5F, 0.1F, 0.5F);
  417. if (lastCompletedNodePath != null) {
  418. for (int i = 0; i < lastCompletedNodePath.Count-1; i++) {
  419. Gizmos.DrawLine((Vector3)lastCompletedNodePath[i].position, (Vector3)lastCompletedNodePath[i+1].position);
  420. }
  421. }
  422. }
  423. Gizmos.color = new Color(0, 1F, 0, 1F);
  424. if (lastCompletedVectorPath != null) {
  425. for (int i = 0; i < lastCompletedVectorPath.Count-1; i++) {
  426. Gizmos.DrawLine(lastCompletedVectorPath[i], lastCompletedVectorPath[i+1]);
  427. }
  428. }
  429. }
  430. protected override int OnUpgradeSerializedData (int version, bool unityThread) {
  431. if (graphMaskCompatibility != -1) {
  432. Debug.Log("Loaded " + graphMaskCompatibility + " " + graphMask.value);
  433. graphMask = graphMaskCompatibility;
  434. graphMaskCompatibility = -1;
  435. }
  436. return base.OnUpgradeSerializedData(version, unityThread);
  437. }
  438. }
  439. }