123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- 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;
- 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);
- }
- }
- }
- }
|