[Open online documentation to see videos] | \xmlonly |
[Open online documentation to see videos] | \xmlonly |
[Open online documentation to see videos] | \xmlonly |
[Open online documentation to see videos] | \xmlonly |
[Open online documentation to see videos] | \xmlonly |
+ /// using UnityEngine;
+ /// using Pathfinding;
+ /// using System.Collections;
+ /// using Pathfinding.ECS;
+ ///
+ /// namespace Pathfinding.Examples {
+ /// public class FollowerJumpLink : MonoBehaviour, IOffMeshLinkHandler, IOffMeshLinkStateMachine {
+ /// // Register this class as the handler for off-mesh links when the component is enabled
+ /// void OnEnable() => GetComponent().onTraverseOffMeshLink = this;
+ /// void OnDisable() => GetComponent().onTraverseOffMeshLink = null;
+ ///
+ /// IOffMeshLinkStateMachine IOffMeshLinkHandler.GetOffMeshLinkStateMachine(AgentOffMeshLinkTraversalContext context) => this;
+ ///
+ /// void IOffMeshLinkStateMachine.OnFinishTraversingOffMeshLink (AgentOffMeshLinkTraversalContext context) {
+ /// Debug.Log("An agent finished traversing an off-mesh link");
+ /// }
+ ///
+ /// void IOffMeshLinkStateMachine.OnAbortTraversingOffMeshLink () {
+ /// Debug.Log("An agent aborted traversing an off-mesh link");
+ /// }
+ ///
+ /// IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink (AgentOffMeshLinkTraversalContext ctx) {
+ /// var start = (Vector3)ctx.link.relativeStart;
+ /// var end = (Vector3)ctx.link.relativeEnd;
+ /// var dir = end - start;
+ ///
+ /// // Disable local avoidance while traversing the off-mesh link.
+ /// // If it was enabled, it will be automatically re-enabled when the agent finishes traversing the link.
+ /// ctx.DisableLocalAvoidance();
+ ///
+ /// // Move and rotate the agent to face the other side of the link.
+ /// // When reaching the off-mesh link, the agent may be facing the wrong direction.
+ /// while (!ctx.MoveTowards(
+ /// position: start,
+ /// rotation: Quaternion.LookRotation(dir, ctx.movementPlane.up),
+ /// gravity: true,
+ /// slowdown: true).reached) {
+ /// yield return null;
+ /// }
+ ///
+ /// var bezierP0 = start;
+ /// var bezierP1 = start + Vector3.up*5;
+ /// var bezierP2 = end + Vector3.up*5;
+ /// var bezierP3 = end;
+ /// var jumpDuration = 1.0f;
+ ///
+ /// // Animate the AI to jump from the start to the end of the link
+ /// for (float t = 0; t < jumpDuration; t += ctx.deltaTime) {
+ /// ctx.transform.Position = AstarSplines.CubicBezier(bezierP0, bezierP1, bezierP2, bezierP3, Mathf.SmoothStep(0, 1, t / jumpDuration));
+ /// yield return null;
+ /// }
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ /// Warning: Off-mesh links can be destroyed or disabled at any moment. The built-in code will attempt to make the agent continue following the link even if it is destroyed,
+ /// but if you write your own traversal code, you should be aware of this.
+ ///
+ /// You can alternatively set the corresponding property property on the off-mesh link (
+ /// IEnumerator Start () {
+ /// ai.destination = somePoint;
+ /// // Start to search for a path to the destination immediately
+ /// ai.SearchPath();
+ /// // Wait until the agent has reached the destination
+ /// while (!ai.reachedDestination) {
+ /// yield return null;
+ /// }
+ /// // The agent has reached the destination now
+ /// }
+ ///
+ ///
+ /// Note: The agent may not be able to reach the destination. In that case this property may never become true. Sometimes
+ /// IEnumerator Start () {
+ /// ai.destination = somePoint;
+ /// // Wait until the AI has reached the destination
+ /// while (!ai.reachedEndOfPath) {
+ /// yield return null;
+ /// }
+ /// // The agent has reached the destination now
+ /// }
+ ///
+ ///
+ /// See:
+ /// IEnumerator Start () {
+ /// ai.SetDestination(somePoint, Vector3.right);
+ /// // Wait until the AI has reached the destination and is rotated to the right in world space
+ /// while (!ai.reachedEndOfPath) {
+ /// yield return null;
+ /// }
+ /// // The agent has reached the destination now
+ /// }
+ ///
+ ///
+ /// See:
+ /// using Pathfinding;
+ /// using Pathfinding.ECS;
+ /// using Unity.Entities;
+ /// using Unity.Mathematics;
+ /// using Unity.Transforms;
+ /// using UnityEngine;
+ ///
+ /// public class MovementModifierNoise : MonoBehaviour {
+ /// /** How much noise to apply */
+ /// public float strength = 1;
+ /// /** How fast the noise should change */
+ /// public float frequency = 1;
+ /// float phase;
+ ///
+ /// public void Start () {
+ /// // Register a callback to modify the movement.
+ /// // This will be called during every simulation step for the agent.
+ /// // This may be called multiple times per frame if the time scale is high or fps is low,
+ /// // or less than once per frame, if the fps is very high.
+ /// GetComponent().movementOverrides.AddBeforeControlCallback(MovementOverride);
+ ///
+ /// // Randomize a phase, to make different agents behave differently
+ /// phase = UnityEngine.Random.value * 1000;
+ /// }
+ ///
+ /// public void OnDisable () {
+ /// // Remove the callback when the component is disabled
+ /// GetComponent().movementOverrides.RemoveBeforeControlCallback(MovementOverride);
+ /// }
+ ///
+ /// public void MovementOverride (Entity entity, float dt, ref LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings) {
+ /// // Rotate the next corner the agent is moving towards around the agent by a random angle.
+ /// // This will make the agent appear to move in a drunken fashion.
+ ///
+ /// // Don't modify the movement as much if we are very close to the end of the path
+ /// var strengthMultiplier = Mathf.Min(1, movementState.remainingDistanceToEndOfPart / Mathf.Max(shape.radius, movementSettings.follower.slowdownTime * movementSettings.follower.speed));
+ /// strengthMultiplier *= strengthMultiplier;
+ ///
+ /// // Generate a smoothly varying rotation angle
+ /// var rotationAngleRad = strength * strengthMultiplier * (Mathf.PerlinNoise1D(Time.time * frequency + phase) - 0.5f);
+ /// // Clamp it to at most plus or minus 90 degrees
+ /// rotationAngleRad = Mathf.Clamp(rotationAngleRad, -math.PI*0.5f, math.PI*0.5f);
+ ///
+ /// // Convert the rotation angle to a world-space quaternion.
+ /// // We use the movement plane to rotate around the agent's up axis,
+ /// // making this code work in both 2D and 3D games.
+ /// var rotation = movementPlane.value.ToWorldRotation(rotationAngleRad);
+ ///
+ /// // Rotate the direction to the next corner around the agent
+ /// movementState.nextCorner = localTransform.Position + math.mul(rotation, movementState.nextCorner - localTransform.Position);
+ /// }
+ /// }
+ ///
+ ///
+ /// There are a few different phases that you can register callbacks for:
+ ///
+ /// - BeforeControl: Called before the agent's movement is calculated. At this point, the agent has a valid path, and the next corner that is moving towards has been calculated.
+ /// - AfterControl: Called after the agent's desired movement is calculated. The agent has stored its desired movement in the
+ /// var buffer = new List();
+ ///
+ /// ai.GetRemainingPath(buffer, out bool stale);
+ /// for (int i = 0; i < buffer.Count - 1; i++) {
+ /// Debug.DrawLine(buffer[i], buffer[i+1], Color.red);
+ /// }
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// var buffer = new List();
+ /// var parts = new List();
+ ///
+ /// ai.GetRemainingPath(buffer, parts, out bool stale);
+ /// foreach (var part in parts) {
+ /// for (int i = part.startIndex; i < part.endIndex; i++) {
+ /// Debug.DrawLine(buffer[i], buffer[i+1], part.type == Funnel.PartType.NodeSequence ? Color.red : Color.green);
+ /// }
+ /// }
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Note: The
+ /// IEnumerator Start () {
+ /// var pointToAvoid = enemy.position;
+ /// // Make the AI flee from an enemy.
+ /// // The path will be about 20 world units long (the default cost of moving 1 world unit is 1000).
+ /// var path = FleePath.Construct(ai.position, pointToAvoid, 1000 * 20);
+ /// ai.SetPath(path);
+ ///
+ /// while (!ai.reachedEndOfPath) {
+ /// yield return null;
+ /// }
+ /// }
+ ///
+ ///
+ /// // Set the velocity to zero, but keep the current gravity
+ /// var newVelocity = new Vector3(0, ai.desiredVelocityWithoutLocalAvoidance.y, 0);
+ ///
+ /// ai.desiredVelocityWithoutLocalAvoidance = newVelocity;
+ ///
+ ///
+ /// Note: The
+ /// IEnumerator Start () {
+ /// ai.destination = somePoint;
+ /// // Start to search for a path to the destination immediately
+ /// ai.SearchPath();
+ /// // Wait until the agent has reached the destination
+ /// while (!ai.reachedDestination) {
+ /// yield return null;
+ /// }
+ /// // The agent has reached the destination now
+ /// }
+ ///
+ ///
+ /// See:
+ /// IEnumerator Start () {
+ /// ai.destination = somePoint;
+ /// // Start to search for a path to the destination immediately
+ /// ai.SearchPath();
+ /// // Wait until the agent has reached the destination
+ /// while (!ai.reachedDestination) {
+ /// yield return null;
+ /// }
+ /// // The agent has reached the destination now
+ /// }
+ ///
+ ///
+ /// IEnumerator Start () {
+ /// ai.destination = somePoint;
+ /// // Start to search for a path to the destination immediately
+ /// // Note that the result may not become available until after a few frames
+ /// // ai.pathPending will be true while the path is being calculated
+ /// ai.SearchPath();
+ /// // Wait until we know for sure that the agent has calculated a path to the destination we set above
+ /// while (ai.pathPending || !ai.reachedEndOfPath) {
+ /// yield return null;
+ /// }
+ /// // The agent has reached the destination now
+ /// }
+ ///
+ ///
+ /// var buffer = new List();
+ ///
+ /// ai.GetRemainingPath(buffer, out bool stale);
+ /// for (int i = 0; i < buffer.Count - 1; i++) {
+ /// Debug.DrawLine(buffer[i], buffer[i+1], Color.red);
+ /// }
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// var buffer = new List();
+ /// var parts = new List();
+ ///
+ /// ai.GetRemainingPath(buffer, parts, out bool stale);
+ /// foreach (var part in parts) {
+ /// for (int i = part.startIndex; i < part.endIndex; i++) {
+ /// Debug.DrawLine(buffer[i], buffer[i+1], part.type == Funnel.PartType.NodeSequence ? Color.red : Color.green);
+ /// }
+ /// }
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Note: The
+ /// // Disable the automatic path recalculation
+ /// ai.canSearch = false;
+ /// var pointToAvoid = enemy.position;
+ /// // Make the AI flee from the enemy.
+ /// // The path will be about 20 world units long (the default cost of moving 1 world unit is 1000).
+ /// var path = FleePath.Construct(ai.position, pointToAvoid, 1000 * 20);
+ /// ai.SetPath(path);
+ ///
+ ///
+ /// ai.Move(someVector);
+ /// ai.FinalizeMovement(ai.position, ai.rotation);
+ ///
+ ///
+ /// The
+ /// void Update () {
+ /// // Disable the AIs own movement code
+ /// ai.canMove = false;
+ /// Vector3 nextPosition;
+ /// Quaternion nextRotation;
+ /// // Calculate how the AI wants to move
+ /// ai.MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);
+ /// // Modify nextPosition and nextRotation in any way you wish
+ /// // Actually move the AI
+ /// ai.FinalizeMovement(nextPosition, nextRotation);
+ /// }
+ ///
+ ///
+ /// void OnEnable () {
+ /// ai = GetComponent();
+ /// if (ai != null) ai.onTraverseOffMeshLink += TraverseOffMeshLink;
+ /// }
+ ///
+ /// void OnDisable () {
+ /// if (ai != null) ai.onTraverseOffMeshLink -= TraverseOffMeshLink;
+ /// }
+ ///
+ /// IEnumerator TraverseOffMeshLink (RichSpecial link) {
+ /// // Traverse the link over 1 second
+ /// float startTime = Time.time;
+ ///
+ /// while (Time.time < startTime + 1) {
+ /// transform.position = Vector3.Lerp(link.first.position, link.second.position, Time.time - startTime);
+ /// yield return null;
+ /// }
+ /// transform.position = link.second.position;
+ /// }
+ ///
+ /// seeker.graphMask = 1 << 3;
+ ///
+ /// See: bitmasks (view in online documentation for working links)
+ ///
+ /// Note that this field only stores which graph indices that are allowed. This means that if the graphs change their ordering
+ /// then this mask may no longer be correct.
+ ///
+ /// If you know the name of the graph you can use the
+ /// GraphMask mask1 = GraphMask.FromGraphName("My Grid Graph");
+ /// GraphMask mask2 = GraphMask.FromGraphName("My Other Grid Graph");
+ ///
+ /// NNConstraint nn = NNConstraint.Walkable;
+ ///
+ /// nn.graphMask = mask1 | mask2;
+ ///
+ /// // Find the node closest to somePoint which is either in 'My Grid Graph' OR in 'My Other Grid Graph'
+ /// var info = AstarPath.active.GetNearest(somePoint, nn);
+ ///
+ ///
+ /// Some overloads of the
+ /// seeker.traversalProvider = new MyCustomTraversalProvider();
+ ///
+ ///
+ /// See: traversal_provider (view in online documentation for working links)
+ ///
+ /// public void Start () {
+ /// // Assumes a Seeker component is attached to the GameObject
+ /// Seeker seeker = GetComponent();
+ ///
+ /// // seeker.pathCallback is a OnPathDelegate, we add the function OnPathComplete to it so it will be called whenever a path has finished calculating on that seeker
+ /// seeker.pathCallback += OnPathComplete;
+ /// }
+ ///
+ /// public void OnPathComplete (Path p) {
+ /// Debug.Log("This is called when a path is completed on the seeker attached to this GameObject");
+ /// }
+ ///
+ ///
+ /// Deprecated: Pass a callback every time to the StartPath method instead, or use ai.SetPath+ai.pathPending on the movement script. You can cache it in your own script if you want to avoid the GC allocation of creating a new delegate.
+ ///
+ /// void Start () {
+ /// // Get the seeker component attached to this GameObject
+ /// var seeker = GetComponent();
+ ///
+ /// // Schedule a new path request from the current position to a position 10 units forward.
+ /// // When the path has been calculated, the OnPathComplete method will be called, unless it was canceled by another path request
+ /// seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, OnPathComplete);
+ ///
+ /// // Note that the path is NOT calculated at this point
+ /// // It has just been queued for calculation
+ /// }
+ ///
+ /// void OnPathComplete (Path path) {
+ /// // The path is now calculated!
+ ///
+ /// if (path.error) {
+ /// Debug.LogError("Path failed: " + path.errorLog);
+ /// return;
+ /// }
+ ///
+ /// // Cast the path to the path type we were using
+ /// var abPath = path as ABPath;
+ ///
+ /// // Draw the path in the scene view for 10 seconds
+ /// for (int i = 0; i < abPath.vectorPath.Count - 1; i++) {
+ /// Debug.DrawLine(abPath.vectorPath[i], abPath.vectorPath[i+1], Color.red, 10);
+ /// }
+ /// }
+ ///
+ ///
+ /// Deprecated: Use
+ /// void Start () {
+ /// // Get the seeker component attached to this GameObject
+ /// var seeker = GetComponent();
+ ///
+ /// // Schedule a new path request from the current position to a position 10 units forward.
+ /// // When the path has been calculated, the OnPathComplete method will be called, unless it was canceled by another path request
+ /// seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, OnPathComplete);
+ ///
+ /// // Note that the path is NOT calculated at this point
+ /// // It has just been queued for calculation
+ /// }
+ ///
+ /// void OnPathComplete (Path path) {
+ /// // The path is now calculated!
+ ///
+ /// if (path.error) {
+ /// Debug.LogError("Path failed: " + path.errorLog);
+ /// return;
+ /// }
+ ///
+ /// // Cast the path to the path type we were using
+ /// var abPath = path as ABPath;
+ ///
+ /// // Draw the path in the scene view for 10 seconds
+ /// for (int i = 0; i < abPath.vectorPath.Count - 1; i++) {
+ /// Debug.DrawLine(abPath.vectorPath[i], abPath.vectorPath[i+1], Color.red, 10);
+ /// }
+ /// }
+ ///
+ ///
+ /// // Schedule a path search that will only start searching the graphs with index 0 and 3
+ /// seeker.StartPath(startPoint, endPoint, null, 1 << 0 | 1 << 3);
+ ///
+ ///
+ /// var endPoints = new Vector3[] {
+ /// transform.position + Vector3.forward * 5,
+ /// transform.position + Vector3.right * 10,
+ /// transform.position + Vector3.back * 15
+ /// };
+ /// // Start a multi target path, where endPoints is a Vector3[] array.
+ /// // The pathsForAll parameter specifies if a path to every end point should be searched for
+ /// // or if it should only try to find the shortest path to any end point.
+ /// var path = seeker.StartMultiTargetPath(transform.position, endPoints, pathsForAll: true, callback: null);
+ /// path.BlockUntilCalculated();
+ ///
+ /// if (path.error) {
+ /// Debug.LogError("Error calculating path: " + path.errorLog);
+ /// return;
+ /// }
+ ///
+ /// Debug.Log("The closest target was index " + path.chosenTarget);
+ ///
+ /// // Draw the path to all targets
+ /// foreach (var subPath in path.vectorPaths) {
+ /// for (int i = 0; i < subPath.Count - 1; i++) {
+ /// Debug.DrawLine(subPath[i], subPath[i+1], Color.green, 10);
+ /// }
+ /// }
+ ///
+ /// // Draw the path to the closest target
+ /// for (int i = 0; i < path.vectorPath.Count - 1; i++) {
+ /// Debug.DrawLine(path.vectorPath[i], path.vectorPath[i+1], Color.red, 10);
+ /// }
+ ///
+ ///
+ /// AstarPath.active.data.GetNodes(node => {
+ /// Debug.Log("I found a node at position " + (Vector3)node.position);
+ /// });
+ ///
+ ///
+ /// See:
+ /// foreach (GridGraph graph in AstarPath.data.FindGraphsOfType (typeof(GridGraph))) {
+ /// //Do something with the graph
+ /// }
+ ///
+ /// See: AstarPath.RegisterSafeNodeUpdate
+ /// foreach (IUpdatableGraph graph in AstarPath.data.GetUpdateableGraphs ()) {
+ /// //Do something with the graph
+ /// }
+ /// See: AstarPath.AddWorkItem
+ /// See: Pathfinding.IUpdatableGraph
+ /// intersectionPoint = start1 + factor1 * (end1-start1)
+ /// intersectionPoint2 = start2 + factor2 * (end2-start2)
+ /// Lines are treated as infinite.
+ /// false is returned if the lines are parallel and true if they are not.
+ /// Only the XZ coordinates are used.
+ /// intersectionPoint = start1 + factor1 * (end1-start1)
+ /// intersectionPoint2 = start2 + factor2 * (end2-start2)
+ /// Lines are treated as infinite.
+ /// false is returned if the lines are parallel and true if they are not.
+ /// Only the XZ coordinates are used.
+ /// intersectionPoint = start1 + factor * (end1-start1)
+ /// Lines are treated as infinite.
+ ///
+ /// The second "line" is treated as a ray, meaning only matches on start2 or forwards towards end2 (and beyond) will be returned
+ /// If the point lies on the wrong side of the ray start, Nan will be returned.
+ ///
+ /// NaN is returned if the lines are parallel.
+ /// intersectionPoint = start1 + intersectionFactor * (end1-start1)
.
+ /// Lines are treated as infinite.
+ /// -1 is returned if the lines are parallel (note that this is a valid return value if they are not parallel too)
+ ///
+ /// // Create a new AstarPath object on Start and apply some default settings
+ /// public void Start () {
+ /// AstarPath.OnAwakeSettings += ApplySettings;
+ /// AstarPath astar = gameObject.AddComponent();
+ /// }
+ ///
+ /// public void ApplySettings () {
+ /// // Unregister from the delegate
+ /// AstarPath.OnAwakeSettings -= ApplySettings;
+ /// // For example threadCount should not be changed after the Awake call
+ /// // so here's the only place to set it if you create the component during runtime
+ /// AstarPath.active.threadCount = ThreadCount.One;
+ /// }
+ ///
+ ///
+ /// var readLock = AstarPath.active.LockGraphDataForReading();
+ /// var handle = new MyJob {
+ /// // ...
+ /// }.Schedule(readLock.dependency);
+ /// readLock.UnlockAfter(handle);
+ ///
+ ///
+ /// See:
+ /// AddWorkItem(new AstarWorkItem(callback));
+ ///
+ ///
+ /// See:
+ /// AddWorkItem(new AstarWorkItem(callback));
+ ///
+ ///
+ /// See:
+ /// AstarPath.active.AddWorkItem(new AstarWorkItem(() => {
+ /// // Safe to update graphs here
+ /// var node = AstarPath.active.GetNearest(transform.position).node;
+ /// node.Walkable = false;
+ /// }));
+ ///
+ ///
+ ///
+ /// AstarPath.active.AddWorkItem(() => {
+ /// // Safe to update graphs here
+ /// var node = AstarPath.active.GetNearest(transform.position).node;
+ /// node.position = (Int3)transform.position;
+ /// });
+ ///
+ ///
+ /// See:
+ /// UpdateGraphs(new GraphUpdateObject(bounds));
+ ///
+ ///
+ /// See: FlushGraphUpdates
+ /// See: batchGraphUpdates
+ /// See: graph-updates (view in online documentation for working links)
+ ///
+ /// var nodes = new PointNode[128];
+ /// var job = AstarPath.active.AllocateNodes(nodes, 128, () => new PointNode(), 1);
+ ///
+ /// job.Complete();
+ ///
+ ///
+ /// See:
+ /// var graphLock = AstarPath.active.PausePathfinding();
+ /// // Here we can modify the graphs safely. For example by increasing the penalty of a node
+ /// AstarPath.active.data.gridGraph.GetNode(0, 0).Penalty += 1000;
+ ///
+ /// // Allow pathfinding to resume
+ /// graphLock.Release();
+ ///
+ ///
+ /// Returns: A lock object. You need to call
+ /// // Recalculate all graphs
+ /// AstarPath.active.Scan();
+ ///
+ /// // Recalculate only the first grid graph
+ /// var graphToScan = AstarPath.active.data.gridGraph;
+ /// AstarPath.active.Scan(graphToScan);
+ ///
+ /// // Recalculate only the first and third graphs
+ /// var graphsToScan = new [] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] };
+ /// AstarPath.active.Scan(graphsToScan);
+ ///
+ ///
+ /// See: graph-updates (view in online documentation for working links)
+ /// See: ScanAsync
+ ///
+ /// // Recalculate all graphs
+ /// AstarPath.active.Scan();
+ ///
+ /// // Recalculate only the first grid graph
+ /// var graphToScan = AstarPath.active.data.gridGraph;
+ /// AstarPath.active.Scan(graphToScan);
+ ///
+ /// // Recalculate only the first and third graphs
+ /// var graphsToScan = new [] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] };
+ /// AstarPath.active.Scan(graphsToScan);
+ ///
+ ///
+ /// See: graph-updates (view in online documentation for working links)
+ /// See: ScanAsync
+ ///
+ /// IEnumerator Start () {
+ /// foreach (Progress progress in AstarPath.active.ScanAsync()) {
+ /// Debug.Log("Scanning... " + progress.ToString());
+ /// yield return null;
+ /// }
+ /// }
+ ///
+ ///
+ /// See: Scan
+ ///