2023-08-01 06:49:57 +00:00
//#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.
2024-02-20 18:34:40 +00:00
/// 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.
2023-08-01 06:49:57 +00:00
///
/// The path can be called like:
/// <code>
2024-02-20 18:34:40 +00:00
/// // 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);
/// }
2023-08-01 06:49:57 +00:00
/// </code>
///
2024-02-20 18:34:40 +00:00
/// 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).
2023-08-01 06:49:57 +00:00
///
/// 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>
2024-02-20 18:34:40 +00:00
/// Determines when the path calculation should stop.
2023-08-01 06:49:57 +00:00
/// This is set up automatically in the constructor to an instance of the Pathfinding.EndingConditionDistance class with a maxGScore is specified in the constructor.
2024-02-20 18:34:40 +00:00
///
2023-08-01 06:49:57 +00:00
/// 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 ;
}
}
}