using UnityEngine; using Unity.Mathematics; using Pathfinding.Pooling; namespace Pathfinding { /// /// 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. /// /// /// /// // 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(); /// /// // 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); /// /// /// /// [Open online documentation to see videos] /// /// See: wander (view in online documentation for working links) /// public class RandomPath : ABPath { /// /// 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. /// /// [Open online documentation to see videos] /// public int searchLength; /// /// All G scores between and + 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. /// /// [Open online documentation to see videos] /// public int spread = 5000; /// /// If an is set, the higher this value is, the more it will try to reach . /// /// Recommended values are between 0 and 10. /// public float aimStrength; /// Currently chosen end node uint chosenPathNodeIndex; uint chosenPathNodeGScore; /// /// The node with the highest G score which is still lower than . /// Used as a backup if a node with a G score higher than could not be found /// uint maxGScorePathNodeIndex; /// The G score of uint maxGScore; /// /// 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: /// public Vector3 aim; int nodesEvaluatedRep; /// Random number generator readonly System.Random rnd = new System.Random(); protected override bool hasEndPoint => false; public override bool endPointKnownBeforeCalculation => false; protected override void Reset () { base.Reset(); searchLength = 5000; spread = 5000; aimStrength = 0.0f; chosenPathNodeIndex = uint.MaxValue; maxGScorePathNodeIndex = uint.MaxValue; chosenPathNodeGScore = 0; maxGScore = 0; aim = Vector3.zero; nodesEvaluatedRep = 0; } public RandomPath () {} public static RandomPath Construct (Vector3 start, int length, OnPathDelegate callback = null) { var p = PathPool.GetPath(); 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; return this; } /// /// Calls callback to return the calculated path. /// See: /// protected override void ReturnPath () { if (path != null && path.Count > 0) { originalEndPoint = endPoint; } if (callback != null) { callback(this); } } protected override void Prepare () { var startNNInfo = GetNearest(startPoint); startPoint = startNNInfo.position; endPoint = startPoint; #if ASTARDEBUG Debug.DrawLine((Vector3)startNNInfo.node.position, startPoint, Color.blue); #endif if (startNNInfo.node == null) { FailWithError("Couldn't find close nodes to the start point"); return; } if (!CanTraverse(startNNInfo.node)) { FailWithError("The node closest to the start point could not be traversed"); return; } heuristicScale = aimStrength; pathHandler.AddTemporaryNode(new TemporaryNode { type = TemporaryNodeType.Start, position = (Int3)startNNInfo.position, associatedNode = startNNInfo.node.NodeIndex, }); heuristicObjective = new HeuristicObjective((int3)(Int3)aim, heuristic, heuristicScale); AddStartNodesToHeap(); } protected override void OnHeapExhausted () { if (chosenPathNodeIndex == uint.MaxValue && maxGScorePathNodeIndex != uint.MaxValue) { chosenPathNodeIndex = maxGScorePathNodeIndex; chosenPathNodeGScore = maxGScore; } if (chosenPathNodeIndex != uint.MaxValue) { OnFoundEndNode(chosenPathNodeIndex, 0, chosenPathNodeGScore); } else { FailWithError("Not a single node found to search"); } } protected override void OnFoundEndNode (uint pathNode, uint hScore, uint gScore) { if (pathHandler.IsTemporaryNode(pathNode)) { base.OnFoundEndNode(pathNode, hScore, gScore); } else { // The target node is a normal node. var node = pathHandler.GetNode(pathNode); endPoint = node.RandomPointOnSurface(); cost = gScore; CompleteState = PathCompleteState.Complete; Trace(pathNode); } } public override void OnVisitNode (uint pathNode, uint hScore, uint gScore) { // This method may be called multiple times without checking if the path is complete yet. if (CompleteState != PathCompleteState.NotCalculated) return; if (gScore >= searchLength) { if (gScore <= searchLength+spread) { nodesEvaluatedRep++; // Use reservoir sampling to pick a node from the ones with the highest G score if (rnd.NextDouble() <= 1.0f/nodesEvaluatedRep) { chosenPathNodeIndex = pathNode; chosenPathNodeGScore = gScore; } } else { // If no node was in the valid range of G scores, then fall back to picking one right outside valid range if (chosenPathNodeIndex == uint.MaxValue) { chosenPathNodeIndex = pathNode; chosenPathNodeGScore = gScore; } OnFoundEndNode(chosenPathNodeIndex, 0, chosenPathNodeGScore); } } else if (gScore > maxGScore) { maxGScore = gScore; maxGScorePathNodeIndex = pathNode; } } } }