1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
|
using UnityEngine;
using System.Collections.Generic;
using Pathfinding.Util;
using UnityEngine.Profiling;
namespace Pathfinding {
using Pathfinding.Drawing;
/// <summary>
/// Handles path calls for a single unit.
///
/// This is a component which is meant to be attached to a single unit (AI, Robot, Player, whatever) to handle its pathfinding calls.
/// It also handles post-processing of paths using modifiers.
///
/// See: calling-pathfinding (view in online documentation for working links)
/// See: modifiers (view in online documentation for working links)
/// </summary>
[AddComponentMenu("Pathfinding/Seeker")]
[HelpURL("https://arongranberg.com/astar/documentation/stable/seeker.html")]
public class Seeker : VersionedMonoBehaviour {
/// <summary>
/// Enables drawing of the last calculated path using Gizmos.
/// The path will show up in green.
///
/// See: OnDrawGizmos
/// </summary>
public bool drawGizmos = true;
/// <summary>
/// Enables drawing of the non-postprocessed path using Gizmos.
/// The path will show up in orange.
///
/// Requires that <see cref="drawGizmos"/> is true.
///
/// This will show the path before any post processing such as smoothing is applied.
///
/// See: drawGizmos
/// See: OnDrawGizmos
/// </summary>
public bool detailedGizmos;
/// <summary>Path modifier which tweaks the start and end points of a path</summary>
[HideInInspector]
public StartEndModifier startEndModifier = new StartEndModifier();
/// <summary>
/// The tags which the Seeker can traverse.
///
/// Note: This field is a bitmask.
/// See: bitmasks (view in online documentation for working links)
/// </summary>
[HideInInspector]
public int traversableTags = -1;
/// <summary>
/// Penalties for each tag.
/// Tag 0 which is the default tag, will have added a penalty of tagPenalties[0].
/// These should only be positive values since the A* algorithm cannot handle negative penalties.
///
/// The length of this array should be exactly 32, one for each tag.
///
/// See: Pathfinding.Path.tagPenalties
/// </summary>
[HideInInspector]
public int[] tagPenalties = new int[32];
/// <summary>
/// Graphs that this Seeker can use.
/// This field determines which graphs will be considered when searching for the start and end nodes of a path.
/// It is useful in numerous situations, for example if you want to make one graph for small units and one graph for large units.
///
/// 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:
/// <code> seeker.graphMask = 1 << 3; </code>
///
/// See: bitmasks (view in online documentation for working links)
///
/// Note that this field only stores which graph indices that are allowed. This means that if the graphs change their ordering
/// then this mask may no longer be correct.
///
/// If you know the name of the graph you can use the <see cref="Pathfinding.GraphMask.FromGraphName"/> method:
/// <code>
/// GraphMask mask1 = GraphMask.FromGraphName("My Grid Graph");
/// GraphMask mask2 = GraphMask.FromGraphName("My Other Grid Graph");
///
/// NNConstraint nn = NNConstraint.Walkable;
///
/// nn.graphMask = mask1 | mask2;
///
/// // Find the node closest to somePoint which is either in 'My Grid Graph' OR in 'My Other Grid Graph'
/// var info = AstarPath.active.GetNearest(somePoint, nn);
/// </code>
///
/// Some overloads of the <see cref="StartPath"/> methods take a graphMask parameter. If those overloads are used then they
/// will override the graph mask for that path request.
///
/// [Open online documentation to see images]
///
/// See: multiple-agent-types (view in online documentation for working links)
/// </summary>
[HideInInspector]
public GraphMask graphMask = GraphMask.everything;
/// <summary>
/// Custom traversal provider to calculate which nodes are traversable and their penalties.
///
/// This can be used to override the built-in pathfinding logic.
///
/// <code>
/// seeker.traversalProvider = new MyCustomTraversalProvider();
/// </code>
///
/// See: traversal_provider (view in online documentation for working links)
/// </summary>
public ITraversalProvider traversalProvider;
/// <summary>Used for serialization backwards compatibility</summary>
[UnityEngine.Serialization.FormerlySerializedAs("graphMask")]
int graphMaskCompatibility = -1;
/// <summary>
/// Callback for when a path is completed.
/// Movement scripts should register to this delegate.
/// A temporary callback can also be set when calling StartPath, but that delegate will only be called for that path
///
/// <code>
/// public void Start () {
/// // Assumes a Seeker component is attached to the GameObject
/// Seeker seeker = GetComponent<Seeker>();
///
/// // seeker.pathCallback is a OnPathDelegate, we add the function OnPathComplete to it so it will be called whenever a path has finished calculating on that seeker
/// seeker.pathCallback += OnPathComplete;
/// }
///
/// public void OnPathComplete (Path p) {
/// Debug.Log("This is called when a path is completed on the seeker attached to this GameObject");
/// }
/// </code>
///
/// Deprecated: Pass a callback every time to the StartPath method instead, or use ai.SetPath+ai.pathPending on the movement script. You can cache it in your own script if you want to avoid the GC allocation of creating a new delegate.
/// </summary>
[System.Obsolete("Pass a callback every time to the StartPath method instead, or use ai.SetPath+ai.pathPending on the movement script. You can cache it in your own script if you want to avoid the GC allocation of creating a new delegate.")]
public OnPathDelegate pathCallback;
/// <summary>Called before pathfinding is started</summary>
public OnPathDelegate preProcessPath;
/// <summary>Called after a path has been calculated, right before modifiers are executed.</summary>
public OnPathDelegate postProcessPath;
#if UNITY_EDITOR
/// <summary>Used for drawing gizmos</summary>
[System.NonSerialized]
List<Vector3> lastCompletedVectorPath;
/// <summary>Used for drawing gizmos</summary>
[System.NonSerialized]
List<GraphNode> lastCompletedNodePath;
#endif
/// <summary>The current path</summary>
[System.NonSerialized]
protected Path path;
/// <summary>Previous path. Used to draw gizmos</summary>
[System.NonSerialized]
private Path prevPath;
/// <summary>Cached delegate to avoid allocating one every time a path is started</summary>
private readonly OnPathDelegate onPathDelegate;
/// <summary>Cached delegate to avoid allocating one every time a path is started</summary>
private readonly OnPathDelegate onPartialPathDelegate;
/// <summary>Temporary callback only called for the current path. This value is set by the StartPath functions</summary>
private OnPathDelegate tmpPathCallback;
/// <summary>The path ID of the last path queried</summary>
protected uint lastPathID;
/// <summary>Internal list of all modifiers</summary>
readonly List<IPathModifier> modifiers = new List<IPathModifier>();
public enum ModifierPass {
PreProcess,
// An obsolete item occupied index 1 previously
PostProcess = 2,
}
public Seeker () {
onPathDelegate = OnPathComplete;
onPartialPathDelegate = OnPartialPathComplete;
}
/// <summary>Initializes a few variables</summary>
protected override void Awake () {
base.Awake();
startEndModifier.Awake(this);
}
/// <summary>
/// Path that is currently being calculated or was last calculated.
/// You should rarely have to use this. Instead get the path when the path callback is called.
///
/// See: <see cref="StartPath"/>
/// </summary>
public Path GetCurrentPath() => path;
/// <summary>
/// Stop calculating the current path request.
/// If this Seeker is currently calculating a path it will be canceled.
/// The callback (usually to a method named OnPathComplete) will soon be called
/// with a path that has the 'error' field set to true.
///
/// This does not stop the character from moving, it just aborts
/// the path calculation.
/// </summary>
/// <param name="pool">If true then the path will be pooled when the pathfinding system is done with it.</param>
public void CancelCurrentPathRequest (bool pool = true) {
if (!IsDone()) {
path.FailWithError("Canceled by script (Seeker.CancelCurrentPathRequest)");
if (pool) {
// Make sure the path has had its reference count incremented and decremented once.
// If this is not done the system will think no pooling is used at all and will not pool the path.
// The particular object that is used as the parameter (in this case 'path') doesn't matter at all
// it just has to be *some* object.
path.Claim(path);
path.Release(path);
}
}
}
/// <summary>
/// Cleans up some variables.
/// Releases any eventually claimed paths.
/// Calls OnDestroy on the <see cref="startEndModifier"/>.
///
/// See: <see cref="ReleaseClaimedPath"/>
/// See: <see cref="startEndModifier"/>
/// </summary>
void OnDestroy () {
ReleaseClaimedPath();
startEndModifier.OnDestroy(this);
}
/// <summary>
/// Releases the path used for gizmos (if any).
/// The seeker keeps the latest path claimed so it can draw gizmos.
/// In some cases this might not be desireable and you want it released.
/// In that case, you can call this method to release it (not that path gizmos will then not be drawn).
///
/// If you didn't understand anything from the description above, you probably don't need to use this method.
///
/// See: pooling (view in online documentation for working links)
/// </summary>
void ReleaseClaimedPath () {
if (prevPath != null) {
prevPath.Release(this, true);
prevPath = null;
}
}
/// <summary>Called by modifiers to register themselves</summary>
public void RegisterModifier (IPathModifier modifier) {
modifiers.Add(modifier);
// Sort the modifiers based on their specified order
modifiers.Sort((a, b) => a.Order.CompareTo(b.Order));
}
/// <summary>Called by modifiers when they are disabled or destroyed</summary>
public void DeregisterModifier (IPathModifier modifier) {
modifiers.Remove(modifier);
}
/// <summary>
/// Post Processes the path.
/// This will run any modifiers attached to this GameObject on the path.
/// This is identical to calling RunModifiers(ModifierPass.PostProcess, path)
/// See: <see cref="RunModifiers"/>
/// </summary>
public void PostProcess (Path path) {
RunModifiers(ModifierPass.PostProcess, path);
}
/// <summary>Runs modifiers on a path</summary>
public void RunModifiers (ModifierPass pass, Path path) {
if (pass == ModifierPass.PreProcess) {
if (preProcessPath != null) preProcessPath(path);
for (int i = 0; i < modifiers.Count; i++) modifiers[i].PreProcess(path);
} else if (pass == ModifierPass.PostProcess) {
Profiler.BeginSample("Running Path Modifiers");
// Call delegates if they exist
if (postProcessPath != null) postProcessPath(path);
// Loop through all modifiers and apply post processing
for (int i = 0; i < modifiers.Count; i++) modifiers[i].Apply(path);
Profiler.EndSample();
}
}
/// <summary>
/// Is the current path done calculating.
/// Returns true if the current <see cref="path"/> has been returned or if the <see cref="path"/> is null.
///
/// Note: Do not confuse this with Pathfinding.Path.IsDone. They usually return the same value, but not always.
/// The path might be completely calculated, but has not yet been processed by the Seeker.
///
/// Inside the OnPathComplete callback this method will return true.
///
/// Version: Before version 4.2.13 this would return false inside the OnPathComplete callback. However, this behaviour was unintuitive.
/// </summary>
public bool IsDone() => path == null || path.PipelineState >= PathState.Returning;
/// <summary>Called when a path has completed</summary>
void OnPathComplete (Path path) {
OnPathComplete(path, true, true);
}
/// <summary>
/// Called when a path has completed.
/// Will post process it and return it by calling <see cref="tmpPathCallback"/> and <see cref="pathCallback"/>
/// </summary>
void OnPathComplete (Path p, bool runModifiers, bool sendCallbacks) {
if (p != null && p != path && sendCallbacks) {
return;
}
if (this == null || p == null || p != path)
return;
if (!path.error && runModifiers) {
// This will send the path for post processing to modifiers attached to this Seeker
RunModifiers(ModifierPass.PostProcess, path);
}
if (sendCallbacks) {
p.Claim(this);
#if UNITY_EDITOR
lastCompletedNodePath = p.path;
lastCompletedVectorPath = p.vectorPath;
#endif
#pragma warning disable 618
if (tmpPathCallback == null && pathCallback == null) {
#if UNITY_EDITOR
// This checks for a common error that people make when they upgrade from an older version
// This will be removed in a future version to avoid the slight performance cost.
if (TryGetComponent<IAstarAI>(out var ai)) {
Debug.LogWarning("A path was calculated, but no callback was specified when calling StartPath. If you wanted a movement script to use this path, use <b>ai.SetPath</b> instead of calling StartPath on the Seeker directly. The path will be forwarded to the attached movement script, but this behavior will be removed in the future.", this);
ai.SetPath(p);
}
#endif
} else {
// This will send the path to the callback (if any) specified when calling StartPath
if (tmpPathCallback != null) {
tmpPathCallback(p);
}
// This will send the path to any script which has registered to the callback
if (pathCallback != null) {
pathCallback(p);
}
}
#pragma warning restore 618
// Note: it is important that #prevPath is kept alive (i.e. not pooled)
// if we are drawing gizmos.
// It is also important that #path is kept alive since it can be returned
// from the GetCurrentPath method.
// Since #path will be copied to #prevPath it is sufficient that #prevPath
// is kept alive until it is replaced.
// Recycle the previous path to reduce the load on the GC
if (prevPath != null) {
prevPath.Release(this, true);
}
prevPath = p;
}
}
/// <summary>
/// Called for each path in a MultiTargetPath.
/// Only post processes the path, does not return it.
/// </summary>
void OnPartialPathComplete (Path p) {
OnPathComplete(p, true, false);
}
/// <summary>Called once for a MultiTargetPath. Only returns the path, does not post process.</summary>
void OnMultiPathComplete (Path p) {
OnPathComplete(p, false, true);
}
/// <summary>
/// Queue a path to be calculated.
/// Since this method does not take a callback parameter, you should set the <see cref="pathCallback"/> field before calling this method.
///
/// <code>
/// void Start () {
/// // Get the seeker component attached to this GameObject
/// var seeker = GetComponent<Seeker>();
///
/// // Schedule a new path request from the current position to a position 10 units forward.
/// // When the path has been calculated, the OnPathComplete method will be called, unless it was canceled by another path request
/// seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, OnPathComplete);
///
/// // Note that the path is NOT calculated at this point
/// // It has just been queued for calculation
/// }
///
/// void OnPathComplete (Path path) {
/// // The path is now calculated!
///
/// if (path.error) {
/// Debug.LogError("Path failed: " + path.errorLog);
/// return;
/// }
///
/// // Cast the path to the path type we were using
/// var abPath = path as ABPath;
///
/// // Draw the path in the scene view for 10 seconds
/// for (int i = 0; i < abPath.vectorPath.Count - 1; i++) {
/// Debug.DrawLine(abPath.vectorPath[i], abPath.vectorPath[i+1], Color.red, 10);
/// }
/// }
/// </code>
///
/// Deprecated: Use <see cref="StartPath(Vector3,Vector3,OnPathDelegate)"/> instead.
/// </summary>
/// <param name="start">The start point of the path</param>
/// <param name="end">The end point of the path</param>
[System.Obsolete("Use the overload that takes a callback instead")]
public Path StartPath (Vector3 start, Vector3 end) {
return StartPath(start, end, null);
}
/// <summary>
/// Queue a path to be calculated.
///
/// The callback will be called when the path has been calculated (which may be several frames into the future).
/// Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed)
///
/// <code>
/// void Start () {
/// // Get the seeker component attached to this GameObject
/// var seeker = GetComponent<Seeker>();
///
/// // Schedule a new path request from the current position to a position 10 units forward.
/// // When the path has been calculated, the OnPathComplete method will be called, unless it was canceled by another path request
/// seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, OnPathComplete);
///
/// // Note that the path is NOT calculated at this point
/// // It has just been queued for calculation
/// }
///
/// void OnPathComplete (Path path) {
/// // The path is now calculated!
///
/// if (path.error) {
/// Debug.LogError("Path failed: " + path.errorLog);
/// return;
/// }
///
/// // Cast the path to the path type we were using
/// var abPath = path as ABPath;
///
/// // Draw the path in the scene view for 10 seconds
/// for (int i = 0; i < abPath.vectorPath.Count - 1; i++) {
/// Debug.DrawLine(abPath.vectorPath[i], abPath.vectorPath[i+1], Color.red, 10);
/// }
/// }
/// </code>
/// </summary>
/// <param name="start">The start point of the path</param>
/// <param name="end">The end point of the path</param>
/// <param name="callback">The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null.</param>
public Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback) {
return StartPath(ABPath.Construct(start, end, null), callback);
}
/// <summary>
/// Queue a path to be calculated.
///
/// The callback will be called when the path has been calculated (which may be several frames into the future).
/// Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed)
///
/// <code>
/// // Schedule a path search that will only start searching the graphs with index 0 and 3
/// seeker.StartPath(startPoint, endPoint, null, 1 << 0 | 1 << 3);
/// </code>
/// </summary>
/// <param name="start">The start point of the path</param>
/// <param name="end">The end point of the path</param>
/// <param name="callback">The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null.</param>
/// <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>
public Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback, GraphMask graphMask) {
return StartPath(ABPath.Construct(start, end, null), callback, graphMask);
}
/// <summary>
/// Queue a path to be calculated.
///
/// 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).
///
/// This overload takes no callback parameter. Instead, it is expected that you poll the path for completion, or block until it is completed.
///
/// See: <see cref="IsDone"/>
/// See: <see cref="Path.WaitForPath"/>
/// See: <see cref="Path.BlockUntilCalculated"/>
///
/// However, <see cref="Path.IsDone"/> should not be used with the Seeker component. This is because while the path itself may be calculated, the Seeker may not have had time to run post processing modifiers on the path yet.
/// </summary>
/// <param name="p">The path to start calculating</param>
public Path StartPath (Path p) {
return StartPath(p, null);
}
/// <summary>
/// Queue a path to be calculated.
///
/// The callback will be called when the path has been calculated (which may be several frames into the future).
/// The callback will not be called if a new path request is started before this path request has been calculated.
/// </summary>
/// <param name="p">The path to start calculating</param>
/// <param name="callback">The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null.</param>
public Path StartPath (Path p, OnPathDelegate callback) {
// Set the graph mask only if the user has not changed it from the default value.
// This is not perfect as the user may have wanted it to be precisely -1
// however it is the best detection that I can do.
// The non-default check is primarily for compatibility reasons to avoid breaking peoples existing code.
// The StartPath overloads with an explicit graphMask field should be used instead to set the graphMask.
if (p.nnConstraint.graphMask == -1) p.nnConstraint.graphMask = graphMask;
StartPathInternal(p, callback);
return p;
}
/// <summary>
/// Call this function to start calculating a path.
///
/// The callback will be called when the path has been calculated (which may be several frames into the future).
/// The callback will not be called if a new path request is started before this path request has been calculated.
/// </summary>
/// <param name="p">The path to start calculating</param>
/// <param name="callback">The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null.</param>
/// <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>
public Path StartPath (Path p, OnPathDelegate callback, GraphMask graphMask) {
p.nnConstraint.graphMask = graphMask;
StartPathInternal(p, callback);
return p;
}
/// <summary>Internal method to start a path and mark it as the currently active path</summary>
void StartPathInternal (Path p, OnPathDelegate callback) {
var mtp = p as MultiTargetPath;
if (mtp != null) {
// TODO: Allocation, cache
var callbacks = new OnPathDelegate[mtp.targetPoints.Length];
for (int i = 0; i < callbacks.Length; i++) {
callbacks[i] = onPartialPathDelegate;
}
mtp.callbacks = callbacks;
p.callback += OnMultiPathComplete;
} else {
p.callback += onPathDelegate;
}
p.enabledTags = traversableTags;
p.tagPenalties = tagPenalties;
if (traversalProvider != null) p.traversalProvider = traversalProvider;
// 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
if (path != null && path.PipelineState <= PathState.Processing && path.CompleteState != PathCompleteState.Error && lastPathID == path.pathID) {
path.FailWithError("Canceled path because a new one was requested.\n"+
"This happens when a new path is requested from the seeker when one was already being calculated.\n" +
"For example if a unit got a new order, you might request a new path directly instead of waiting for the now" +
" invalid path to be calculated. Which is probably what you want.\n" +
"If you are getting this a lot, you might want to consider how you are scheduling path requests.");
// No callback will be sent for the canceled path
}
// Set p as the active path
path = p;
tmpPathCallback = callback;
// Save the path id so we can make sure that if we cancel a path (see above) it should not have been recycled yet.
lastPathID = path.pathID;
// Pre process the path
RunModifiers(ModifierPass.PreProcess, path);
// Send the request to the pathfinder
AstarPath.StartPath(path);
}
/// <summary>
/// Starts a Multi Target Path from one start point to multiple end points.
/// A Multi Target Path will search for all the end points in one search and will return all paths if pathsForAll is true, or only the shortest one if pathsForAll is false.
///
/// callback and <see cref="pathCallback"/> will be called when the path has completed. Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed)
///
/// See: Pathfinding.MultiTargetPath
/// See: MultiTargetPathExample.cs (view in online documentation for working links) "Example of how to use multi-target-paths"
///
/// <code>
/// var endPoints = new Vector3[] {
/// transform.position + Vector3.forward * 5,
/// transform.position + Vector3.right * 10,
/// transform.position + Vector3.back * 15
/// };
/// // Start a multi target path, where endPoints is a Vector3[] array.
/// // The pathsForAll parameter specifies if a path to every end point should be searched for
/// // or if it should only try to find the shortest path to any end point.
/// var path = seeker.StartMultiTargetPath(transform.position, endPoints, pathsForAll: true, callback: null);
/// path.BlockUntilCalculated();
///
/// if (path.error) {
/// Debug.LogError("Error calculating path: " + path.errorLog);
/// return;
/// }
///
/// Debug.Log("The closest target was index " + path.chosenTarget);
///
/// // Draw the path to all targets
/// foreach (var subPath in path.vectorPaths) {
/// for (int i = 0; i < subPath.Count - 1; i++) {
/// Debug.DrawLine(subPath[i], subPath[i+1], Color.green, 10);
/// }
/// }
///
/// // Draw the path to the closest target
/// for (int i = 0; i < path.vectorPath.Count - 1; i++) {
/// Debug.DrawLine(path.vectorPath[i], path.vectorPath[i+1], Color.red, 10);
/// }
/// </code>
/// </summary>
/// <param name="start">The start point of the path</param>
/// <param name="endPoints">The end points of the path</param>
/// <param name="pathsForAll">Indicates whether or not a path to all end points should be searched for or only to the closest one</param>
/// <param name="callback">The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null.</param>
/// <param name="graphMask">Mask used to specify which graphs should be searched for close nodes. See Pathfinding.NNConstraint.graphMask.</param>
public MultiTargetPath StartMultiTargetPath (Vector3 start, Vector3[] endPoints, bool pathsForAll, OnPathDelegate callback, int graphMask = -1) {
MultiTargetPath p = MultiTargetPath.Construct(start, endPoints, null, null);
p.pathsForAll = pathsForAll;
StartPath(p, callback, graphMask);
return p;
}
/// <summary>
/// Starts a Multi Target Path from multiple start points to a single target point.
/// A Multi Target Path will search from all start points to the target point in one search and will return all paths if pathsForAll is true, or only the shortest one if pathsForAll is false.
///
/// callback and <see cref="pathCallback"/> will be called when the path has completed. Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed)
///
/// See: Pathfinding.MultiTargetPath
/// See: MultiTargetPathExample.cs (view in online documentation for working links) "Example of how to use multi-target-paths"
/// </summary>
/// <param name="startPoints">The start points of the path</param>
/// <param name="end">The end point of the path</param>
/// <param name="pathsForAll">Indicates whether or not a path from all start points should be searched for or only to the closest one</param>
/// <param name="callback">The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null.</param>
/// <param name="graphMask">Mask used to specify which graphs should be searched for close nodes. See Pathfinding.NNConstraint.graphMask.</param>
public MultiTargetPath StartMultiTargetPath (Vector3[] startPoints, Vector3 end, bool pathsForAll, OnPathDelegate callback, int graphMask = -1) {
MultiTargetPath p = MultiTargetPath.Construct(startPoints, end, null, null);
p.pathsForAll = pathsForAll;
StartPath(p, callback, graphMask);
return p;
}
#if UNITY_EDITOR
/// <summary>Draws gizmos for the Seeker</summary>
public override void DrawGizmos () {
if (lastCompletedNodePath == null || !drawGizmos) {
return;
}
if (detailedGizmos && lastCompletedNodePath != null) {
using (Draw.WithColor(new Color(0.7F, 0.5F, 0.1F, 0.5F))) {
for (int i = 0; i < lastCompletedNodePath.Count-1; i++) {
Draw.Line((Vector3)lastCompletedNodePath[i].position, (Vector3)lastCompletedNodePath[i+1].position);
}
}
}
if (lastCompletedVectorPath != null) {
using (Draw.WithColor(new Color(0, 1F, 0, 1F))) {
for (int i = 0; i < lastCompletedVectorPath.Count-1; i++) {
Draw.Line(lastCompletedVectorPath[i], lastCompletedVectorPath[i+1]);
}
}
}
}
#endif
protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) {
if (graphMaskCompatibility != -1) {
graphMask = graphMaskCompatibility;
graphMaskCompatibility = -1;
}
base.OnUpgradeSerializedData(ref migrations, unityThread);
}
}
}
|