This AI is the default movement script which comes with the A* Pathfinding Project. It is in no way required by the rest of the system, so feel free to write your own. But I hope this script will make it easier to set up movement for the characters in your game. This script works well for many types of units, but if you need the highest performance (for example if you are moving hundreds of characters) you may want to customize this script or write a custom movement script to be able to optimize it specifically for your game.
Here is a video of this script being used move an agent around (technically it uses the Pathfinding.Examples.MineBotAI script that inherits from this one but adds a bit of animation support for the example scenes):
Quick overview of the variables
In the inspector in Unity, you will see a bunch of variables. You can view detailed information further down, but here's a quick overview.
The repathRate determines how often it will search for new paths, if you have fast moving targets, you might want to set it to a lower value. The destination field is where the AI will try to move, it can be a point on the ground where the player has clicked in an RTS for example. Or it can be the player object in a zombie game. The maxSpeed is self-explanatory, as is rotationSpeed. however slowdownDistance might require some explanation: It is the approximate distance from the target where the AI will start to slow down. Setting it to a large value will make the AI slow down very gradually. pickNextWaypointDist determines the distance to the point the AI will move to (see image below).
This script has many movement fallbacks. If it finds an RVOController attached to the same GameObject as this component, it will use that. If it finds a character controller it will also use that. If it finds a rigidbody it will use that. Lastly it will fall back to simply modifying Transform.position which is guaranteed to always work and is also the most performant option.
How it works
In this section I'm going to go over how this script is structured and how information flows. This is useful if you want to make changes to this script or if you just want to understand how it works a bit more deeply. However you do not need to read this section if you are just going to use the script as-is.
This script inherits from the #AIBase class. The movement happens either in Unity's standard #Update or #FixedUpdate method. They are both defined in the AIBase class. Which one is actually used depends on if a rigidbody is used for movement or not. Rigidbody movement has to be done inside the FixedUpdate method while otherwise it is better to do it in Update.
From there a call is made to the MovementUpdate method (which in turn calls MovementUpdateInternal). This method contains the main bulk of the code and calculates how the AI *wants* to move. However it doesn't do any movement itself. Instead it returns the position and rotation it wants the AI to move to have at the end of the frame. The #Update (or #FixedUpdate) method then passes these values to the FinalizeMovement method which is responsible for actually moving the character. That method also handles things like making sure the AI doesn't fall through the ground using raycasting.
The AI recalculates its path regularly. This happens in the Update method which checks shouldRecalculatePath and if that returns true it will call SearchPath. The SearchPath method will prepare a path request and send it to the Seeker component which should be attached to the same GameObject as this script. Since this script will when waking up register to the Seeker.pathCallback delegate this script will be notified every time a new path is calculated by the OnPathComplete method being called. It may take one or sometimes multiple frames for the path to be calculated, but finally the OnPathComplete method will be called and the current path that the AI is following will be replaced.
The buffer will be cleared and replaced with the path. The first point is the current position of the agent.
out bool
stale
May be true if the path is invalid in some way. For example if the agent has no path or (for the RichAI script only) if the agent has detected that some nodes in the path have been destroyed.
)
Fills buffer with the remaining path.
var buffer = new List<Vector3>();
ai.GetRemainingPath(buffer, out bool stale); for (int i = 0; i < buffer.Count - 1; i++) { Debug.DrawLine(buffer[i], buffer[i+1], Color.red); }
OnTargetReached
()
The end of the path has been reached.
Public
void
OnTargetReached ()
The end of the path has been reached.
If you want custom logic for when the AI has reached it's destination add it here. You can also create a new script which inherits from this one and override the function in that script.
This method will be called again if a new path is calculated as the destination may have changed. So when the agent is close to the destination this method will typically be called every repathRate seconds.
This will trigger a path recalculation (if clearPath is true, which is the default) so if you want to teleport the agent and change its destination it is recommended that you set the destination before calling this method.
Draws detailed gizmos constantly in the scene view instead of only when the agent is selected and settings are being modified.
Public
bool
alwaysDrawGizmos
Draws detailed gizmos constantly in the scene view instead of only when the agent is selected and settings are being modified.
constrainInsideGraph
Ensure that the character is always on the traversable surface of the navmesh.
Public
bool
constrainInsideGraph = false
Ensure that the character is always on the traversable surface of the navmesh.
When this option is enabled a GetNearest query will be done every frame to find the closest node that the agent can walk on and if the agent is not inside that node, then the agent will be moved to it.
This is especially useful together with local avoidance in order to avoid agents pushing each other into walls.
This option also integrates with local avoidance so that if the agent is say forced into a wall by other agents the local avoidance system will be informed about that wall and can take that into account.
Enabling this has some performance impact depending on the graph type (pretty fast for grid graphs, slightly slower for navmesh/recast graphs). If you are using a navmesh/recast graph you may want to switch to the RichAI movement script which is specifically written for navmesh/recast graphs and does this kind of clamping out of the box. In many cases it can also follow the path more smoothly around sharp bends in the path.
It is not recommended that you use this option together with the funnel modifier on grid graphs because the funnel modifier will make the path go very close to the border of the graph and this script has a tendency to try to cut corners a bit. This may cause it to try to go slightly outside the traversable surface near corners and that will look bad if this option is enabled.
Warning
This option makes no sense to use on point graphs because point graphs do not have a surface. Enabling this option when using a point graph will lead to the agent being snapped to the closest node every frame which is likely not what you want.
Below you can see an image where several agents using local avoidance were ordered to go to the same point in a corner. When not constraining the agents to the graph they are easily pushed inside obstacles.
endOfPath
End point of path the agent is currently following.
Public
Vector3?
endOfPath
End point of path the agent is currently following.
If the agent has no path (or it might not be calculated yet), this will return the destination instead. If the agent has no destination it either it will return (+inf,+inf,+inf).
The end of the path is usually identical or very close to the destination, but it may differ if the path for example was blocked by a wall so that the agent couldn't get any closer.
This is only updated when the path is recalculated.
hasPath
True if this agent currently has a path that it follows.
Public
bool
hasPath
True if this agent currently has a path that it follows.
maxAcceleration
How quickly the agent accelerates.
Public
float
maxAcceleration = -2.5f
How quickly the agent accelerates.
Positive values represent an acceleration in world units per second squared. Negative values are interpreted as an inverse time of how long it should take for the agent to reach its max speed. For example if it should take roughly 0.4 seconds for the agent to reach its max speed then this field should be set to -1/0.4 = -2.5. For a negative value the final acceleration will be: -acceleration*maxSpeed. This behaviour exists mostly for compatibility reasons.
In the Unity inspector there are two modes: Default and Custom. In the Default mode this field is set to -2.5 which means that it takes about 0.4 seconds for the agent to reach its top speed. In the Custom mode you can set the acceleration to any positive value.
pathPending
True if a path is currently being calculated.
Public
bool
pathPending
True if a path is currently being calculated.
pickNextWaypointDist
How far the AI looks ahead along the path to determine the point it moves to.
Public
float
pickNextWaypointDist = 2
How far the AI looks ahead along the path to determine the point it moves to.
In world units. If you enable the alwaysDrawGizmos toggle this value will be visualized in the scene view as a blue circle around the agent.
Here are a few example videos showing some typical outcomes with good values as well as how it looks when this value is too low and too high.
Too low A too low value and a too low acceleration will result in the agent overshooting a lot and not managing to follow the path well.
Ok A low value but a high acceleration works decently to make the AI follow the path more closely. Note that the AILerp component is better suited if you want the agent to follow the path without any deviations.
Ok A reasonable value in this example.
Ok A reasonable value in this example, but the path is followed slightly more loosely than in the previous video.
Too high A too high value will make the agent follow the path too loosely and may cause it to try to move through obstacles.
preventMovingBackwards
Prevent the velocity from being too far away from the forward direction of the character.
Public
bool
preventMovingBackwards = false
Prevent the velocity from being too far away from the forward direction of the character.
If the character is ordered to move in the opposite direction from where it is facing then enabling this will cause it to make a small loop instead of turning on the spot.
This is a best effort calculation to see if the destination has been reached. For the AIPath/RichAI scripts, this is when the character is within endReachedDistance world units from the destination. For the AILerp script it is when the character is at the destination (±a very small margin).
This value will be updated immediately when the destination is changed (in contrast to reachedEndOfPath), however since path requests are asynchronous it will use an approximation until it sees the real path result. What this property does is to check the distance to the end of the current path, and add to that the distance from the end of the path to the destination (i.e. is assumes it is possible to move in a straight line between the end of the current path to the destination) and then checks if that total distance is less than endReachedDistance. This property is therefore only a best effort, but it will work well for almost all use cases.
Furthermore it will not report that the destination is reached if the destination is above the head of the character or more than half the height of the character below its feet (so if you have a multilevel building, it is important that you configure the height of the character correctly).
The cases which could be problematic are if an agent is standing next to a very thin wall and the destination suddenly changes to the other side of that thin wall. During the time that it takes for the path to be calculated the agent may see itself as alredy having reached the destination because the destination only moved a very small distance (the wall was thin), even though it may actually be quite a long way around the wall to the other side.
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 }
True if the agent has reached the end of the current path.
Public
bool
reachedEndOfPath
True if the agent has reached the end of the current path.
Note that setting the destination does not immediately update the path, nor is there any guarantee that the AI will actually be able to reach the destination that you set. The AI will try to get as close as possible. Often you want to use reachedDestination instead which is easier to work with.
It is very hard to provide a method for detecting if the AI has reached the destination that works across all different games because the destination may not even lie on the navmesh and how that is handled differs from game to game (see also the code snippet in the docs for destination).
Approximate remaining distance along the current path to the end of the path.
Public
float?
remainingDistance
Approximate remaining distance along the current path to the end of the path.
The RichAI movement script approximates this distance since it is quite expensive to calculate the real distance. However it will be accurate when the agent is within 1 corner of the destination. You can use GetRemainingPath to calculate the actual remaining path more precisely.
The AIPath and AILerp scripts use a more accurate distance calculation at all times.
If the agent does not currently have a path, then positive infinity will be returned.
Note
This is the distance to the end of the path, which may or may not be at the destination. If the character cannot reach the destination it will try to move as close as possible to it.
Warning
Since path requests are asynchronous, there is a small delay between a path request being sent and this value being updated with the new calculated path.
Rotation is calculated using Quaternion.RotateTowards. This variable represents the rotation speed in degrees per second. The higher it is, the faster the character will be able to rotate.
slowWhenNotFacingTarget
Slow down when not facing the target direction.
Public
bool
slowWhenNotFacingTarget = true
Slow down when not facing the target direction.
Incurs at a small performance overhead.
This setting only has an effect if enableRotation is enabled.
slowdownDistance
Distance from the end of the path where the AI will start to slow down.
Public
float
slowdownDistance = 0.6F
Distance from the end of the path where the AI will start to slow down.
steeringTarget
Point on the path which the agent is currently moving towards.
Public
Vector3?
steeringTarget
Point on the path which the agent is currently moving towards.
This is usually a point a small distance ahead of the agent or the end of the path.
If the agent does not have a path at the moment, then the agent's current position will be returned.
Looks for any attached components like RVOController and CharacterController etc.
Public
void
FindComponents ()
Looks for any attached components like RVOController and CharacterController etc.
This is done during OnEnable. If you are adding/removing components during runtime you may want to call this function to make sure that this script finds them. It is unfortunately prohibitive from a performance standpoint to look for components every frame.
This is used for pathfinding as the character's pivot point is sometimes placed at the center of the character instead of near the feet. In a building with multiple floors the center of a character may in some scenarios be closer to the navmesh on the floor above than to the floor below which could cause an incorrect path to be calculated. To solve this the start point of the requested paths is always at the base of the character.
Direction and distance to move the agent in world space.
)
Move the agent.
This is intended for external movement forces such as those applied by wind, conveyor belts, knockbacks etc.
Some movement scripts may ignore this completely (notably the AILerp script) if it does not have any concept of being moved externally.
The agent will not be moved immediately when calling this method. Instead this offset will be stored and then applied the next time the agent runs its movement calculations (which is usually later this frame or the next frame). If you want to move the agent immediately then call: ai.Move(someVector); ai.FinalizeMovement(ai.position, ai.rotation);
the rotation that the agent wants to rotate to during this frame.
)
Calculate how the character wants to move during this frame.
Note that this does not actually move the character. You need to call FinalizeMovement for that. This is called automatically unless canMove is false.
To handle movement yourself you can disable canMove and call this method manually. This code will replicate the normal behavior of the component: 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); }
SearchPath
()
Recalculate the current path.
Public
void
SearchPath ()
Recalculate the current path.
You can for example use this if you want very quick reaction times when you have changed the destination so that the agent does not have to wait until the next automatic path recalculation (see canSearch).
If there is an ongoing path calculation, it will be canceled, so make sure you leave time for the paths to get calculated before calling this function again. A canceled path will show up in the log with the message "Canceled by script" (see #Seeker.CancelCurrentPathRequest()).
If no destination has been set yet then nothing will be done.
Note
The path result may not become available until after a few frames. During the calculation time the #pathPending property will return true.
If true, the destination property will be set to the end point of the path. If false, the previous destination value will be kept.
)
Make the AI follow the specified path.
In case the path has not been calculated, the script will call seeker.StartPath to calculate it. This means the AI may not actually start to follow the path until in a few frames when the path has been calculated. The #pathPending field will as usual return true while the path is being calculated.
In case the path has already been calculated it will immediately replace the current path the AI is following. This is useful if you want to replace how the AI calculates its paths. Note that if you calculate the path using seeker.StartPath then this script will already pick it up because it is listening for all paths that the Seeker finishes calculating. In that case you do not need to call this function.
If you pass null as a parameter then the current path will be cleared and the agent will stop moving. Note than unless you have also disabled canSearch then the agent will soon recalculate its path and start moving again.
You can disable the automatic path recalculation by setting the canSearch field to false.
// 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);
// If you want to make use of properties like ai.reachedDestination or ai.remainingDistance or similar // you should also set the destination property to something reasonable. // Since the agent's own path recalculation is disabled, setting this will not affect how the paths are calculated. // ai.destination = ...
ShapeGizmoColor
PublicStaticReadonly
ColorShapeGizmoColor = new Color(240/255f, 213/255f, 30/255f)
SimulateRotationTowards
(direction, maxDegrees)
Simulates rotating the agent towards the specified direction and returns the new rotation.
Simulates rotating the agent towards the specified direction and returns the new rotation.
Note that this only calculates a new rotation, it does not change the actual rotation of the agent. Useful when you are handling movement externally using FinalizeMovement but you want to use the built-in rotation code.
Velocity that this agent wants to move with before taking local avoidance into account.
Includes gravity. In world units per second.
Setting this property will set the current velocity that the agent is trying to move with, including gravity. This can be useful if you want to make the agent come to a complete stop in a single frame or if you want to modify the velocity in some way.
// Set the velocity to zero, but keep the current gravity var newVelocity = new Vector3(0, ai.desiredVelocityWithoutLocalAvoidance.y, 0);
The Pathfinding.AILerp movement script doesn't use local avoidance so this property will always be identical to velocity on that component.
Warning
Trying to set this property on an AILerp component will throw an exception since its velocity cannot meaningfully be changed abitrarily.
If you are not using local avoidance then this property will in almost all cases be identical to desiredVelocity plus some noise due to floating point math.
Position in the world that this agent should move to.
If no destination has been set yet, then (+infinity, +infinity, +infinity) will be returned.
Note that setting this property does not immediately cause the agent to recalculate its path. So it may take some time before the agent starts to move towards this point. Most movement scripts have a repathRate field which indicates how often the agent looks for a new path. You can also call the SearchPath method to immediately start to search for a new path. Paths are calculated asynchronously so when an agent starts to search for path it may take a few frames (usually 1 or 2) until the result is available. During this time the #pathPending property will return true.
If you are setting a destination and then want to know when the agent has reached that destination then you could either use reachedDestination (recommended) or check both #pathPending and #reachedEndOfPath. Check the documentation for the respective fields to learn about their differences.
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 }
enableRotation
If true, the AI will rotate to face the movement direction.
Public
bool
enableRotation = true
If true, the AI will rotate to face the movement direction.
Distance to the end point to consider the end of path to be reached.
Public
float
endReachedDistance = 0.2f
Distance to the end point to consider the end of path to be reached.
When the end of the path is within this distance then #reachedEndOfPath will return true. When the destination is within this distance then reachedDestination will return true.
Note that the destination may not be reached just because the end of the path was reached. The destination may not be reachable at all.
Vector3gravity = new Vector3(float.NaN, float.NaN, float.NaN)
Gravity to use.
If set to (NaN,NaN,NaN) then Physics.Gravity (configured in the Unity project settings) will be used. If set to (0,0,0) then no gravity will be used and no raycast to check for ground penetration will be performed.
This is visualized in the scene view as a yellow cylinder around the character.
This value is currently only used if an RVOController is attached to the same GameObject, otherwise it is only used for drawing nice gizmos in the scene view. However since the height value is used for some things, the radius field is always visible for consistency and easier visualization of the character. That said, it may be used for something in a future release.
Note
The Pathfinding.AILerp script doesn't really have any use of knowing the radius or the height of the character, so this property will always return 0 in that script.
isStopped
Gets or sets if the agent should stop moving.
Public
bool
isStopped
Gets or sets if the agent should stop moving.
If this is set to true the agent will immediately start to slow down as quickly as it can to come to a full stop. The agent will still react to local avoidance and gravity (if applicable), but it will not try to move in any particular direction.
The current path of the agent will not be cleared, so when this is set to false again the agent will continue moving along the previous path.
This is a purely user-controlled parameter, so for example it is not set automatically when the agent stops moving because it has reached the target. Use #reachedEndOfPath for that.
If this property is set to true while the agent is traversing an off-mesh link (RichAI script only), then the agent will continue traversing the link and stop once it has completed it.
Note
This is not the same as the canMove setting which some movement scripts have. The canMove setting disables movement calculations completely (which among other things makes it not be affected by local avoidance or gravity). For the AILerp movement script which doesn't use gravity or local avoidance anyway changing this property is very similar to changing canMove.
The #steeringTarget property will continue to indicate the point which the agent would move towards if it would not be stopped.
For 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games. For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games.
Using the YAxisForward option will also allow the agent to assume that the movement will happen in the 2D (XY) plane instead of the XZ plane if it does not know. This is important only for the point graph which does not have a well defined up direction. The other built-in graphs (e.g the grid graph) will all tell the agent which movement plane it is supposed to use.
position
Position of the agent.
Public
Vector3?
position
Position of the agent.
In world space. If updatePosition is true then this value is idential to transform.position.
This is visualized in the scene view as a yellow cylinder around the character.
Note
The Pathfinding.AILerp script doesn't really have any use of knowing the radius or the height of the character, so this property will always return 0 in that script.
rotation
Rotation of the agent.
Public
Quaternion?
rotation
Rotation of the agent.
If updateRotation is true then this value is identical to transform.rotation.
rvoDensityBehavior
Controls if the agent slows down to a stop if the area around the destination is crowded.
This is an A* Pathfinding Project Pro feature only. This function/class/variable might not exist in the Free version of the A* Pathfinding Project or the functionality might be limited. The Pro version can be bought here
updatePosition
Determines if the character's position should be coupled to the Transform's position.
Public
bool
updatePosition = true
Determines if the character's position should be coupled to the Transform's position.
If false then all movement calculations will happen as usual, but the object that this component is attached to will not move instead only the position property will change.
This is useful if you want to control the movement of the character using some other means such as for example root motion but still want the AI to move freely.
Determines if the character's rotation should be coupled to the Transform's rotation.
Public
bool
updateRotation = true
Determines if the character's rotation should be coupled to the Transform's rotation.
If false then all movement calculations will happen as usual, but the object that this component is attached to will not rotate instead only the rotation property will change.
The character can either stop immediately when it comes within that distance, which is useful for e.g archers or other ranged units that want to fire on a target. Or the character can continue to try to reach the exact destination point and come to a full stop there. This is useful if you want the character to reach the exact point that you specified.
Note
#reachedEndOfPath will become true when the character is within endReachedDistance units from the destination regardless of what this field is set to.
Outputs the start point and end point of the next automatic path request.
This is a separate method to make it easy for subclasses to swap out the endpoints of path requests. For example the #LocalSpaceRichAI script which requires the endpoints to be transformed to graph space first.
CancelCurrentPathRequest
()
Protected
void
CancelCurrentPathRequest ()
ClampToNavmesh
(position, positionChanged)
Constrains the character's position to lie on the navmesh.
A path is first requested by #UpdatePath, it is then calculated, probably in the same or the next frame. Finally it is returned to the seeker which forwards it to this function.
OnUpdate
(dt)
Called every frame.
Protected
void
OnUpdate (
float
dt
)
Called every frame.
OnUpgradeSerializedData
(version, unityThread)
Handle serialization backwards compatibility.
Protected
int
OnUpgradeSerializedData (
int
version
bool
unityThread
)
Handle serialization backwards compatibility.
RaycastPosition
(position, lastElevation)
Checks if the character is grounded and prevents ground penetration.
Direction in the movement plane to rotate towards.
float
maxDegreesMainAxis
Maximum number of degrees to rotate this frame around the character's main axis. This is rotating left and right as a character normally does.
float
maxDegreesOffAxis=float.PositiveInfinity
Maximum number of degrees to rotate this frame around other axes. This is used to ensure the character's up direction is correct. It is only used for non-planar worlds where the up direction changes depending on the position of the character. More precisely a faster code path which ignores this parameter is used whenever the current movementPlane is exactly the XZ or XY plane. This must be at least as large as maxDegreesMainAxis.
)
Simulates rotating the agent towards the specified direction and returns the new rotation.
Note that this only calculates a new rotation, it does not change the actual rotation of the agent.
This is visualized in the scene view as a yellow cylinder around the character.
This value is currently only used if an RVOController is attached to the same GameObject, otherwise it is only used for drawing nice gizmos in the scene view. However since the height value is used for some things, the radius field is always visible for consistency and easier visualization of the character. That said, it may be used for something in a future release.
Note
The Pathfinding.AILerp script doesn't really have any use of knowing the radius or the height of the character, so this property will always return 0 in that script.
interpolator
Helper which calculates points along the current path.
Position of the character at the end of the frame before the last frame.
radius
Radius of the agent in world units.
Private
float IAstarAI.
radius
Radius of the agent in world units.
This is visualized in the scene view as a yellow cylinder around the character.
Note
The Pathfinding.AILerp script doesn't really have any use of knowing the radius or the height of the character, so this property will always return 0 in that script.