123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504 |
- using UnityEngine;
- using System.Collections.Generic;
- #if UNITY_5_5_OR_NEWER
- using UnityEngine.Profiling;
- #endif
- namespace Pathfinding {
- /// <summary>
- /// Handles path calls for a single unit.
- /// \ingroup relevant
- /// 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.
- ///
- /// [Open online documentation to see images]
- ///
- /// See: calling-pathfinding (view in online documentation for working links)
- /// See: modifiers (view in online documentation for working links)
- /// </summary>
- [AddComponentMenu("Pathfinding/Seeker")]
- [HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_seeker.php")]
- 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.
- ///
- /// Note: This array should always have a length of 32 otherwise the system will ignore it.
- ///
- /// 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.Default;
- ///
- /// 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>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.\n
- /// A temporary callback can also be set when calling StartPath, but that delegate will only be called for that path
- /// </summary>
- 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;
- /// <summary>Used for drawing gizmos</summary>
- [System.NonSerialized]
- List<Vector3> lastCompletedVectorPath;
- /// <summary>Used for drawing gizmos</summary>
- [System.NonSerialized]
- List<GraphNode> lastCompletedNodePath;
- /// <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>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;
- }
- /// <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: pathCallback
- /// </summary>
- public Path GetCurrentPath () {
- return 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: ReleaseClaimedPath
- /// See: startEndModifier
- /// </summary>
- public 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>
- public 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: RunModifiers
- /// \since Added in 3.2
- /// </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
- /// since the path might be completely calculated, but it has not yet been processed by the Seeker.
- ///
- /// \since Added in 3.0.8
- /// Version: Behaviour changed in 3.2
- /// </summary>
- public bool IsDone () {
- return path == null || path.PipelineState >= PathState.Returned;
- }
- /// <summary>
- /// Called when a path has completed.
- /// 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)
- /// See: OnPathComplete(Path,bool,bool)
- /// </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);
- lastCompletedNodePath = p.path;
- lastCompletedVectorPath = p.vectorPath;
- // 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);
- }
- // Recycle the previous path to reduce the load on the GC
- if (prevPath != null) {
- prevPath.Release(this, true);
- }
- prevPath = p;
- // If not drawing gizmos, then storing prevPath is quite unecessary
- // So clear it and set prevPath to null
- if (!drawGizmos) ReleaseClaimedPath();
- }
- }
- /// <summary>
- /// Returns a new path instance.
- /// The path will be taken from the path pool if path recycling is turned on.\n
- /// 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.
- /// <code>
- /// var seeker = GetComponent<Seeker>();
- /// Path p = seeker.GetNewPath (transform.position, transform.position+transform.forward*100);
- /// // Disable heuristics on just this path for example
- /// p.heuristic = Heuristic.None;
- /// seeker.StartPath (p, OnPathComplete);
- /// </code>
- /// Deprecated: Use ABPath.Construct(start, end, null) instead.
- /// </summary>
- [System.Obsolete("Use ABPath.Construct(start, end, null) instead")]
- public ABPath GetNewPath (Vector3 start, Vector3 end) {
- // Construct a path with start and end points
- return ABPath.Construct(start, end, null);
- }
- /// <summary>
- /// Call this function to start calculating a path.
- /// Since this method does not take a callback parameter, you should set the <see cref="pathCallback"/> field before calling this method.
- /// </summary>
- /// <param name="start">The start point of the path</param>
- /// <param name="end">The end point of the path</param>
- public Path StartPath (Vector3 start, Vector3 end) {
- return StartPath(start, end, null);
- }
- /// <summary>
- /// Call this function to start calculating a path.
- ///
- /// callback 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)
- /// </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</param>
- public Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback) {
- return StartPath(ABPath.Construct(start, end, null), callback);
- }
- /// <summary>
- /// Call this function to start calculating a path.
- ///
- /// callback 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)
- /// </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</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>
- /// 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.
- ///
- /// Version: Since 3.8.3 this method works properly if a MultiTargetPath is used.
- /// It now behaves identically to the StartMultiTargetPath(MultiTargetPath) method.
- ///
- /// 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).
- /// </summary>
- /// <param name="p">The path to start calculating</param>
- /// <param name="callback">The function to call when the path has been calculated</param>
- public Path StartPath (Path p, OnPathDelegate callback = null) {
- // 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.
- ///
- /// Version: Since 3.8.3 this method works properly if a MultiTargetPath is used.
- /// It now behaves identically to the StartMultiTargetPath(MultiTargetPath) method.
- /// </summary>
- /// <param name="p">The path to start calculating</param>
- /// <param name="callback">The function to call when the path has been calculated</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) {
- p.callback += onPathDelegate;
- p.enabledTags = traversableTags;
- p.tagPenalties = tagPenalties;
- // 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>Draws gizmos for the Seeker</summary>
- public void OnDrawGizmos () {
- if (lastCompletedNodePath == null || !drawGizmos) {
- return;
- }
- if (detailedGizmos) {
- Gizmos.color = new Color(0.7F, 0.5F, 0.1F, 0.5F);
- if (lastCompletedNodePath != null) {
- for (int i = 0; i < lastCompletedNodePath.Count-1; i++) {
- Gizmos.DrawLine((Vector3)lastCompletedNodePath[i].position, (Vector3)lastCompletedNodePath[i+1].position);
- }
- }
- }
- Gizmos.color = new Color(0, 1F, 0, 1F);
- if (lastCompletedVectorPath != null) {
- for (int i = 0; i < lastCompletedVectorPath.Count-1; i++) {
- Gizmos.DrawLine(lastCompletedVectorPath[i], lastCompletedVectorPath[i+1]);
- }
- }
- }
- protected override int OnUpgradeSerializedData (int version, bool unityThread) {
- if (graphMaskCompatibility != -1) {
- Debug.Log("Loaded " + graphMaskCompatibility + " " + graphMask.value);
- graphMask = graphMaskCompatibility;
- graphMaskCompatibility = -1;
- }
- return base.OnUpgradeSerializedData(version, unityThread);
- }
- }
- }
|