mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-11-12 17:18:22 +00:00
提交
This commit is contained in:
@@ -0,0 +1,768 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Basic path, finds the shortest path from A to B.
|
||||
///
|
||||
/// This is the most basic path object it will try to find the shortest path between two points.
|
||||
/// Many other path types inherit from this type.
|
||||
/// See: Seeker.StartPath
|
||||
/// See: calling-pathfinding (view in online documentation for working links)
|
||||
/// See: getstarted (view in online documentation for working links)
|
||||
/// </summary>
|
||||
public class ABPath : Path {
|
||||
/// <summary>Start node of the path</summary>
|
||||
public GraphNode startNode;
|
||||
|
||||
/// <summary>End node of the path</summary>
|
||||
public GraphNode endNode;
|
||||
|
||||
/// <summary>Start Point exactly as in the path request</summary>
|
||||
public Vector3 originalStartPoint;
|
||||
|
||||
/// <summary>End Point exactly as in the path request</summary>
|
||||
public Vector3 originalEndPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Start point of the path.
|
||||
/// This is the closest point on the <see cref="startNode"/> to <see cref="originalStartPoint"/>
|
||||
/// </summary>
|
||||
public Vector3 startPoint;
|
||||
|
||||
/// <summary>
|
||||
/// End point of the path.
|
||||
/// This is the closest point on the <see cref="endNode"/> to <see cref="originalEndPoint"/>
|
||||
/// </summary>
|
||||
public Vector3 endPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Total cost of this path as used by the pathfinding algorithm.
|
||||
///
|
||||
/// The cost is influenced by both the length of the path, as well as any tags or penalties on the nodes.
|
||||
/// By default, the cost to move 1 world unit is <see cref="Int3.Precision"/>.
|
||||
///
|
||||
/// If the path failed, the cost will be set to zero.
|
||||
///
|
||||
/// See: tags (view in online documentation for working links)
|
||||
/// </summary>
|
||||
public uint cost;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a search for an end node should be done.
|
||||
/// Set by different path types.
|
||||
/// Since: Added in 3.0.8.3
|
||||
/// </summary>
|
||||
protected virtual bool hasEndPoint {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Start point in integer coordinates</summary>
|
||||
public Int3 startIntPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Calculate partial path if the target node cannot be reached.
|
||||
/// If the target node cannot be reached, the node which was closest (given by heuristic) will be chosen as target node
|
||||
/// and a partial path will be returned.
|
||||
/// This only works if a heuristic is used (which is the default).
|
||||
/// If a partial path is found, CompleteState is set to Partial.
|
||||
/// Note: It is not required by other path types to respect this setting
|
||||
///
|
||||
/// The <see cref="endNode"/> and <see cref="endPoint"/> will be modified and be set to the node which ends up being closest to the target.
|
||||
///
|
||||
/// Warning: Using this may make path calculations significantly slower if you have a big graph. The reason is that
|
||||
/// when the target node cannot be reached, the path must search through every single other node that it can reach in order
|
||||
/// to determine which one is closest. This may be expensive, and is why this option is disabled by default.
|
||||
/// </summary>
|
||||
public bool calculatePartial;
|
||||
|
||||
/// <summary>
|
||||
/// Current best target for the partial path.
|
||||
/// This is the node with the lowest H score.
|
||||
/// </summary>
|
||||
protected PathNode partialBestTarget;
|
||||
|
||||
/// <summary>Saved original costs for the end node. See: ResetCosts</summary>
|
||||
protected int[] endNodeCosts;
|
||||
|
||||
#if !ASTAR_NO_GRID_GRAPH
|
||||
/// <summary>Used in EndPointGridGraphSpecialCase</summary>
|
||||
GridNode gridSpecialCaseNode;
|
||||
#endif
|
||||
|
||||
/// <summary>@{ @name Constructors</summary>
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// Do not use this. Instead use the static Construct method which can handle path pooling.
|
||||
/// </summary>
|
||||
public ABPath () {}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a path with a start and end point.
|
||||
/// The delegate will be called when the path has been calculated.
|
||||
/// Do not confuse it with the Seeker callback as they are sent at different times.
|
||||
/// If you are using a Seeker to start the path you can set callback to null.
|
||||
///
|
||||
/// Returns: The constructed path object
|
||||
/// </summary>
|
||||
public static ABPath Construct (Vector3 start, Vector3 end, OnPathDelegate callback = null) {
|
||||
var p = PathPool.GetPath<ABPath>();
|
||||
|
||||
p.Setup(start, end, callback);
|
||||
return p;
|
||||
}
|
||||
|
||||
protected void Setup (Vector3 start, Vector3 end, OnPathDelegate callbackDelegate) {
|
||||
callback = callbackDelegate;
|
||||
UpdateStartEnd(start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fake path.
|
||||
/// Creates a path that looks almost exactly like it would if the pathfinding system had calculated it.
|
||||
///
|
||||
/// This is useful if you want your agents to follow some known path that cannot be calculated using the pathfinding system for some reason.
|
||||
///
|
||||
/// <code>
|
||||
/// var path = ABPath.FakePath(new List<Vector3> { new Vector3(1, 2, 3), new Vector3(4, 5, 6) });
|
||||
///
|
||||
/// ai.SetPath(path);
|
||||
/// </code>
|
||||
///
|
||||
/// You can use it to combine existing paths like this:
|
||||
///
|
||||
/// <code>
|
||||
/// var a = Vector3.zero;
|
||||
/// var b = new Vector3(1, 2, 3);
|
||||
/// var c = new Vector3(2, 3, 4);
|
||||
/// var path1 = ABPath.Construct(a, b);
|
||||
/// var path2 = ABPath.Construct(b, c);
|
||||
///
|
||||
/// AstarPath.StartPath(path1);
|
||||
/// AstarPath.StartPath(path2);
|
||||
/// path1.BlockUntilCalculated();
|
||||
/// path2.BlockUntilCalculated();
|
||||
///
|
||||
/// // Combine the paths
|
||||
/// // Note: Skip the first element in the second path as that will likely be the last element in the first path
|
||||
/// var newVectorPath = path1.vectorPath.Concat(path2.vectorPath.Skip(1)).ToList();
|
||||
/// var newNodePath = path1.path.Concat(path2.path.Skip(1)).ToList();
|
||||
/// var combinedPath = ABPath.FakePath(newVectorPath, newNodePath);
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public static ABPath FakePath (List<Vector3> vectorPath, List<GraphNode> nodePath = null) {
|
||||
var path = PathPool.GetPath<ABPath>();
|
||||
|
||||
for (int i = 0; i < vectorPath.Count; i++) path.vectorPath.Add(vectorPath[i]);
|
||||
|
||||
path.completeState = PathCompleteState.Complete;
|
||||
((IPathInternals)path).AdvanceState(PathState.Returned);
|
||||
|
||||
if (vectorPath.Count > 0) {
|
||||
path.UpdateStartEnd(vectorPath[0], vectorPath[vectorPath.Count - 1]);
|
||||
}
|
||||
|
||||
if (nodePath != null) {
|
||||
for (int i = 0; i < nodePath.Count; i++) path.path.Add(nodePath[i]);
|
||||
if (nodePath.Count > 0) {
|
||||
path.startNode = nodePath[0];
|
||||
path.endNode = nodePath[nodePath.Count - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>@}</summary>
|
||||
|
||||
/// <summary>
|
||||
/// Sets the start and end points.
|
||||
/// Sets <see cref="originalStartPoint"/>, <see cref="originalEndPoint"/>, <see cref="startPoint"/>, <see cref="endPoint"/>, <see cref="startIntPoint"/> and <see cref="hTarget"/> (to end )
|
||||
/// </summary>
|
||||
protected void UpdateStartEnd (Vector3 start, Vector3 end) {
|
||||
originalStartPoint = start;
|
||||
originalEndPoint = end;
|
||||
|
||||
startPoint = start;
|
||||
endPoint = end;
|
||||
|
||||
startIntPoint = (Int3)start;
|
||||
hTarget = (Int3)end;
|
||||
}
|
||||
|
||||
public override uint GetConnectionSpecialCost (GraphNode a, GraphNode b, uint currentCost) {
|
||||
if (startNode != null && endNode != null) {
|
||||
if (a == startNode) {
|
||||
return (uint)((startIntPoint - (b == endNode ? hTarget : b.position)).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
|
||||
}
|
||||
if (b == startNode) {
|
||||
return (uint)((startIntPoint - (a == endNode ? hTarget : a.position)).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
|
||||
}
|
||||
if (a == endNode) {
|
||||
return (uint)((hTarget - b.position).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
|
||||
}
|
||||
if (b == endNode) {
|
||||
return (uint)((hTarget - a.position).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
|
||||
}
|
||||
} else {
|
||||
// endNode is null, startNode should never be null for an ABPath
|
||||
if (a == startNode) {
|
||||
return (uint)((startIntPoint - b.position).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
|
||||
}
|
||||
if (b == startNode) {
|
||||
return (uint)((startIntPoint - a.position).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
|
||||
}
|
||||
}
|
||||
|
||||
return currentCost;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset all values to their default values.
|
||||
/// All inheriting path types must implement this function, resetting ALL their variables to enable recycling of paths.
|
||||
/// Call this base function in inheriting types with base.Reset ();
|
||||
/// </summary>
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
|
||||
startNode = null;
|
||||
endNode = null;
|
||||
originalStartPoint = Vector3.zero;
|
||||
originalEndPoint = Vector3.zero;
|
||||
startPoint = Vector3.zero;
|
||||
endPoint = Vector3.zero;
|
||||
calculatePartial = false;
|
||||
partialBestTarget = null;
|
||||
startIntPoint = new Int3();
|
||||
hTarget = new Int3();
|
||||
endNodeCosts = null;
|
||||
cost = 0;
|
||||
|
||||
#if !ASTAR_NO_GRID_GRAPH
|
||||
gridSpecialCaseNode = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !ASTAR_NO_GRID_GRAPH
|
||||
/// <summary>Cached <see cref="Pathfinding.NNConstraint.None"/> to reduce allocations</summary>
|
||||
static readonly NNConstraint NNConstraintNone = NNConstraint.None;
|
||||
|
||||
/// <summary>
|
||||
/// Applies a special case for grid nodes.
|
||||
///
|
||||
/// Assume the closest walkable node is a grid node.
|
||||
/// We will now apply a special case only for grid graphs.
|
||||
/// In tile based games, an obstacle often occupies a whole
|
||||
/// node. When a path is requested to the position of an obstacle
|
||||
/// (single unwalkable node) the closest walkable node will be
|
||||
/// one of the 8 nodes surrounding that unwalkable node
|
||||
/// but that node is not neccessarily the one that is most
|
||||
/// optimal to walk to so in this special case
|
||||
/// we mark all nodes around the unwalkable node as targets
|
||||
/// and when we search and find any one of them we simply exit
|
||||
/// and set that first node we found to be the 'real' end node
|
||||
/// because that will be the optimal node (this does not apply
|
||||
/// in general unless the heuristic is set to None, but
|
||||
/// for a single unwalkable node it does).
|
||||
/// This also applies if the nearest node cannot be traversed for
|
||||
/// some other reason like restricted tags.
|
||||
///
|
||||
/// Returns: True if the workaround was applied. If this happens the
|
||||
/// endPoint, endNode, hTarget and hTargetNode fields will be modified.
|
||||
///
|
||||
/// Image below shows paths when this special case is applied. The path goes from the white sphere to the orange box.
|
||||
/// [Open online documentation to see images]
|
||||
///
|
||||
/// Image below shows paths when this special case has been disabled
|
||||
/// [Open online documentation to see images]
|
||||
/// </summary>
|
||||
protected virtual bool EndPointGridGraphSpecialCase (GraphNode closestWalkableEndNode) {
|
||||
var gridNode = closestWalkableEndNode as GridNode;
|
||||
|
||||
if (gridNode != null) {
|
||||
var gridGraph = GridNode.GetGridGraph(gridNode.GraphIndex);
|
||||
|
||||
// Find the closest node, not neccessarily walkable
|
||||
var endNNInfo2 = AstarPath.active.GetNearest(originalEndPoint, NNConstraintNone);
|
||||
var gridNode2 = endNNInfo2.node as GridNode;
|
||||
|
||||
if (gridNode != gridNode2 && gridNode2 != null && gridNode.GraphIndex == gridNode2.GraphIndex) {
|
||||
// Calculate the coordinates of the nodes
|
||||
var x1 = gridNode.NodeInGridIndex % gridGraph.width;
|
||||
var z1 = gridNode.NodeInGridIndex / gridGraph.width;
|
||||
|
||||
var x2 = gridNode2.NodeInGridIndex % gridGraph.width;
|
||||
var z2 = gridNode2.NodeInGridIndex / gridGraph.width;
|
||||
|
||||
bool wasClose = false;
|
||||
switch (gridGraph.neighbours) {
|
||||
case NumNeighbours.Four:
|
||||
if ((x1 == x2 && System.Math.Abs(z1-z2) == 1) || (z1 == z2 && System.Math.Abs(x1-x2) == 1)) {
|
||||
// If 'O' is gridNode2, then gridNode is one of the nodes marked with an 'x'
|
||||
// x
|
||||
// x O x
|
||||
// x
|
||||
wasClose = true;
|
||||
}
|
||||
break;
|
||||
case NumNeighbours.Eight:
|
||||
if (System.Math.Abs(x1-x2) <= 1 && System.Math.Abs(z1-z2) <= 1) {
|
||||
// If 'O' is gridNode2, then gridNode is one of the nodes marked with an 'x'
|
||||
// x x x
|
||||
// x O x
|
||||
// x x x
|
||||
wasClose = true;
|
||||
}
|
||||
break;
|
||||
case NumNeighbours.Six:
|
||||
// Hexagon graph
|
||||
for (int i = 0; i < 6; i++) {
|
||||
var nx = x2 + gridGraph.neighbourXOffsets[GridGraph.hexagonNeighbourIndices[i]];
|
||||
var nz = z2 + gridGraph.neighbourZOffsets[GridGraph.hexagonNeighbourIndices[i]];
|
||||
if (x1 == nx && z1 == nz) {
|
||||
// If 'O' is gridNode2, then gridNode is one of the nodes marked with an 'x'
|
||||
// x x
|
||||
// x O x
|
||||
// x x
|
||||
wasClose = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Should not happen unless NumNeighbours is modified in the future
|
||||
throw new System.Exception("Unhandled NumNeighbours");
|
||||
}
|
||||
|
||||
if (wasClose) {
|
||||
// We now need to find all nodes marked with an x to be able to mark them as targets
|
||||
SetFlagOnSurroundingGridNodes(gridNode2, 1, true);
|
||||
|
||||
// Note, other methods assume hTarget is (Int3)endPoint
|
||||
endPoint = (Vector3)gridNode2.position;
|
||||
hTarget = gridNode2.position;
|
||||
endNode = gridNode2;
|
||||
|
||||
// hTargetNode is used for heuristic optimizations
|
||||
// (also known as euclidean embedding).
|
||||
// Even though the endNode is not walkable
|
||||
// we can use it for better heuristics since
|
||||
// there is a workaround added (EuclideanEmbedding.ApplyGridGraphEndpointSpecialCase)
|
||||
// which is there to support this case.
|
||||
hTargetNode = endNode;
|
||||
|
||||
// We need to save this node
|
||||
// so that we can reset flag1 on all nodes later
|
||||
gridSpecialCaseNode = gridNode2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Helper method to set PathNode.flag1 to a specific value for all nodes adjacent to a grid node</summary>
|
||||
void SetFlagOnSurroundingGridNodes (GridNode gridNode, int flag, bool flagState) {
|
||||
// Loop through all adjacent grid nodes
|
||||
var gridGraph = GridNode.GetGridGraph(gridNode.GraphIndex);
|
||||
|
||||
// Number of neighbours as an int
|
||||
int mxnum = gridGraph.neighbours == NumNeighbours.Four ? 4 : (gridGraph.neighbours == NumNeighbours.Eight ? 8 : 6);
|
||||
|
||||
// Calculate the coordinates of the node
|
||||
var x = gridNode.NodeInGridIndex % gridGraph.width;
|
||||
var z = gridNode.NodeInGridIndex / gridGraph.width;
|
||||
|
||||
if (flag != 1 && flag != 2)
|
||||
throw new System.ArgumentOutOfRangeException("flag");
|
||||
|
||||
for (int i = 0; i < mxnum; i++) {
|
||||
int nx, nz;
|
||||
if (gridGraph.neighbours == NumNeighbours.Six) {
|
||||
// Hexagon graph
|
||||
nx = x + gridGraph.neighbourXOffsets[GridGraph.hexagonNeighbourIndices[i]];
|
||||
nz = z + gridGraph.neighbourZOffsets[GridGraph.hexagonNeighbourIndices[i]];
|
||||
} else {
|
||||
nx = x + gridGraph.neighbourXOffsets[i];
|
||||
nz = z + gridGraph.neighbourZOffsets[i];
|
||||
}
|
||||
|
||||
// Check if the position is still inside the grid
|
||||
if (nx >= 0 && nz >= 0 && nx < gridGraph.width && nz < gridGraph.depth) {
|
||||
var adjacentNode = gridGraph.nodes[nz*gridGraph.width + nx];
|
||||
var pathNode = pathHandler.GetPathNode(adjacentNode);
|
||||
if (flag == 1) pathNode.flag1 = flagState;
|
||||
else pathNode.flag2 = flagState;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>Prepares the path. Searches for start and end nodes and does some simple checking if a path is at all possible</summary>
|
||||
protected override void Prepare () {
|
||||
//Initialize the NNConstraint
|
||||
nnConstraint.tags = enabledTags;
|
||||
var startNNInfo = AstarPath.active.GetNearest(startPoint, nnConstraint);
|
||||
|
||||
//Tell the NNConstraint which node was found as the start node if it is a PathNNConstraint and not a normal NNConstraint
|
||||
var pathNNConstraint = nnConstraint as PathNNConstraint;
|
||||
if (pathNNConstraint != null) {
|
||||
pathNNConstraint.SetStart(startNNInfo.node);
|
||||
}
|
||||
|
||||
startPoint = startNNInfo.position;
|
||||
|
||||
startIntPoint = (Int3)startPoint;
|
||||
startNode = startNNInfo.node;
|
||||
|
||||
if (startNode == null) {
|
||||
FailWithError("Couldn't find a node close to the start point");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanTraverse(startNode)) {
|
||||
FailWithError("The node closest to the start point could not be traversed");
|
||||
return;
|
||||
}
|
||||
|
||||
// If it is declared that this path type has an end point
|
||||
// Some path types might want to use most of the ABPath code, but will not have an explicit end point at this stage
|
||||
if (hasEndPoint) {
|
||||
var endNNInfo = AstarPath.active.GetNearest(endPoint, nnConstraint);
|
||||
endPoint = endNNInfo.position;
|
||||
endNode = endNNInfo.node;
|
||||
|
||||
if (endNode == null) {
|
||||
FailWithError("Couldn't find a node close to the end point");
|
||||
return;
|
||||
}
|
||||
|
||||
// This should not trigger unless the user has modified the NNConstraint
|
||||
if (!CanTraverse(endNode)) {
|
||||
FailWithError("The node closest to the end point could not be traversed");
|
||||
return;
|
||||
}
|
||||
|
||||
// This should not trigger unless the user has modified the NNConstraint
|
||||
if (startNode.Area != endNode.Area) {
|
||||
FailWithError("There is no valid path to the target");
|
||||
return;
|
||||
}
|
||||
|
||||
#if !ASTAR_NO_GRID_GRAPH
|
||||
// Potentially we want to special case grid graphs a bit
|
||||
// to better support some kinds of games
|
||||
// If this returns true it will overwrite the
|
||||
// endNode, endPoint, hTarget and hTargetNode fields
|
||||
if (!EndPointGridGraphSpecialCase(endNNInfo.node))
|
||||
#endif
|
||||
{
|
||||
// Note, other methods assume hTarget is (Int3)endPoint
|
||||
hTarget = (Int3)endPoint;
|
||||
hTargetNode = endNode;
|
||||
|
||||
// Mark end node with flag1 to mark it as a target point
|
||||
pathHandler.GetPathNode(endNode).flag1 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the start node is the target and complete the path if that is the case.
|
||||
/// This is necessary so that subclasses (e.g XPath) can override this behaviour.
|
||||
///
|
||||
/// If the start node is a valid target point, this method should set CompleteState to Complete
|
||||
/// and trace the path.
|
||||
/// </summary>
|
||||
protected virtual void CompletePathIfStartIsValidTarget () {
|
||||
// flag1 specifies if a node is a target node for the path
|
||||
if (hasEndPoint && pathHandler.GetPathNode(startNode).flag1) {
|
||||
CompleteWith(startNode);
|
||||
Trace(pathHandler.GetPathNode(startNode));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Initialize () {
|
||||
// Mark nodes to enable special connection costs for start and end nodes
|
||||
// See GetConnectionSpecialCost
|
||||
if (startNode != null) pathHandler.GetPathNode(startNode).flag2 = true;
|
||||
if (endNode != null) pathHandler.GetPathNode(endNode).flag2 = true;
|
||||
|
||||
// Zero out the properties on the start node
|
||||
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);
|
||||
|
||||
// Check if the start node is the target and complete the path if that is the case
|
||||
CompletePathIfStartIsValidTarget();
|
||||
if (CompleteState == PathCompleteState.Complete) return;
|
||||
|
||||
// Open the start node and puts its neighbours in the open list
|
||||
startNode.Open(this, startRNode, pathHandler);
|
||||
|
||||
searchedNodes++;
|
||||
|
||||
partialBestTarget = startRNode;
|
||||
|
||||
// Any nodes left to search?
|
||||
if (pathHandler.heap.isEmpty) {
|
||||
if (calculatePartial) {
|
||||
CompletePartial(partialBestTarget);
|
||||
} else {
|
||||
FailWithError("The start node either had no neighbours, or no neighbours that the path could traverse");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Pop the first node off the open list
|
||||
currentR = pathHandler.heap.Remove();
|
||||
}
|
||||
|
||||
protected override void Cleanup () {
|
||||
// TODO: Set flag1 = false as well?
|
||||
if (startNode != null) {
|
||||
var pathStartNode = pathHandler.GetPathNode(startNode);
|
||||
pathStartNode.flag1 = false;
|
||||
pathStartNode.flag2 = false;
|
||||
}
|
||||
|
||||
if (endNode != null) {
|
||||
var pathEndNode = pathHandler.GetPathNode(endNode);
|
||||
pathEndNode.flag1 = false;
|
||||
pathEndNode.flag2 = false;
|
||||
}
|
||||
|
||||
#if !ASTAR_NO_GRID_GRAPH
|
||||
// Set flag1 and flag2 to false on all nodes we set it to true on
|
||||
// at the start of the path call. Otherwise this state
|
||||
// will leak to other path calculations and cause all
|
||||
// kinds of havoc.
|
||||
// flag2 is also set because the end node could have changed
|
||||
// and thus the flag2 which is set to false above might not set
|
||||
// it on the correct node
|
||||
if (gridSpecialCaseNode != null) {
|
||||
var pathNode = pathHandler.GetPathNode(gridSpecialCaseNode);
|
||||
pathNode.flag1 = false;
|
||||
pathNode.flag2 = false;
|
||||
SetFlagOnSurroundingGridNodes(gridSpecialCaseNode, 1, false);
|
||||
SetFlagOnSurroundingGridNodes(gridSpecialCaseNode, 2, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CompletePartial (PathNode node) {
|
||||
// We will change the end node, so we have to clean up the previous end node to not
|
||||
// leave it with stale data.
|
||||
var pathEndNode = pathHandler.GetPathNode(endNode);
|
||||
pathEndNode.flag1 = false;
|
||||
pathEndNode.flag2 = false;
|
||||
|
||||
CompleteState = PathCompleteState.Partial;
|
||||
endNode = node.node;
|
||||
endPoint = endNode.ClosestPointOnNode(originalEndPoint);
|
||||
cost = pathEndNode.G;
|
||||
Trace(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the path using the specified target node.
|
||||
/// This method assumes that the node is a target node of the path
|
||||
/// not just any random node.
|
||||
/// </summary>
|
||||
void CompleteWith (GraphNode node) {
|
||||
#if !ASTAR_NO_GRID_GRAPH
|
||||
if (endNode == node) {
|
||||
// Common case, no grid graph special case has been applied
|
||||
// Nothing to do
|
||||
} else {
|
||||
// See EndPointGridGraphSpecialCase()
|
||||
var gridNode = node as GridNode;
|
||||
if (gridNode == null) {
|
||||
throw new System.Exception("Some path is not cleaning up the flag1 field. This is a bug.");
|
||||
}
|
||||
|
||||
// The grid graph special case has been applied
|
||||
// The closest point on the node is not yet known
|
||||
// so we need to calculate it
|
||||
endPoint = gridNode.ClosestPointOnNode(originalEndPoint);
|
||||
// This is now our end node
|
||||
// We didn't know it before, but apparently it was optimal
|
||||
// to move to this node
|
||||
endNode = node;
|
||||
}
|
||||
#else
|
||||
// This should always be true unless
|
||||
// the grid graph special case has been applied
|
||||
// which can only happen if grid graphs have not
|
||||
// been stripped out with ASTAR_NO_GRID_GRAPH
|
||||
node.MustBeEqual(endNode);
|
||||
#endif
|
||||
cost = pathHandler.GetPathNode(endNode).G;
|
||||
// Mark the path as completed
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the path until completed or until the time has passed targetTick.
|
||||
/// Usually a check is only done every 500 nodes if the time has passed targetTick.
|
||||
/// Time/Ticks are got from System.DateTime.UtcNow.Ticks.
|
||||
///
|
||||
/// Basic outline of what the function does for the standard path (Pathfinding.ABPath).
|
||||
/// <code>
|
||||
/// while the end has not been found and no error has occurred
|
||||
/// check if we have reached the end
|
||||
/// if so, exit and return the path
|
||||
///
|
||||
/// open the current node, i.e loop through its neighbours, mark them as visited and put them on a heap
|
||||
///
|
||||
/// check if there are still nodes left to process (or have we searched the whole graph)
|
||||
/// if there are none, flag error and exit
|
||||
///
|
||||
/// pop the next node of the heap and set it as current
|
||||
///
|
||||
/// check if the function has exceeded the time limit
|
||||
/// if so, return and wait for the function to get called again
|
||||
/// </code>
|
||||
/// </summary>
|
||||
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++;
|
||||
|
||||
// Close the current node, if the current node is the target node then the path is finished
|
||||
if (currentR.flag1) {
|
||||
// We found a target point
|
||||
// Mark that node as the end point
|
||||
CompleteWith(currentR.node);
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentR.H < partialBestTarget.H) {
|
||||
partialBestTarget = currentR;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (calculatePartial && partialBestTarget != null) {
|
||||
CompletePartial(partialBestTarget);
|
||||
} else {
|
||||
FailWithError("Searched all reachable nodes, but could not find target. This can happen if you have nodes with a different tag blocking the way to the goal. You can enable path.calculatePartial to handle that case workaround (though this comes with a performance cost).");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 (System.DateTime.UtcNow.Ticks >= targetTick) {
|
||||
// Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
|
||||
return;
|
||||
}
|
||||
counter = 0;
|
||||
|
||||
// Mostly for development
|
||||
if (searchedNodes > 1000000) {
|
||||
throw new System.Exception("Probable infinite loop. Over 1,000,000 nodes searched");
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
if (CompleteState == PathCompleteState.Complete) {
|
||||
Trace(currentR);
|
||||
} else if (calculatePartial && partialBestTarget != null) {
|
||||
CompletePartial(partialBestTarget);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns a debug string for this path.</summary>
|
||||
protected override string DebugString (PathLog logMode) {
|
||||
if (logMode == PathLog.None || (!error && logMode == PathLog.OnlyErrors)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var text = new System.Text.StringBuilder();
|
||||
|
||||
DebugStringPrefix(logMode, text);
|
||||
|
||||
if (!error && logMode == PathLog.Heavy) {
|
||||
if (hasEndPoint && endNode != null) {
|
||||
PathNode nodeR = pathHandler.GetPathNode(endNode);
|
||||
text.Append("\nEnd Node\n G: ");
|
||||
text.Append(nodeR.G);
|
||||
text.Append("\n H: ");
|
||||
text.Append(nodeR.H);
|
||||
text.Append("\n F: ");
|
||||
text.Append(nodeR.F);
|
||||
text.Append("\n Point: ");
|
||||
text.Append(((Vector3)endPoint).ToString());
|
||||
text.Append("\n Graph: ");
|
||||
text.Append(endNode.GraphIndex);
|
||||
}
|
||||
|
||||
text.Append("\nStart Node");
|
||||
text.Append("\n Point: ");
|
||||
text.Append(((Vector3)startPoint).ToString());
|
||||
text.Append("\n Graph: ");
|
||||
if (startNode != null) text.Append(startNode.GraphIndex);
|
||||
else text.Append("< null startNode >");
|
||||
}
|
||||
|
||||
DebugStringSuffix(logMode, text);
|
||||
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
/// <summary>\cond INTERNAL</summary>
|
||||
/// <summary>
|
||||
/// Returns in which direction to move from a point on the path.
|
||||
/// A simple and quite slow (well, compared to more optimized algorithms) algorithm first finds the closest path segment (from <see cref="vectorPath)"/> and then returns
|
||||
/// the direction to the next point from there. The direction is not normalized.
|
||||
/// Returns: Direction to move from a point, returns Vector3.zero if <see cref="vectorPath"/> is null or has a length of 0
|
||||
/// Deprecated:
|
||||
/// </summary>
|
||||
[System.Obsolete()]
|
||||
public Vector3 GetMovementVector (Vector3 point) {
|
||||
if (vectorPath == null || vectorPath.Count == 0) {
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
if (vectorPath.Count == 1) {
|
||||
return vectorPath[0]-point;
|
||||
}
|
||||
|
||||
float minDist = float.PositiveInfinity;//Mathf.Infinity;
|
||||
int minSegment = 0;
|
||||
|
||||
for (int i = 0; i < vectorPath.Count-1; i++) {
|
||||
Vector3 closest = VectorMath.ClosestPointOnSegment(vectorPath[i], vectorPath[i+1], point);
|
||||
float dist = (closest-point).sqrMagnitude;
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
minSegment = i;
|
||||
}
|
||||
}
|
||||
|
||||
return vectorPath[minSegment+1]-point;
|
||||
}
|
||||
|
||||
/// <summary>\endcond</summary>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7833eca4f9a3c4a119943a3ef09cdfc9
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
@@ -0,0 +1,233 @@
|
||||
//#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c10cdc7060e34b84aae81aeb516a4f1
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
@@ -0,0 +1,59 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Returns a path heading away from a specified point to avoid.
|
||||
/// The search will terminate when G \> length (passed to the constructor) + FleePath.spread.
|
||||
///
|
||||
/// Can be used to make an AI to flee from an enemy (cannot guarantee that it will not be forced into corners though :D )
|
||||
/// <code>
|
||||
///
|
||||
/// // Call a FleePath call like this, assumes that a Seeker is attached to the GameObject
|
||||
/// Vector3 thePointToFleeFrom = Vector3.zero;
|
||||
///
|
||||
/// // The path will be returned when the path is over a specified length (or more accurately when the traversal cost is greater than a specified value).
|
||||
/// // A score of 1000 is approximately equal to the cost of moving one world unit.
|
||||
/// int theGScoreToStopAt = 10000;
|
||||
///
|
||||
/// // Create a path object
|
||||
/// FleePath path = FleePath.Construct (transform.position, thePointToFleeFrom, theGScoreToStopAt);
|
||||
/// // This is how strongly it will try to flee, if you set it to 0 it will behave like a RandomPath
|
||||
/// path.aimStrength = 1;
|
||||
/// // Determines the variation in path length that is allowed
|
||||
/// path.spread = 4000;
|
||||
///
|
||||
/// // Get the Seeker component which must be attached to this GameObject
|
||||
/// Seeker seeker = GetComponent<Seeker>();
|
||||
///
|
||||
/// // Start the path and return the result to MyCompleteFunction (which is a function you have to define, the name can of course be changed)
|
||||
/// seeker.StartPath(path, MyCompleteFunction);
|
||||
///
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class FleePath : RandomPath {
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// Do not use this. Instead use the static Construct method which can handle path pooling.
|
||||
/// </summary>
|
||||
public FleePath () {}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new FleePath.
|
||||
/// The FleePath will be taken from a pool.
|
||||
/// </summary>
|
||||
public static FleePath Construct (Vector3 start, Vector3 avoid, int searchLength, OnPathDelegate callback = null) {
|
||||
var p = PathPool.GetPath<FleePath>();
|
||||
|
||||
p.Setup(start, avoid, searchLength, callback);
|
||||
return p;
|
||||
}
|
||||
|
||||
protected void Setup (Vector3 start, Vector3 avoid, int searchLength, OnPathDelegate callback) {
|
||||
Setup(start, searchLength, callback);
|
||||
// Set the aim to a point in the opposite direction from the point we to avoid
|
||||
// TODO: Why is this multiplication by 10 here?
|
||||
// Might want to remove it
|
||||
aim = start - (avoid-start)*10;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7dbd923c08b724d578abb732392cebdf
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Calculates paths from everywhere to a single point.
|
||||
/// This path is a bit special, because it does not do anything useful by itself. What it does is that it calculates paths to all nodes it can reach, it floods the graph.
|
||||
/// This data will remain stored in the path. Then you can calculate a FloodPathTracer path. That path will trace the path from its starting point all the way to where this path started.
|
||||
/// A FloodPathTracer search is extremely fast to calculate compared to a normal path request.
|
||||
///
|
||||
/// It is very useful in for example tower defence games, where all your AIs will walk to the same point but from different places, and you do not update the graph or change the target point very often.
|
||||
///
|
||||
/// Usage:
|
||||
/// - At start, you calculate ONE FloodPath and save the reference (it will be needed later).
|
||||
/// - Then when a unit is spawned or needs its path recalculated, start a FloodPathTracer path from the unit's position.
|
||||
/// It will then find the shortest path to the point specified when you calculated the FloodPath extremely quickly.
|
||||
/// - If you update the graph (for example place a tower in a TD game) or need to change the target point, you calculate a new FloodPath and make all AIs calculate new FloodPathTracer paths.
|
||||
///
|
||||
/// Note: Since a FloodPathTracer path only uses precalculated information, it will always use the same penalties/tags as the FloodPath it references.
|
||||
/// If you want to use different penalties/tags, you will have to calculate a new FloodPath.
|
||||
///
|
||||
/// Here follows some example code of the above list of steps:
|
||||
/// <code>
|
||||
/// public static FloodPath fpath;
|
||||
///
|
||||
/// public void Start () {
|
||||
/// fpath = FloodPath.Construct (someTargetPosition, null);
|
||||
/// AstarPath.StartPath (fpath);
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// When searching for a new path to someTargetPosition from let's say transform.position, you do
|
||||
/// <code>
|
||||
/// FloodPathTracer fpathTrace = FloodPathTracer.Construct (transform.position,fpath,null);
|
||||
/// seeker.StartPath (fpathTrace,OnPathComplete);
|
||||
/// </code>
|
||||
/// Where OnPathComplete is your callback function.
|
||||
///
|
||||
/// Another thing to note is that if you are using an NNConstraint on the FloodPathTracer, they must always inherit from <see cref="FloodPathConstraint"/>.
|
||||
/// The easiest is to just modify the instance of FloodPathConstraint which is created as the default one.
|
||||
///
|
||||
/// \section flood-path-builtin-movement Integration with the built-in movement scripts
|
||||
/// The built-in movement scripts cannot calculate a FloodPathTracer path themselves, but you can use the SetPath method to assign such a path to them:
|
||||
/// <code>
|
||||
/// var ai = GetComponent<IAstarAI>();
|
||||
/// // Disable the agent's own path recalculation code
|
||||
/// ai.canSearch = false;
|
||||
/// ai.SetPath(FloodPathTracer.Construct(ai.position, floodPath));
|
||||
/// </code>
|
||||
///
|
||||
/// [Open online documentation to see images]
|
||||
/// </summary>
|
||||
public class FloodPath : Path {
|
||||
public Vector3 originalStartPoint;
|
||||
public Vector3 startPoint;
|
||||
public GraphNode startNode;
|
||||
|
||||
/// <summary>
|
||||
/// If false, will not save any information.
|
||||
/// Used by some internal parts of the system which doesn't need it.
|
||||
/// </summary>
|
||||
public bool saveParents = true;
|
||||
|
||||
protected Dictionary<GraphNode, GraphNode> parents;
|
||||
|
||||
public override bool FloodingPath {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPathTo (GraphNode node) {
|
||||
return parents != null && parents.ContainsKey(node);
|
||||
}
|
||||
|
||||
public GraphNode GetParent (GraphNode node) {
|
||||
return parents[node];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// Do not use this. Instead use the static Construct method which can handle path pooling.
|
||||
/// </summary>
|
||||
public FloodPath () {}
|
||||
|
||||
public static FloodPath Construct (Vector3 start, OnPathDelegate callback = null) {
|
||||
var p = PathPool.GetPath<FloodPath>();
|
||||
|
||||
p.Setup(start, callback);
|
||||
return p;
|
||||
}
|
||||
|
||||
public static FloodPath Construct (GraphNode start, OnPathDelegate callback = null) {
|
||||
if (start == null) throw new ArgumentNullException("start");
|
||||
|
||||
var p = PathPool.GetPath<FloodPath>();
|
||||
p.Setup(start, callback);
|
||||
return p;
|
||||
}
|
||||
|
||||
protected void Setup (Vector3 start, OnPathDelegate callback) {
|
||||
this.callback = callback;
|
||||
originalStartPoint = start;
|
||||
startPoint = start;
|
||||
heuristic = Heuristic.None;
|
||||
}
|
||||
|
||||
protected void Setup (GraphNode start, OnPathDelegate callback) {
|
||||
this.callback = callback;
|
||||
originalStartPoint = (Vector3)start.position;
|
||||
startNode = start;
|
||||
startPoint = (Vector3)start.position;
|
||||
heuristic = Heuristic.None;
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
originalStartPoint = Vector3.zero;
|
||||
startPoint = Vector3.zero;
|
||||
startNode = null;
|
||||
/// <summary>TODO: Avoid this allocation</summary>
|
||||
parents = new Dictionary<GraphNode, GraphNode>();
|
||||
saveParents = true;
|
||||
}
|
||||
|
||||
protected override void Prepare () {
|
||||
if (startNode == null) {
|
||||
//Initialize the NNConstraint
|
||||
nnConstraint.tags = enabledTags;
|
||||
var startNNInfo = AstarPath.active.GetNearest(originalStartPoint, nnConstraint);
|
||||
|
||||
startPoint = startNNInfo.position;
|
||||
startNode = startNNInfo.node;
|
||||
} else if (startNode.Destroyed) {
|
||||
FailWithError("Start node has been destroyed");
|
||||
return;
|
||||
} else {
|
||||
startPoint = (Vector3)startNode.position;
|
||||
}
|
||||
|
||||
#if ASTARDEBUG
|
||||
Debug.DrawLine((Vector3)startNode.position, startPoint, Color.blue);
|
||||
#endif
|
||||
|
||||
if (startNode == null) {
|
||||
FailWithError("Couldn't find a close node to the start point");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanTraverse(startNode)) {
|
||||
FailWithError("The node closest to the start point could not be traversed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
parents[startNode] = null;
|
||||
|
||||
startNode.Open(this, startRNode, pathHandler);
|
||||
|
||||
searchedNodes++;
|
||||
|
||||
// Any nodes left to search?
|
||||
if (pathHandler.heap.isEmpty) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
return;
|
||||
}
|
||||
|
||||
currentR = pathHandler.heap.Remove();
|
||||
}
|
||||
|
||||
/// <summary>Opens nodes until there are none left to search (or until the max time limit has been exceeded)</summary>
|
||||
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++;
|
||||
|
||||
//Loop through all walkable neighbours of the node and add them to the open list.
|
||||
currentR.node.Open(this, currentR, pathHandler);
|
||||
|
||||
// Insert into internal search tree
|
||||
if (saveParents) parents[currentR.node] = currentR.parent.node;
|
||||
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13037e083edb4439e8039fcc998ef099
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
@@ -0,0 +1,109 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Restrict suitable nodes by if they have been searched by a FloodPath.
|
||||
///
|
||||
/// Suitable nodes are in addition to the basic contraints, only the nodes which return true on a FloodPath.HasPathTo (node) call.
|
||||
/// See: Pathfinding.FloodPath
|
||||
/// See: Pathfinding.FloodPathTracer
|
||||
/// </summary>
|
||||
public class FloodPathConstraint : NNConstraint {
|
||||
readonly FloodPath path;
|
||||
|
||||
public FloodPathConstraint (FloodPath path) {
|
||||
if (path == null) { Debug.LogWarning("FloodPathConstraint should not be used with a NULL path"); }
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public override bool Suitable (GraphNode node) {
|
||||
return base.Suitable(node) && path.HasPathTo(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traces a path created with the Pathfinding.FloodPath.
|
||||
///
|
||||
/// See Pathfinding.FloodPath for examples on how to use this path type
|
||||
///
|
||||
/// [Open online documentation to see images]
|
||||
/// </summary>
|
||||
public class FloodPathTracer : ABPath {
|
||||
/// <summary>Reference to the FloodPath which searched the path originally</summary>
|
||||
protected FloodPath flood;
|
||||
|
||||
protected override bool hasEndPoint {
|
||||
get {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// Do not use this. Instead use the static Construct method which can handle path pooling.
|
||||
/// </summary>
|
||||
public FloodPathTracer () {}
|
||||
|
||||
public static FloodPathTracer Construct (Vector3 start, FloodPath flood, OnPathDelegate callback = null) {
|
||||
var p = PathPool.GetPath<FloodPathTracer>();
|
||||
|
||||
p.Setup(start, flood, callback);
|
||||
return p;
|
||||
}
|
||||
|
||||
protected void Setup (Vector3 start, FloodPath flood, OnPathDelegate callback) {
|
||||
this.flood = flood;
|
||||
|
||||
if (flood == null || flood.PipelineState < PathState.Returning) {
|
||||
throw new System.ArgumentException("You must supply a calculated FloodPath to the 'flood' argument");
|
||||
}
|
||||
|
||||
base.Setup(start, flood.originalStartPoint, callback);
|
||||
nnConstraint = new FloodPathConstraint(flood);
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
flood = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the path.
|
||||
/// Traces the path from the start node.
|
||||
/// </summary>
|
||||
protected override void Initialize () {
|
||||
if (startNode != null && flood.HasPathTo(startNode)) {
|
||||
Trace(startNode);
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
} else {
|
||||
FailWithError("Could not find valid start node");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CalculateStep (long targetTick) {
|
||||
if (CompleteState != PathCompleteState.Complete) throw new System.Exception("Something went wrong. At this point the path should be completed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traces the calculated path from the start node to the end.
|
||||
/// This will build an array (<see cref="path)"/> of the nodes this path will pass through and also set the <see cref="vectorPath"/> array to the <see cref="path"/> arrays positions.
|
||||
/// This implementation will use the <see cref="flood"/> (FloodPath) to trace the path from precalculated data.
|
||||
/// </summary>
|
||||
public void Trace (GraphNode from) {
|
||||
GraphNode c = from;
|
||||
int count = 0;
|
||||
|
||||
while (c != null) {
|
||||
path.Add(c);
|
||||
vectorPath.Add((Vector3)c.position);
|
||||
c = flood.GetParent(c);
|
||||
|
||||
count++;
|
||||
if (count > 1024) {
|
||||
Debug.LogWarning("Inifinity loop? >1024 node path. Remove this message if you really have that long paths (FloodPathTracer.cs, Trace function)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5753a5fb7a0047bca02db8138a07343
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
@@ -0,0 +1,696 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// A path which searches from one point to a number of different targets in one search or from a number of different start points to a single target.
|
||||
///
|
||||
/// This is faster than searching with an ABPath for each target if pathsForAll is true.
|
||||
/// This path type can be used for example when you want an agent to find the closest target of a few different options.
|
||||
///
|
||||
/// When pathsForAll is true, it will calculate a path to each target point, but it can share a lot of calculations for the different paths so
|
||||
/// it is faster than requesting them separately.
|
||||
///
|
||||
/// When pathsForAll is false, it will perform a search using the heuristic set to None and stop as soon as it finds the first target.
|
||||
/// This may be faster or slower than requesting each path separately.
|
||||
/// It will run a Dijkstra search where it searches all nodes around the start point until the closest target is found.
|
||||
/// Note that this is usually faster if some target points are very close to the start point and some are very far away, but
|
||||
/// it can be slower if all target points are relatively far away because then it will have to search a much larger
|
||||
/// region since it will not use any heuristics.
|
||||
///
|
||||
/// See: Seeker.StartMultiTargetPath
|
||||
/// See: MultiTargetPathExample.cs (view in online documentation for working links) "Example of how to use multi-target-paths"
|
||||
///
|
||||
/// Version: Since 3.7.1 the vectorPath and path fields are always set to the shortest path even when pathsForAll is true.
|
||||
/// </summary>
|
||||
public class MultiTargetPath : ABPath {
|
||||
/// <summary>Callbacks to call for each individual path</summary>
|
||||
public OnPathDelegate[] callbacks;
|
||||
|
||||
/// <summary>Nearest nodes to the <see cref="targetPoints"/></summary>
|
||||
public GraphNode[] targetNodes;
|
||||
|
||||
/// <summary>Number of target nodes left to find</summary>
|
||||
protected int targetNodeCount;
|
||||
|
||||
/// <summary>Indicates if the target has been found. Also true if the target cannot be reached (is in another area)</summary>
|
||||
public bool[] targetsFound;
|
||||
|
||||
/// <summary>Target points specified when creating the path. These are snapped to the nearest nodes</summary>
|
||||
public Vector3[] targetPoints;
|
||||
|
||||
/// <summary>Target points specified when creating the path. These are not snapped to the nearest nodes</summary>
|
||||
public Vector3[] originalTargetPoints;
|
||||
|
||||
/// <summary>Stores all vector paths to the targets. Elements are null if no path was found</summary>
|
||||
public List<Vector3>[] vectorPaths;
|
||||
|
||||
/// <summary>Stores all paths to the targets. Elements are null if no path was found</summary>
|
||||
public List<GraphNode>[] nodePaths;
|
||||
|
||||
/// <summary>If true, a path to all targets will be returned, otherwise just the one to the closest one.</summary>
|
||||
public bool pathsForAll = true;
|
||||
|
||||
/// <summary>The closest target index (if any target was found)</summary>
|
||||
public int chosenTarget = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Current target for Sequential <see cref="heuristicMode"/>.
|
||||
/// Refers to an item in the targetPoints array
|
||||
/// </summary>
|
||||
int sequentialTarget;
|
||||
|
||||
/// <summary>
|
||||
/// How to calculate the heuristic.
|
||||
/// The <see cref="<see cref="hTarget"/> heuristic target"/> can be calculated in different ways,
|
||||
/// by taking the Average position of all targets, or taking the mid point of them (i.e center of the AABB encapsulating all targets).
|
||||
///
|
||||
/// The one which works best seems to be Sequential, it sets <see cref="hTarget"/> to the target furthest away, and when that target is found, it moves on to the next one.
|
||||
/// Some modes have the option to be 'moving' (e.g 'MovingAverage'), that means that it is updated every time a target is found.
|
||||
/// The H score is calculated according to AstarPath.heuristic
|
||||
///
|
||||
/// Note: If pathsForAll is false then this option is ignored and it is always treated as being set to None
|
||||
/// </summary>
|
||||
public HeuristicMode heuristicMode = HeuristicMode.Sequential;
|
||||
|
||||
public enum HeuristicMode {
|
||||
None,
|
||||
Average,
|
||||
MovingAverage,
|
||||
Midpoint,
|
||||
MovingMidpoint,
|
||||
Sequential
|
||||
}
|
||||
|
||||
/// <summary>False if the path goes from one point to multiple targets. True if it goes from multiple start points to one target point</summary>
|
||||
public bool inverted { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// Do not use this. Instead use the static Construct method which can handle path pooling.
|
||||
/// </summary>
|
||||
public MultiTargetPath () {}
|
||||
|
||||
public static MultiTargetPath Construct (Vector3[] startPoints, Vector3 target, OnPathDelegate[] callbackDelegates, OnPathDelegate callback = null) {
|
||||
MultiTargetPath p = Construct(target, startPoints, callbackDelegates, callback);
|
||||
|
||||
p.inverted = true;
|
||||
return p;
|
||||
}
|
||||
|
||||
public static MultiTargetPath Construct (Vector3 start, Vector3[] targets, OnPathDelegate[] callbackDelegates, OnPathDelegate callback = null) {
|
||||
var p = PathPool.GetPath<MultiTargetPath>();
|
||||
|
||||
p.Setup(start, targets, callbackDelegates, callback);
|
||||
return p;
|
||||
}
|
||||
|
||||
protected void Setup (Vector3 start, Vector3[] targets, OnPathDelegate[] callbackDelegates, OnPathDelegate callback) {
|
||||
inverted = false;
|
||||
this.callback = callback;
|
||||
callbacks = callbackDelegates;
|
||||
if (callbacks != null && callbacks.Length != targets.Length) throw new System.ArgumentException("The targets array must have the same length as the callbackDelegates array");
|
||||
targetPoints = targets;
|
||||
|
||||
originalStartPoint = start;
|
||||
|
||||
startPoint = start;
|
||||
startIntPoint = (Int3)start;
|
||||
|
||||
if (targets.Length == 0) {
|
||||
FailWithError("No targets were assigned to the MultiTargetPath");
|
||||
return;
|
||||
}
|
||||
|
||||
endPoint = targets[0];
|
||||
|
||||
originalTargetPoints = new Vector3[targetPoints.Length];
|
||||
for (int i = 0; i < targetPoints.Length; i++) {
|
||||
originalTargetPoints[i] = targetPoints[i];
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
pathsForAll = true;
|
||||
chosenTarget = -1;
|
||||
sequentialTarget = 0;
|
||||
inverted = true;
|
||||
heuristicMode = HeuristicMode.Sequential;
|
||||
}
|
||||
|
||||
protected override void OnEnterPool () {
|
||||
if (vectorPaths != null)
|
||||
for (int i = 0; i < vectorPaths.Length; i++)
|
||||
if (vectorPaths[i] != null) Util.ListPool<Vector3>.Release(vectorPaths[i]);
|
||||
|
||||
vectorPaths = null;
|
||||
vectorPath = null;
|
||||
|
||||
if (nodePaths != null)
|
||||
for (int i = 0; i < nodePaths.Length; i++)
|
||||
if (nodePaths[i] != null) Util.ListPool<GraphNode>.Release(nodePaths[i]);
|
||||
|
||||
nodePaths = null;
|
||||
path = null;
|
||||
callbacks = null;
|
||||
targetNodes = null;
|
||||
targetsFound = null;
|
||||
targetPoints = null;
|
||||
originalTargetPoints = null;
|
||||
|
||||
base.OnEnterPool();
|
||||
}
|
||||
|
||||
/// <summary>Set chosenTarget to the index of the shortest path</summary>
|
||||
void ChooseShortestPath () {
|
||||
//
|
||||
// When pathsForAll is false there will only be one non-null path
|
||||
chosenTarget = -1;
|
||||
if (nodePaths != null) {
|
||||
uint bestG = int.MaxValue;
|
||||
for (int i = 0; i < nodePaths.Length; i++) {
|
||||
var currentPath = nodePaths[i];
|
||||
if (currentPath != null) {
|
||||
// Get the G score of the first or the last node in the path
|
||||
// depending on if the paths are reversed or not
|
||||
var g = pathHandler.GetPathNode(currentPath[inverted ? 0 : currentPath.Count-1]).G;
|
||||
if (chosenTarget == -1 || g < bestG) {
|
||||
chosenTarget = i;
|
||||
bestG = g;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetPathParametersForReturn (int target) {
|
||||
path = nodePaths[target];
|
||||
vectorPath = vectorPaths[target];
|
||||
|
||||
if (inverted) {
|
||||
startNode = targetNodes[target];
|
||||
startPoint = targetPoints[target];
|
||||
originalStartPoint = originalTargetPoints[target];
|
||||
} else {
|
||||
endNode = targetNodes[target];
|
||||
endPoint = targetPoints[target];
|
||||
originalEndPoint = originalTargetPoints[target];
|
||||
}
|
||||
cost = path != null? pathHandler.GetPathNode(endNode).G : 0;
|
||||
}
|
||||
|
||||
protected override void ReturnPath () {
|
||||
if (error) {
|
||||
// Call all callbacks
|
||||
if (callbacks != null) {
|
||||
for (int i = 0; i < callbacks.Length; i++)
|
||||
if (callbacks[i] != null) callbacks[i] (this);
|
||||
}
|
||||
|
||||
if (callback != null) callback(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool anySucceded = false;
|
||||
|
||||
// Set the end point to the start point
|
||||
// since the path is reversed
|
||||
// (the start point will be set individually for each path)
|
||||
if (inverted) {
|
||||
endPoint = startPoint;
|
||||
endNode = startNode;
|
||||
originalEndPoint = originalStartPoint;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodePaths.Length; i++) {
|
||||
if (nodePaths[i] != null) {
|
||||
// Note that we use the lowercase 'completeState' here.
|
||||
// The property (CompleteState) will ensure that the complete state is never
|
||||
// changed away from the error state but in this case we don't want that behaviour.
|
||||
completeState = PathCompleteState.Complete;
|
||||
anySucceded = true;
|
||||
} else {
|
||||
completeState = PathCompleteState.Error;
|
||||
}
|
||||
|
||||
if (callbacks != null && callbacks[i] != null) {
|
||||
SetPathParametersForReturn(i);
|
||||
callbacks[i] (this);
|
||||
|
||||
// In case a modifier changed the vectorPath, update the array of all vectorPaths
|
||||
vectorPaths[i] = vectorPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (anySucceded) {
|
||||
completeState = PathCompleteState.Complete;
|
||||
SetPathParametersForReturn(chosenTarget);
|
||||
} else {
|
||||
completeState = PathCompleteState.Error;
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void FoundTarget (PathNode nodeR, int i) {
|
||||
nodeR.flag1 = false; // Reset bit 8
|
||||
|
||||
Trace(nodeR);
|
||||
vectorPaths[i] = vectorPath;
|
||||
nodePaths[i] = path;
|
||||
vectorPath = Util.ListPool<Vector3>.Claim();
|
||||
path = Util.ListPool<GraphNode>.Claim();
|
||||
|
||||
targetsFound[i] = true;
|
||||
|
||||
targetNodeCount--;
|
||||
|
||||
// Since we have found one target
|
||||
// and the heuristic is always set to None when
|
||||
// pathsForAll is false, we will have found the shortest path
|
||||
if (!pathsForAll) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
targetNodeCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// If there are no more targets to find, return here and avoid calculating a new hTarget
|
||||
if (targetNodeCount <= 0) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
return;
|
||||
}
|
||||
|
||||
RecalculateHTarget(false);
|
||||
}
|
||||
|
||||
protected void RebuildOpenList () {
|
||||
BinaryHeap heap = pathHandler.heap;
|
||||
|
||||
for (int j = 0; j < heap.numberOfItems; j++) {
|
||||
PathNode nodeR = heap.GetNode(j);
|
||||
nodeR.H = CalculateHScore(nodeR.node);
|
||||
heap.SetF(j, nodeR.F);
|
||||
}
|
||||
|
||||
pathHandler.heap.Rebuild();
|
||||
}
|
||||
|
||||
protected override void Prepare () {
|
||||
nnConstraint.tags = enabledTags;
|
||||
var startNNInfo = AstarPath.active.GetNearest(startPoint, nnConstraint);
|
||||
startNode = startNNInfo.node;
|
||||
|
||||
if (startNode == null) {
|
||||
FailWithError("Could not find start node for multi target path");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanTraverse(startNode)) {
|
||||
FailWithError("The node closest to the start point could not be traversed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell the NNConstraint which node was found as the start node if it is a PathNNConstraint and not a normal NNConstraint
|
||||
var pathNNConstraint = nnConstraint as PathNNConstraint;
|
||||
if (pathNNConstraint != null) {
|
||||
pathNNConstraint.SetStart(startNNInfo.node);
|
||||
}
|
||||
|
||||
vectorPaths = new List<Vector3>[targetPoints.Length];
|
||||
nodePaths = new List<GraphNode>[targetPoints.Length];
|
||||
targetNodes = new GraphNode[targetPoints.Length];
|
||||
targetsFound = new bool[targetPoints.Length];
|
||||
targetNodeCount = targetPoints.Length;
|
||||
|
||||
bool anyWalkable = false;
|
||||
bool anySameArea = false;
|
||||
bool anyNotNull = false;
|
||||
|
||||
for (int i = 0; i < targetPoints.Length; i++) {
|
||||
var endNNInfo = AstarPath.active.GetNearest(targetPoints[i], nnConstraint);
|
||||
|
||||
targetNodes[i] = endNNInfo.node;
|
||||
|
||||
targetPoints[i] = endNNInfo.position;
|
||||
if (targetNodes[i] != null) {
|
||||
anyNotNull = true;
|
||||
endNode = targetNodes[i];
|
||||
}
|
||||
|
||||
bool notReachable = false;
|
||||
|
||||
if (endNNInfo.node != null && CanTraverse(endNNInfo.node)) {
|
||||
anyWalkable = true;
|
||||
} else {
|
||||
notReachable = true;
|
||||
}
|
||||
|
||||
if (endNNInfo.node != null && endNNInfo.node.Area == startNode.Area) {
|
||||
anySameArea = true;
|
||||
} else {
|
||||
notReachable = true;
|
||||
}
|
||||
|
||||
if (notReachable) {
|
||||
// Signal that the pathfinder should not look for this node because we have already found it
|
||||
targetsFound[i] = true;
|
||||
targetNodeCount--;
|
||||
}
|
||||
}
|
||||
|
||||
startPoint = startNNInfo.position;
|
||||
|
||||
startIntPoint = (Int3)startPoint;
|
||||
|
||||
if (!anyNotNull) {
|
||||
FailWithError("Couldn't find a valid node close to the any of the end points");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!anyWalkable) {
|
||||
FailWithError("No target nodes could be traversed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!anySameArea) {
|
||||
FailWithError("There are no valid paths to the targets");
|
||||
return;
|
||||
}
|
||||
|
||||
RecalculateHTarget(true);
|
||||
}
|
||||
|
||||
void RecalculateHTarget (bool firstTime) {
|
||||
// When pathsForAll is false
|
||||
// then no heuristic should be used
|
||||
if (!pathsForAll) {
|
||||
heuristic = Heuristic.None;
|
||||
heuristicScale = 0.0F;
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate a new hTarget and rebuild the open list if necessary
|
||||
// Rebuilding the open list is necessary when the H score for nodes changes
|
||||
switch (heuristicMode) {
|
||||
case HeuristicMode.None:
|
||||
heuristic = Heuristic.None;
|
||||
heuristicScale = 0F;
|
||||
break;
|
||||
case HeuristicMode.Average:
|
||||
if (!firstTime) return;
|
||||
|
||||
// No break
|
||||
// The first time the implementation
|
||||
// for Average and MovingAverage is identical
|
||||
// so we just use fallthrough
|
||||
goto case HeuristicMode.MovingAverage;
|
||||
case HeuristicMode.MovingAverage:
|
||||
|
||||
// Pick the average position of all nodes that have not been found yet
|
||||
var avg = Vector3.zero;
|
||||
int count = 0;
|
||||
for (int j = 0; j < targetPoints.Length; j++) {
|
||||
if (!targetsFound[j]) {
|
||||
avg += (Vector3)targetNodes[j].position;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Should use asserts, but they were first added in Unity 5.1
|
||||
// so I cannot use them because I want to keep compatibility with 4.6
|
||||
// (as of 2015)
|
||||
if (count == 0) throw new System.Exception("Should not happen");
|
||||
|
||||
avg /= count;
|
||||
hTarget = (Int3)avg;
|
||||
break;
|
||||
case HeuristicMode.Midpoint:
|
||||
if (!firstTime) return;
|
||||
|
||||
// No break
|
||||
// The first time the implementation
|
||||
// for Midpoint and MovingMidpoint is identical
|
||||
// so we just use fallthrough
|
||||
goto case HeuristicMode.MovingMidpoint;
|
||||
case HeuristicMode.MovingMidpoint:
|
||||
|
||||
Vector3 min = Vector3.zero;
|
||||
Vector3 max = Vector3.zero;
|
||||
bool set = false;
|
||||
|
||||
// Pick the median of all points that have
|
||||
// not been found yet
|
||||
for (int j = 0; j < targetPoints.Length; j++) {
|
||||
if (!targetsFound[j]) {
|
||||
if (!set) {
|
||||
min = (Vector3)targetNodes[j].position;
|
||||
max = (Vector3)targetNodes[j].position;
|
||||
set = true;
|
||||
} else {
|
||||
min = Vector3.Min((Vector3)targetNodes[j].position, min);
|
||||
max = Vector3.Max((Vector3)targetNodes[j].position, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var midpoint = (Int3)((min+max)*0.5F);
|
||||
hTarget = midpoint;
|
||||
break;
|
||||
case HeuristicMode.Sequential:
|
||||
|
||||
// The first time the hTarget should always be recalculated
|
||||
// But other times we can skip it if we have not yet found the current target
|
||||
// since then the hTarget would just be set to the same value again
|
||||
if (!firstTime && !targetsFound[sequentialTarget]) {
|
||||
return;
|
||||
}
|
||||
|
||||
float dist = 0;
|
||||
|
||||
// Pick the target which is furthest away and has not been found yet
|
||||
for (int j = 0; j < targetPoints.Length; j++) {
|
||||
if (!targetsFound[j]) {
|
||||
float d = (targetNodes[j].position-startNode.position).sqrMagnitude;
|
||||
if (d > dist) {
|
||||
dist = d;
|
||||
hTarget = (Int3)targetPoints[j];
|
||||
sequentialTarget = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Rebuild the open list since all the H scores have changed
|
||||
// However the first time we can skip this since
|
||||
// no nodes are added to the heap yet
|
||||
if (!firstTime) {
|
||||
RebuildOpenList();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Initialize () {
|
||||
// Reset the start node to prevent
|
||||
// old info from previous paths to be used
|
||||
PathNode startRNode = pathHandler.GetPathNode(startNode);
|
||||
|
||||
startRNode.node = startNode;
|
||||
startRNode.pathID = pathID;
|
||||
startRNode.parent = null;
|
||||
startRNode.cost = 0;
|
||||
startRNode.G = GetTraversalCost(startNode);
|
||||
startRNode.H = CalculateHScore(startNode);
|
||||
|
||||
for (int j = 0; j < targetNodes.Length; j++) {
|
||||
if (startNode == targetNodes[j]) {
|
||||
// The start node is equal to the target node
|
||||
// so we can immediately mark the path as calculated
|
||||
FoundTarget(startRNode, j);
|
||||
} else if (targetNodes[j] != null) {
|
||||
// Mark the node with a flag so that we can quickly check if we have found a target node
|
||||
pathHandler.GetPathNode(targetNodes[j]).flag1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If all paths have either been invalidated or found already because they were at the same node as the start node
|
||||
if (targetNodeCount <= 0) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
return;
|
||||
}
|
||||
|
||||
//if (recalcStartEndCosts) {
|
||||
// startNode.InitialOpen (open,hTarget,startIntPoint,this,true);
|
||||
//} else {
|
||||
startNode.Open(this, startRNode, pathHandler);
|
||||
//}
|
||||
searchedNodes++;
|
||||
|
||||
//any nodes left to search?
|
||||
if (pathHandler.heap.isEmpty) {
|
||||
FailWithError("No open points, the start node didn't open any nodes");
|
||||
return;
|
||||
}
|
||||
|
||||
// Take the first node off the heap
|
||||
currentR = pathHandler.heap.Remove();
|
||||
}
|
||||
|
||||
protected override void Cleanup () {
|
||||
// Make sure that the shortest path is set
|
||||
// after the path has been calculated
|
||||
ChooseShortestPath();
|
||||
ResetFlags();
|
||||
}
|
||||
|
||||
/// <summary>Reset flag1 on all nodes after the pathfinding has completed (no matter if an error occurs or if the path is canceled)</summary>
|
||||
void ResetFlags () {
|
||||
// Reset all flags
|
||||
if (targetNodes != null) {
|
||||
for (int i = 0; i < targetNodes.Length; i++) {
|
||||
if (targetNodes[i] != null) pathHandler.GetPathNode(targetNodes[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) {
|
||||
// @Performance Just for debug info
|
||||
searchedNodes++;
|
||||
|
||||
// The node might be the target node for one of the paths
|
||||
if (currentR.flag1) {
|
||||
// Close the current node, if the current node is the target node then the path is finnished
|
||||
for (int i = 0; i < targetNodes.Length; i++) {
|
||||
if (!targetsFound[i] && currentR.node == targetNodes[i]) {
|
||||
FoundTarget(currentR, i);
|
||||
if (CompleteState != PathCompleteState.NotCalculated) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetNodeCount <= 0) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (System.DateTime.UtcNow.Ticks >= targetTick) {
|
||||
// Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
|
||||
return;
|
||||
}
|
||||
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Trace (PathNode node) {
|
||||
base.Trace(node);
|
||||
|
||||
if (inverted) {
|
||||
// Reverse the paths
|
||||
int half = path.Count/2;
|
||||
|
||||
for (int i = 0; i < half; i++) {
|
||||
GraphNode tmp = path[i];
|
||||
path[i] = path[path.Count-i-1];
|
||||
path[path.Count-i-1] = tmp;
|
||||
}
|
||||
|
||||
for (int i = 0; i < half; i++) {
|
||||
Vector3 tmp = vectorPath[i];
|
||||
vectorPath[i] = vectorPath[vectorPath.Count-i-1];
|
||||
vectorPath[vectorPath.Count-i-1] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override string DebugString (PathLog logMode) {
|
||||
if (logMode == PathLog.None || (!error && logMode == PathLog.OnlyErrors)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
System.Text.StringBuilder text = pathHandler.DebugStringBuilder;
|
||||
text.Length = 0;
|
||||
|
||||
DebugStringPrefix(logMode, text);
|
||||
|
||||
if (!error) {
|
||||
text.Append("\nShortest path was ");
|
||||
text.Append(chosenTarget == -1 ? "undefined" : nodePaths[chosenTarget].Count.ToString());
|
||||
text.Append(" nodes long");
|
||||
|
||||
if (logMode == PathLog.Heavy) {
|
||||
text.Append("\nPaths (").Append(targetsFound.Length).Append("):");
|
||||
for (int i = 0; i < targetsFound.Length; i++) {
|
||||
text.Append("\n\n Path ").Append(i).Append(" Found: ").Append(targetsFound[i]);
|
||||
|
||||
if (nodePaths[i] != null) {
|
||||
text.Append("\n Length: ");
|
||||
text.Append(nodePaths[i].Count);
|
||||
|
||||
GraphNode node = nodePaths[i][nodePaths[i].Count-1];
|
||||
|
||||
if (node != null) {
|
||||
PathNode nodeR = pathHandler.GetPathNode(endNode);
|
||||
if (nodeR != null) {
|
||||
text.Append("\n End Node");
|
||||
text.Append("\n G: ");
|
||||
text.Append(nodeR.G);
|
||||
text.Append("\n H: ");
|
||||
text.Append(nodeR.H);
|
||||
text.Append("\n F: ");
|
||||
text.Append(nodeR.F);
|
||||
text.Append("\n Point: ");
|
||||
text.Append(((Vector3)endPoint).ToString());
|
||||
text.Append("\n Graph: ");
|
||||
text.Append(endNode.GraphIndex);
|
||||
} else {
|
||||
text.Append("\n End Node: Null");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text.Append("\nStart Node");
|
||||
text.Append("\n Point: ");
|
||||
text.Append(((Vector3)endPoint).ToString());
|
||||
text.Append("\n Graph: ");
|
||||
text.Append(startNode.GraphIndex);
|
||||
text.Append("\nBinary Heap size at completion: ");
|
||||
text.AppendLine(pathHandler.heap == null ? "Null" : (pathHandler.heap.numberOfItems-2).ToString()); // -2 because numberOfItems includes the next item to be added and item zero is not used
|
||||
}
|
||||
}
|
||||
|
||||
DebugStringSuffix(logMode, text);
|
||||
|
||||
return text.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 610d59b3e8ca0400192a16c204ad20fb
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
@@ -0,0 +1,282 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Finds a path in a random direction from the start node.
|
||||
///
|
||||
/// Terminates and returns when G \>= length (passed to the constructor) + RandomPath.spread or when there are no more nodes left to search.
|
||||
///
|
||||
/// <code>
|
||||
///
|
||||
/// // Call a RandomPath call like this, assumes that a Seeker is attached to the GameObject
|
||||
///
|
||||
/// // The path will be returned when the path is over a specified length (or more accurately when the traversal cost is greater than a specified value).
|
||||
/// // A score of 1000 is approximately equal to the cost of moving one world unit.
|
||||
/// int theGScoreToStopAt = 50000;
|
||||
///
|
||||
/// // Create a path object
|
||||
/// RandomPath path = RandomPath.Construct(transform.position, theGScoreToStopAt);
|
||||
/// // Determines the variation in path length that is allowed
|
||||
/// path.spread = 5000;
|
||||
///
|
||||
/// // Get the Seeker component which must be attached to this GameObject
|
||||
/// Seeker seeker = GetComponent<Seeker>();
|
||||
///
|
||||
/// // Start the path and return the result to MyCompleteFunction (which is a function you have to define, the name can of course be changed)
|
||||
/// seeker.StartPath(path, MyCompleteFunction);
|
||||
///
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class RandomPath : ABPath {
|
||||
/// <summary>
|
||||
/// G score to stop searching at.
|
||||
/// The G score is rougly the distance to get from the start node to a node multiplied by 1000 (per default, see Pathfinding.Int3.Precision), plus any penalties
|
||||
/// </summary>
|
||||
public int searchLength;
|
||||
|
||||
/// <summary>
|
||||
/// All G scores between <see cref="searchLength"/> and <see cref="searchLength"/>+<see cref="spread"/> are valid end points, a random one of them is chosen as the final point.
|
||||
/// On grid graphs a low spread usually works (but keep it higher than nodeSize*1000 since that it the default cost of moving between two nodes), on NavMesh graphs
|
||||
/// I would recommend a higher spread so it can evaluate more nodes
|
||||
/// </summary>
|
||||
public int spread = 5000;
|
||||
|
||||
/// <summary>If an <see cref="aim"/> is set, the higher this value is, the more it will try to reach <see cref="aim"/></summary>
|
||||
public float aimStrength;
|
||||
|
||||
/// <summary>Currently chosen end node</summary>
|
||||
PathNode chosenNodeR;
|
||||
|
||||
/// <summary>
|
||||
/// The node with the highest G score which is still lower than <see cref="searchLength"/>.
|
||||
/// Used as a backup if a node with a G score higher than <see cref="searchLength"/> could not be found
|
||||
/// </summary>
|
||||
PathNode maxGScoreNodeR;
|
||||
|
||||
/// <summary>The G score of <see cref="maxGScoreNodeR"/></summary>
|
||||
int maxGScore;
|
||||
|
||||
/// <summary>
|
||||
/// An aim can be used to guide the pathfinder to not take totally random paths.
|
||||
/// For example you might want your AI to continue in generally the same direction as before, then you can specify
|
||||
/// aim to be transform.postion + transform.forward*10 which will make it more often take paths nearer that point
|
||||
/// See: <see cref="aimStrength"/>
|
||||
/// </summary>
|
||||
public Vector3 aim;
|
||||
|
||||
int nodesEvaluatedRep;
|
||||
|
||||
/// <summary>Random number generator</summary>
|
||||
readonly System.Random rnd = new System.Random();
|
||||
|
||||
public override bool FloodingPath {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool hasEndPoint {
|
||||
get {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
|
||||
searchLength = 5000;
|
||||
spread = 5000;
|
||||
|
||||
aimStrength = 0.0f;
|
||||
chosenNodeR = null;
|
||||
maxGScoreNodeR = null;
|
||||
maxGScore = 0;
|
||||
aim = Vector3.zero;
|
||||
|
||||
nodesEvaluatedRep = 0;
|
||||
}
|
||||
|
||||
public RandomPath () {}
|
||||
|
||||
public static RandomPath Construct (Vector3 start, int length, OnPathDelegate callback = null) {
|
||||
var p = PathPool.GetPath<RandomPath>();
|
||||
|
||||
p.Setup(start, length, callback);
|
||||
return p;
|
||||
}
|
||||
|
||||
protected RandomPath Setup (Vector3 start, int length, OnPathDelegate callback) {
|
||||
this.callback = callback;
|
||||
|
||||
searchLength = length;
|
||||
|
||||
originalStartPoint = start;
|
||||
originalEndPoint = Vector3.zero;
|
||||
|
||||
startPoint = start;
|
||||
endPoint = Vector3.zero;
|
||||
|
||||
startIntPoint = (Int3)start;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls callback to return the calculated path.
|
||||
/// See: <see cref="callback"/>
|
||||
/// </summary>
|
||||
protected override void ReturnPath () {
|
||||
if (path != null && path.Count > 0) {
|
||||
endNode = path[path.Count-1];
|
||||
endPoint = (Vector3)endNode.position;
|
||||
originalEndPoint = endPoint;
|
||||
cost = pathHandler.GetPathNode(endNode).G;
|
||||
hTarget = endNode.position;
|
||||
}
|
||||
if (callback != null) {
|
||||
callback(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Prepare () {
|
||||
nnConstraint.tags = enabledTags;
|
||||
var startNNInfo = AstarPath.active.GetNearest(startPoint, nnConstraint);
|
||||
|
||||
startPoint = startNNInfo.position;
|
||||
endPoint = startPoint;
|
||||
|
||||
startIntPoint = (Int3)startPoint;
|
||||
hTarget = (Int3)aim;//startIntPoint;
|
||||
|
||||
startNode = startNNInfo.node;
|
||||
endNode = startNode;
|
||||
|
||||
#if ASTARDEBUG
|
||||
Debug.DrawLine((Vector3)startNode.position, startPoint, Color.blue);
|
||||
Debug.DrawLine((Vector3)endNode.position, endPoint, Color.blue);
|
||||
#endif
|
||||
|
||||
if (startNode == null || endNode == null) {
|
||||
FailWithError("Couldn't find close nodes to the start point");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanTraverse(startNode)) {
|
||||
FailWithError("The node closest to the start point could not be traversed");
|
||||
return;
|
||||
}
|
||||
|
||||
heuristicScale = aimStrength;
|
||||
}
|
||||
|
||||
protected override void Initialize () {
|
||||
//Adjust the costs for the end node
|
||||
/*if (hasEndPoint && recalcStartEndCosts) {
|
||||
* endNodeCosts = endNode.InitialOpen (open,hTarget,(Int3)endPoint,this,false);
|
||||
* callback += ResetCosts; /* \todo Might interfere with other paths since other paths might be calculated before #callback is called *
|
||||
* }*/
|
||||
|
||||
//Node.activePath = this;
|
||||
PathNode startRNode = pathHandler.GetPathNode(startNode);
|
||||
|
||||
startRNode.node = startNode;
|
||||
|
||||
if (searchLength+spread <= 0) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
Trace(startRNode);
|
||||
return;
|
||||
}
|
||||
|
||||
startRNode.pathID = pathID;
|
||||
startRNode.parent = null;
|
||||
startRNode.cost = 0;
|
||||
startRNode.G = GetTraversalCost(startNode);
|
||||
startRNode.H = CalculateHScore(startNode);
|
||||
|
||||
/*if (recalcStartEndCosts) {
|
||||
* startNode.InitialOpen (open,hTarget,startIntPoint,this,true);
|
||||
* } else {*/
|
||||
startNode.Open(this, startRNode, pathHandler);
|
||||
//}
|
||||
|
||||
searchedNodes++;
|
||||
|
||||
//any nodes left to search?
|
||||
if (pathHandler.heap.isEmpty) {
|
||||
FailWithError("No open points, the start node didn't open any nodes");
|
||||
return;
|
||||
}
|
||||
|
||||
currentR = pathHandler.heap.Remove();
|
||||
}
|
||||
|
||||
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++;
|
||||
|
||||
// Close the current node, if the current node is the target node then the path is finished
|
||||
if (currentR.G >= searchLength) {
|
||||
if (currentR.G <= searchLength+spread) {
|
||||
nodesEvaluatedRep++;
|
||||
|
||||
if (rnd.NextDouble() <= 1.0f/nodesEvaluatedRep) {
|
||||
chosenNodeR = currentR;
|
||||
}
|
||||
} else {
|
||||
// If no node was in the valid range of G scores, then fall back to picking one right outside valid range
|
||||
if (chosenNodeR == null) chosenNodeR = currentR;
|
||||
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
break;
|
||||
}
|
||||
} else if (currentR.G > maxGScore) {
|
||||
maxGScore = (int)currentR.G;
|
||||
maxGScoreNodeR = currentR;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (chosenNodeR != null) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
} else if (maxGScoreNodeR != null) {
|
||||
chosenNodeR = maxGScoreNodeR;
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
} else {
|
||||
FailWithError("Not a single node found to search");
|
||||
}
|
||||
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 (System.DateTime.UtcNow.Ticks >= targetTick) {
|
||||
// Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
|
||||
return;
|
||||
}
|
||||
counter = 0;
|
||||
|
||||
if (searchedNodes > 1000000) {
|
||||
throw new System.Exception("Probable infinite loop. Over 1,000,000 nodes searched");
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
if (CompleteState == PathCompleteState.Complete) {
|
||||
Trace(chosenNodeR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2006bb8ffadf54c04ac4550f47be20b2
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
@@ -0,0 +1,228 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// Extended Path.
|
||||
///
|
||||
/// This is the same as a standard path but it is possible to customize when the target should be considered reached.
|
||||
/// Can be used to for example signal a path as complete when it is within a specific distance from the target.
|
||||
///
|
||||
/// Note: More customizations does make it slower to calculate than an ABPath but not by very much.
|
||||
///
|
||||
/// See: Pathfinding.PathEndingCondition
|
||||
/// </summary>
|
||||
public class XPath : ABPath {
|
||||
/// <summary>
|
||||
/// Ending Condition for the path.
|
||||
/// The ending condition determines when the path has been completed.
|
||||
/// Can be used to for example signal a path as complete when it is within a specific distance from the target.
|
||||
///
|
||||
/// If ending conditions are used that are not centered around the endpoint of the path
|
||||
/// you should also switch the <see cref="heuristic"/> to None to make sure that optimal paths are still found.
|
||||
/// This has quite a large performance impact so you might want to try to run it with the default
|
||||
/// heuristic and see if the path is optimal in enough cases.
|
||||
/// </summary>
|
||||
public PathEndingCondition endingCondition;
|
||||
|
||||
public XPath () {}
|
||||
|
||||
public new static XPath Construct (Vector3 start, Vector3 end, OnPathDelegate callback = null) {
|
||||
var p = PathPool.GetPath<XPath>();
|
||||
|
||||
p.Setup(start, end, callback);
|
||||
p.endingCondition = new ABPathEndingCondition(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
endingCondition = null;
|
||||
}
|
||||
|
||||
#if !ASTAR_NO_GRID_GRAPH
|
||||
protected override bool EndPointGridGraphSpecialCase (GraphNode endNode) {
|
||||
// Don't use the grid graph special case for this path type
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>The start node need to be special cased and checked here if it is a valid target</summary>
|
||||
protected override void CompletePathIfStartIsValidTarget () {
|
||||
var pNode = pathHandler.GetPathNode(startNode);
|
||||
|
||||
if (endingCondition.TargetFound(pNode)) {
|
||||
ChangeEndNode(startNode);
|
||||
Trace(pNode);
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the <see cref="endNode"/> to target and resets some temporary flags on the previous node.
|
||||
/// Also sets <see cref="endPoint"/> to the position of target.
|
||||
/// </summary>
|
||||
void ChangeEndNode (GraphNode target) {
|
||||
// Reset temporary flags on the previous end node, otherwise they might be
|
||||
// left in the graph and cause other paths to calculate paths incorrectly
|
||||
if (endNode != null && endNode != startNode) {
|
||||
var pathNode = pathHandler.GetPathNode(endNode);
|
||||
pathNode.flag1 = pathNode.flag2 = false;
|
||||
}
|
||||
|
||||
endNode = target;
|
||||
endPoint = (Vector3)target.position;
|
||||
cost = pathHandler.GetPathNode(endNode).G;
|
||||
}
|
||||
|
||||
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++;
|
||||
|
||||
// Close the current node, if the current node is the target node then the path is finished
|
||||
if (endingCondition.TargetFound(currentR)) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
FailWithError("Searched whole area but could not find target");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 (System.DateTime.UtcNow.Ticks >= targetTick) {
|
||||
//Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
|
||||
return;
|
||||
}
|
||||
counter = 0;
|
||||
|
||||
if (searchedNodes > 1000000) {
|
||||
throw new System.Exception("Probable infinite loop. Over 1,000,000 nodes searched");
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
if (CompleteState == PathCompleteState.Complete) {
|
||||
ChangeEndNode(currentR.node);
|
||||
Trace(currentR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized ending condition for a path.
|
||||
/// This class can be used to implement a custom ending condition for e.g an Pathfinding.XPath.
|
||||
/// Inherit from this class and override the <see cref="TargetFound"/> function to implement you own ending condition logic.
|
||||
///
|
||||
/// For example, you might want to create an Ending Condition which stops when a node is close enough to a given point.
|
||||
/// Then what you do is that you create your own class, let's call it MyEndingCondition and override the function TargetFound to specify our own logic.
|
||||
/// We want to inherit from ABPathEndingCondition because only ABPaths have end points defined.
|
||||
///
|
||||
/// <code>
|
||||
/// public class MyEndingCondition : ABPathEndingCondition {
|
||||
///
|
||||
/// // Maximum world distance to the target node before terminating the path
|
||||
/// public float maxDistance = 10;
|
||||
///
|
||||
/// // Reuse the constructor in the superclass
|
||||
/// public MyEndingCondition (ABPath p) : base (p) {}
|
||||
///
|
||||
/// public override bool TargetFound (PathNode node) {
|
||||
/// return ((Vector3)node.node.position - abPath.originalEndPoint).sqrMagnitude <= maxDistance*maxDistance;
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// One part at a time. We need to cast the node's position to a Vector3 since internally, it is stored as an integer coordinate (Int3).
|
||||
/// Then we subtract the Pathfinding.Path.originalEndPoint from it to get their difference.
|
||||
/// The original end point is always the exact point specified when calling the path.
|
||||
/// As a last step we check the squared magnitude (squared distance, it is much faster than the non-squared distance) and check if it is lower or equal to our maxDistance squared.
|
||||
/// There you have it, it is as simple as that.
|
||||
/// Then you simply assign it to the endingCondition variable on, for example an XPath which uses the EndingCondition.
|
||||
///
|
||||
/// <code>
|
||||
/// XPath myXPath = XPath.Construct(startPoint, endPoint);
|
||||
/// MyEndingCondition ec = new MyEndingCondition();
|
||||
/// ec.maxDistance = 100; // Or some other value
|
||||
/// myXPath.endingCondition = ec;
|
||||
///
|
||||
/// // Calculate the path!
|
||||
/// seeker.StartPath (ec);
|
||||
/// </code>
|
||||
///
|
||||
/// Where seeker is a <see cref="Seeker"/> component, and myXPath is an Pathfinding.XPath.
|
||||
///
|
||||
/// Note: The above was written without testing. I hope I haven't made any mistakes, if you try it out, and it doesn't seem to work. Please post a comment in the forums.
|
||||
///
|
||||
/// Version: Method structure changed in 3.2
|
||||
/// Version: Updated in version 3.6.8
|
||||
///
|
||||
/// See: Pathfinding.XPath
|
||||
/// See: Pathfinding.ConstantPath
|
||||
/// </summary>
|
||||
public abstract class PathEndingCondition {
|
||||
/// <summary>Path which this ending condition is used on</summary>
|
||||
protected Path path;
|
||||
|
||||
protected PathEndingCondition () {}
|
||||
|
||||
public PathEndingCondition (Path p) {
|
||||
if (p == null) throw new System.ArgumentNullException("p");
|
||||
this.path = p;
|
||||
}
|
||||
|
||||
/// <summary>Has the ending condition been fulfilled.</summary>
|
||||
/// <param name="node">The current node.</param>
|
||||
public abstract bool TargetFound(PathNode node);
|
||||
}
|
||||
|
||||
/// <summary>Ending condition which emulates the default one for the ABPath</summary>
|
||||
public class ABPathEndingCondition : PathEndingCondition {
|
||||
/// <summary>
|
||||
/// Path which this ending condition is used on.
|
||||
/// Same as <see cref="path"/> but downcasted to ABPath
|
||||
/// </summary>
|
||||
protected ABPath abPath;
|
||||
|
||||
public ABPathEndingCondition (ABPath p) {
|
||||
if (p == null) throw new System.ArgumentNullException("p");
|
||||
abPath = p;
|
||||
path = p;
|
||||
}
|
||||
|
||||
/// <summary>Has the ending condition been fulfilled.</summary>
|
||||
/// <param name="node">The current node.
|
||||
/// This is per default the same as asking if node == p.endNode</param>
|
||||
public override bool TargetFound (PathNode node) {
|
||||
return node.node == abPath.endNode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ending condition which stops a fixed distance from the target point</summary>
|
||||
public class EndingConditionProximity : ABPathEndingCondition {
|
||||
/// <summary>Maximum world distance to the target node before terminating the path</summary>
|
||||
public float maxDistance = 10;
|
||||
|
||||
public EndingConditionProximity (ABPath p, float maxDistance) : base(p) {
|
||||
this.maxDistance = maxDistance;
|
||||
}
|
||||
|
||||
public override bool TargetFound (PathNode node) {
|
||||
return ((Vector3)node.node.position - abPath.originalEndPoint).sqrMagnitude <= maxDistance*maxDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0055d294de64544e0a18edeca86f45fd
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
Reference in New Issue
Block a user