mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-06-26 19:34:47 +00:00
234 lines
8.0 KiB
C#
234 lines
8.0 KiB
C#
|
//#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;
|
||
|
}
|
||
|
}
|
||
|
}
|