//#define ASTARDEBUG //Draws a ray for each node visited using UnityEngine; using System; using System.Collections.Generic; namespace Pathfinding { /// <summary> /// Finds all nodes within a specified distance from the start. /// This class will search outwards from the start point and find all nodes which it costs less than <see cref="EndingConditionDistance.maxGScore"/> to reach, this is usually the same as the distance to them multiplied with 1000. /// /// The path can be called like: /// <code> /// // Here you create a new path and set how far it should search. /// ConstantPath cpath = ConstantPath.Construct(transform.position, 20000, null); /// AstarPath.StartPath(cpath); /// /// // Block until the path has been calculated. You can also calculate it asynchronously /// // by providing a callback in the constructor above. /// cpath.BlockUntilCalculated(); /// /// // Draw a line upwards from all nodes within range /// for (int i = 0; i < cpath.allNodes.Count; i++) { /// Debug.DrawRay((Vector3)cpath.allNodes[i].position, Vector3.up, Color.red, 2f); /// } /// </code> /// /// When the path has been calculated, all nodes it searched will be stored in the variable <see cref="ConstantPath.allNodes"/> (remember that you need to cast it from Path to ConstantPath first to get the variable). /// /// This list will be sorted by the cost to reach that node (more specifically the G score if you are familiar with the terminology for search algorithms). /// [Open online documentation to see images] /// </summary> public class ConstantPath : Path { public GraphNode startNode; public Vector3 startPoint; public Vector3 originalStartPoint; /// <summary> /// Contains all nodes the path found. /// This list will be sorted by G score (cost/distance to reach the node). /// </summary> public List<GraphNode> allNodes; /// <summary> /// Determines when the path calculation should stop. /// This is set up automatically in the constructor to an instance of the Pathfinding.EndingConditionDistance class with a maxGScore is specified in the constructor. /// /// See: Pathfinding.PathEndingCondition for examples /// </summary> public PathEndingCondition endingCondition; public override bool FloodingPath { get { return true; } } /// <summary> /// Constructs a ConstantPath starting from the specified point. /// /// Searching will be stopped when a node has a G score (cost to reach it) greater or equal to maxGScore /// in order words it will search all nodes with a cost to get there less than maxGScore. /// </summary> /// <param name="start">From where the path will be started from (the closest node to that point will be used)</param> /// <param name="maxGScore">Searching will be stopped when a node has a G score greater than this</param> /// <param name="callback">Will be called when the path has completed, leave this to null if you use a Seeker to handle calls</param> public static ConstantPath Construct (Vector3 start, int maxGScore, OnPathDelegate callback = null) { var p = PathPool.GetPath<ConstantPath>(); p.Setup(start, maxGScore, callback); return p; } /// <summary>Sets up a ConstantPath starting from the specified point</summary> protected void Setup (Vector3 start, int maxGScore, OnPathDelegate callback) { this.callback = callback; startPoint = start; originalStartPoint = startPoint; endingCondition = new EndingConditionDistance(this, maxGScore); } protected override void OnEnterPool () { base.OnEnterPool(); if (allNodes != null) Util.ListPool<GraphNode>.Release(ref allNodes); } /// <summary> /// Reset the path to default values. /// Clears the <see cref="allNodes"/> list. /// Note: This does not reset the <see cref="endingCondition"/>. /// /// Also sets <see cref="heuristic"/> to Heuristic.None as it is the default value for this path type /// </summary> protected override void Reset () { base.Reset(); allNodes = Util.ListPool<GraphNode>.Claim(); endingCondition = null; originalStartPoint = Vector3.zero; startPoint = Vector3.zero; startNode = null; heuristic = Heuristic.None; } protected override void Prepare () { nnConstraint.tags = enabledTags; var startNNInfo = AstarPath.active.GetNearest(startPoint, nnConstraint); startNode = startNNInfo.node; if (startNode == null) { FailWithError("Could not find close node to the start point"); return; } } /// <summary> /// Initializes the path. /// Sets up the open list and adds the first node to it /// </summary> protected override void Initialize () { PathNode startRNode = pathHandler.GetPathNode(startNode); startRNode.node = startNode; startRNode.pathID = pathHandler.PathID; startRNode.parent = null; startRNode.cost = 0; startRNode.G = GetTraversalCost(startNode); startRNode.H = CalculateHScore(startNode); startNode.Open(this, startRNode, pathHandler); searchedNodes++; startRNode.flag1 = true; allNodes.Add(startNode); //any nodes left to search? if (pathHandler.heap.isEmpty) { CompleteState = PathCompleteState.Complete; return; } currentR = pathHandler.heap.Remove(); } protected override void Cleanup () { int c = allNodes.Count; for (int i = 0; i < c; i++) pathHandler.GetPathNode(allNodes[i]).flag1 = false; } protected override void CalculateStep (long targetTick) { int counter = 0; //Continue to search as long as we haven't encountered an error and we haven't found the target while (CompleteState == PathCompleteState.NotCalculated) { searchedNodes++; //--- Here's the important stuff //Close the current node, if the current node satisfies the ending condition, the path is finished if (endingCondition.TargetFound(currentR)) { CompleteState = PathCompleteState.Complete; break; } if (!currentR.flag1) { //Add Node to allNodes allNodes.Add(currentR.node); currentR.flag1 = true; } #if ASTARDEBUG Debug.DrawRay((Vector3)currentR.node.position, Vector3.up*5, Color.cyan); #endif //--- Here the important stuff ends //Debug.DrawRay ((Vector3)currentR.node.Position, Vector3.up*2,Color.red); //Loop through all walkable neighbours of the node and add them to the open list. currentR.node.Open(this, currentR, pathHandler); //any nodes left to search? if (pathHandler.heap.isEmpty) { CompleteState = PathCompleteState.Complete; break; } //Select the node with the lowest F score and remove it from the open list currentR = pathHandler.heap.Remove(); //Check for time every 500 nodes, roughly every 0.5 ms usually if (counter > 500) { //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 if (DateTime.UtcNow.Ticks >= targetTick) { //Return instead of yield'ing, a separate function handles the yield (CalculatePaths) return; } counter = 0; if (searchedNodes > 1000000) { throw new Exception("Probable infinite loop. Over 1,000,000 nodes searched"); } } counter++; } } } /// <summary> /// Target is found when the path is longer than a specified value. /// Actually this is defined as when the current node's G score is >= a specified amount (EndingConditionDistance.maxGScore). /// The G score is the cost from the start node to the current node, so an area with a higher penalty (weight) will add more to the G score. /// However the G score is usually just the shortest distance from the start to the current node. /// /// See: Pathfinding.ConstantPath which uses this ending condition /// </summary> public class EndingConditionDistance : PathEndingCondition { /// <summary>Max G score a node may have</summary> public int maxGScore = 100; //public EndingConditionDistance () {} public EndingConditionDistance (Path p, int maxGScore) : base(p) { this.maxGScore = maxGScore; } public override bool TargetFound (PathNode node) { return node.G >= maxGScore; } } }