Upgrade Guide
This page helps you upgrade from an older version of the A* Pathfinding Project to a newer one.
Contents
- Upgrading to 5.4 from 5.x
- Upgrading to 5.0 from 4.x
- Installing the update
- Use ai.SetPath instead of Seeker.StartPath
- Seeker.pathCallback is deprecated
- Scans happen during OnEnable instead of during Awake
- Pathfinding tags are now represented by a struct
- Removed support for RVOSquareObstacle
- Deprecated RVONavmesh
- Changed interface for custom graphs
- Graph updates do more work asynchronously
- Use GraphUpdateObject.ApplyJob instead of Apply for grid graphs
- Batch grid node updates for better performance
- Costs in hexagonal graphs were incorrectly scaled
- NavGraph.GetNearest now always takes constraints into account
- Movement scripts no longer use Update and FixedUpdate
- ProceduralGridMover is now ProceduralGraphMover
- Namespace Structure
- General upgrade notes
Upgrading to 5.4 from 5.x
Version 5.4 brings a lot of new features and improvements, and there are some breaking changes.
Most code will continue to work without any changes, a few things will log compilation warnings, letting you know that you should update your code. And yet fewer things will cause compilation errors, or runtime errors, which you'll have to fix before using the new version.
New and more accurate traversal costs on navmesh/recast graphs
Making regions harder or easier to traverse has been made more intuitive and accurate. Instead of only allowing a cost for entering a node (which is not very accurate on navmesh/recast graphs as their nodes can vary so much in size), you can now set a cost multiplier which is scaled by the distance traversed inside the node.
So for example a multiplier of 2 means that the node is twice as expensive to traverse, compared to the default.
You can find this in the Seeker or FollowerEntity inspectors under the Tags foldout, called "Cost per world unit". In the inspector, the multiplier is multiplied by 1000 to make it correspond to the cost of moving 1 world unit.
The previous tag penalties are still there, but they are now called "cost per node", or TraversalCosts.tagEntryCosts in the code.
If you have previously been using tag penalties on navmesh/recast graphs, consider switching to the new cost multipliers. You may have to fiddle a bit with the values to get similar behavior, but it should be more accurate and easier to understand.
On grid graphs, using the cost multipliers is also a good choice, and likely more intuitive, but using costs per node is also fine. What you choose depends on what you find most intuitive.
Replace NNConstraint with NearestNodeConstraint
The NNConstraint class has been replaced with the new NearestNodeConstraint struct. It works in a similar way, but is now a struct instead of a class, and thus avoids GC allocations in many situations. In particular, the annoying allocation of a single NNConstraint for every pathfinding request is now avoided, and pathfinding can now be completely GC-free for most games.
There are compatibility methods in place, so for the most part you can continue to use your old code. However, for best performance, you should use the new struct instead.
Before: var constraint = NNConstraint.Walkable;
After:
constraint.graphMask = GraphMask.FromGraphName("My Graph");
constraint.constrainTags = true;
constraint.tags = 1 << 0;
constraint.constrainWalkability = true;
constraint.walkable = true;
constraint.constrainDistance = false;
AstarPath.active.GetNearest(Vector3.zero, constraint);var constraint = NearestNodeConstraint.Walkable;
constraint.graphMask = GraphMask.FromGraphName("My Graph");
// The constrainTags boolean does not exist anymore.
// It always filters on tags (though setting it to ~0 will make all tags pass).
constraint.tags = 1 << 0;
// The walkability constraint is now an enum, for clarity
constraint.walkable = NearestNodeConstraint.WalkabilityConstraint.Walkable;
// The max distance is now a nullable float, instead of a bool.
// Setting it to null will use the default value set on the AstarPath component.
constraint.maxDistance = float.PositiveInfinity;
// Set a filter function instead of subclassing the NNConstraint
constraint.filter = node => {
return ((Vector3)node.position).y > 0;
};
// You can also set a traversal provider directly on the constraint.
constraint.traversalProvider = new MyTraversalProvider();
AstarPath.active.GetNearest(Vector3.zero, constraint);
New ways to set traversal constraints and costs on paths
The new TraversalConstraint and TraversalCosts structs allow you to set which nodes are traversable and what the costs are for traversing them. This is a more uniform way of handling traversal constraints and costs, and fixes many edge cases with the old system.
When creating custom paths, you would previously set various fields on the path object itself, but now you should set the constraints and costs on the Path.traversalConstraint and Path.traversalCosts fields instead.
Before: var path = ABPath.Construct(Vector3.zero, Vector3.one, null);
After:
path.enabledTags = 1 << 0;
path.nnConstraint.graphMask = GraphMask.FromGraphName("My Graph");
path.tagPenalties = new uint[32];
path.tagPenalties[5] = 1000;
path.traversalProvider = new SomeTraversalProvider();var path = ABPath.Construct(Vector3.zero, Vector3.one, null);
The TraversalConstraint can also be directly converted into the corresponding NearestNodeConstraint:
path.traversalConstraint.tags = 1 << 0;
path.traversalConstraint.graphMask = GraphMask.FromGraphName("My Graph");
path.traversalCosts.tagEntryCosts = new uint[32];
path.traversalCosts.tagEntryCosts[5] = 1000;
// New field for traversal costs which is more intuitive on navmesh/recast graphs
path.traversalCosts.tagCostMultipliers = new float[32];
path.traversalCosts.tagCostMultipliers[5] = 2; // Make tag 5 twice as costly to traverse
// The traversal provider for costs and traversal constraints are now separate,
// so you must assign it in two places.
var traversalProvider = new SomeTraversalProvider();
path.traversalConstraint.traversalProvider = traversalProvider;
path.traversalCosts.traversalProvider = traversalProvider;
var nearestNodeConstraint = traversalConstraint.ToNearestNodeConstraint();
// Use the nearest node constraint to find the closest node
var nearest = AstarPath.active.GetNearest(transform.position, nearestNodeConstraint);
Overhauled ITraversalProvider
The ITraversalProvider interface has been changed to use the new TraversalConstraint and TraversalCosts structs. This allows for a more flexible and powerful way to define traversal costs and constraints. If you have implemented your own ITraversalProvider, you will need to update your implementation to use the new structs.
Since the ITraversalProvider interface has default implementations for all methods, you will not get any compiler errors, even if your method signatures are wrong. So check your implementation carefully. However, if you try to use the ITraversalProvider for pathfinding, you will get an error at runtime if you are using the old implementation.
Before: public class MyTraversalProvider : ITraversalProvider {
After:
public bool CanTraverse (Path path, GraphNode node) {
return DefaultITraversalProvider.CanTraverse(path, node);
}
public bool CanTraverse (Path path, GraphNode from, GraphNode to) {
return CanTraverse(path, to);
}
public uint GetTraversalCost (Path path, GraphNode node) {
return DefaultITraversalProvider.GetTraversalCost(path, node);
}
}public class MyTraversalProvider : ITraversalProvider {
Important changes:
public bool CanTraverse (ref TraversalConstraint traversalConstraint, GraphNode node) {
return DefaultITraversalProvider.CanTraverse(ref traversalConstraint, node);
}
public bool CanTraverse (ref TraversalConstraint traversalConstraint, GraphNode from, GraphNode to) {
return CanTraverse(ref traversalConstraint, to);
}
public float GetTraversalCostMultiplier (ref TraversalCosts traversalCosts, GraphNode node) {
return DefaultITraversalProvider.GetTraversalCostMultiplier(ref traversalCosts, node);
}
public uint GetConnectionCost (ref TraversalCosts traversalCosts, GraphNode from, GraphNode to) {
return DefaultITraversalProvider.GetConnectionCost(ref traversalCosts, from, to);
}
}
The CanTraverse method now takes a TraversalConstraint struct as the first parameter instead of a Path object.
The GetTraversalCost method has been replaced with GetConnectionCost and GetTraversalCostMultiplier methods, which take a TraversalCosts struct as the first parameter.
The GetTraversalCost method corresponds directly to the GetConnectionCost method, but for most games, the GetTraversalCostMultiplier method is a better choice.
The new GetConnectionCost method receives information about both the start and end node, allowing much more flexibility.
Take a look at the previous sections which describe the TraversalConstraint and TraversalCosts structs for more information.
GraphMask changes
The GraphMask struct has had its internal representation changed to be more flexible. This means that the old implicit conversion from an integer to a GraphMask is no longer available. This is a breaking change, but it should be easy to fix.
Before: GraphMask mask = -1;
After:
mask = 1 << 5;
mask = 1 << 3 | 1 << 5;GraphMask mask = GraphMask.everything;
Previously, the graph mask has not been able to represent graphs with indices larger than 31. Now, it can represent all combinations of graphs up to index 30, and combinations of up to 3 indices that are larger than 31. This means that for the very rare games where you need more than 31 graphs, the graph mask should no longer be a problem for the vast majority of cases.
mask = GraphMask.FromGraphIndex(5);
mask = GraphMask.FromGraphIndex(3) | GraphMask.FromGraphIndex(5);
// Also consider using
mask = GraphMask.FromGraphName("My Graph");
Upgrading to 5.0 from 4.x
There have been many changes in the 5.0 release. For most people, the upgrade should be smooth and require no changes to your code, but there are a few breaking changes which may require you to update your code or tweak some settings.
Installing the update
When you upgrade, make sure to delete your old AstarPathfindingProject folder before importing the new version. Not doing this can sometimes cause problems due to old files being left over.
Use ai.SetPath instead of Seeker.StartPath
The built-in movement scripts no longer listen for all paths that their attached Seeker component calculates; instead, they only listen for paths that they request. If you have been relying on this by calling seeker.StartPath from another script and expecting the movement scripts to start following the path, you will need to change this to call ai.SetPath instead. This has been the recommended way to do it for a long time, but now it is required. There is some compatibility code which will keep your code working for now, but it will log a warning every time it happens.
This does not affect you if you have written your own custom movement script. In that case, you should continue using the Seeker.
Previously, your code may have looked like: var ai = GetComponent<IAstarAI>();
You should replace that with something like:
var seeker = GetComponent<Seeker>();
seeker.StartPath(ai.position, ai.destination);var ai = GetComponent<IAstarAI>();
var path = ABPath.Construct(ai.position, ai.destination);
ai.SetPath(path);
Searching for paths for more info
Seeker.pathCallback is deprecated
The Seeker.pathCallback field is now deprecated. You should instead send a callback to the StartPath method every time.
This only affects you if you are writing your own movement script. If you are using one of the built-in movement scripts, then you should use ai.SetPath as described in the previous section.
Previously, your code may have looked like: Seeker seeker;
void OnEnable () {
seeker = GetComponent<Seeker>();
seeker.pathCallback += OnPathComplete;
}
void OnDisable () {
seeker.pathCallback -= OnPathComplete;
}
void DoSomething() {
seeker.StartPath(transform.position, target.position);
}
void OnPathComplete (Path path) {
// Do something with the path
}
You should replace that with something like: Seeker seeker;
OnPathDelegate onPathComplete;
void OnEnable () {
seeker = GetComponent<Seeker>();
onPathComplete = OnPathComplete;
}
void DoSomething() {
seeker.StartPath(transform.position, target.position, onPathComplete);
}
void OnPathComplete (Path path) {
// Do something with the path
}
Searching for paths for more info
Scans happen during OnEnable instead of during Awake
Scanning when the game starts now happens during early OnEnable instead of during early Awake. This should typically not cause any issues, since it's good practice in Unity to not depend on other components being initialized in Awake. But this may cause issues if you have been depending on this very specific initialization order.
Pathfinding tags are now represented by a struct
The type of some tag fields has changed from int to PathfindingTag. If you have assigned a tag to those fields before, you will now first need to convert it to a PathfindingTag:
GetComponent<GraphUpdateScene>().setTag = new PathfindingTag((uint)tag);
You can also use the PathfindingTag.FromName method to get a tag from a string.
If you want to show a tag enum in the inspector, you can expose a public field of type PathfindingTag. It will automatically be shown as a dropdown with the tag names.
Removed support for RVOSquareObstacle
The RVOSquareObstacle has been removed. It never worked particularly well and the performance was poor.
However, the local avoidance system now takes the pathfinding graph into account much better (if this is enabled on the RVOSimulator component), so you can use graph updates to update the pathfinding graph instead.
Deprecated RVONavmesh
The RVONavmesh component has been deprecated. This is now handled automatically by enabling the RVOSimulator.useNavmeshAsObstacle option.
Changed interface for custom graphs
If you have written your own custom graph class, several methods have changed their signature.
You can read more about what your code needs to do in Writing Graph Generators.
Graph updates do more work asynchronously
Graph updates for grid graphs and recast graphs do more work asynchronously now. This is usually a pure win as it improves your framerate. However, graph updates may take more frames to complete now, since the game can run while the graph updates are being calculated.
If you want to ensure that the graph updates are done immediately, you can call AstarPath.FlushGraphUpdates.
Use GraphUpdateObject.ApplyJob instead of Apply for grid graphs
Graph updates for grid graphs no longer call the GraphUpdateObject.Apply method; instead, the GraphUpdateObject.ApplyJob method is called. If you have been inheriting from the GraphUpdateObject to make custom graph updates, you may need to convert your code so that it works with Burst.
You may also be interested in Writing Custom Grid Graph Rules
Batch grid node updates for better performance
Due to the new burst and job system, individual node updates are a bit slower than they used to be, but batch updates can be significantly faster.
If you have been updating node walkability manually like this:
var grid = AstarPath.active.data.gridGraph;
grid.GetNodes(node => {
if (someCondition) {
node.Walkable = false;
}
});
grid.GetNodes(node => grid.CalculateConnections(node);
You should now do:
var grid = AstarPath.active.data.gridGraph;
Or you can use the new convenience method GridGraph.SetWalkability:
grid.GetNodes(node => {
if (someCondition) {
node.Walkable = false;
}
});
grid.RecalculateAllConnections();
// Perform the update when it is safe to do so
Similarly, if you have been using GridGraph.CalculateConnectionsForCellAndNeighbours to update many nodes, I strongly recommend that you use GridGraph.RecalculateAllConnections or GridGraph.RecalculateConnectionsInRegion instead. If you have been just updating one or two nodes, then it's fine to continue using GridGraph.CalculateConnectionsForCellAndNeighbours.
AstarPath.active.AddWorkItem(() => {
var grid = AstarPath.active.data.gridGraph;
// Mark all nodes in a 10x10 square, in the top-left corner of the graph, as unwalkable.
grid.SetWalkability(new bool[10*10], new IntRect(0, 0, 9, 9));
});
If you are doing more complex updates of the graph, you may instead want to write a custom grid graph rule. Grid graph rules are a new feature in 5.0 which allows you to write code that integrates much more tightly with the scanning process. This also means that they work automatically with graph updates and the ProceduralGraphMover component.
Costs in hexagonal graphs were incorrectly scaled
Costs between nodes in hexagonal graphs were sqrt(3/2)≈1.22 times too large. The connection costs are now approximately 1000*the distance between the nodes, as they should be.
For example, if you have set the hexagon width to 1, then the cost to move between two adjacent hexagons is now 1000 instead of 1224 like it was before. For almost all users, this will not affect anything; however, it may improve pathfinding performance a bit.
If you have been using penalties on your graph, then you may have to divide them by 1.22 to get the same behavior. Similarly, if you have been using the ConstantPath to get a set of nodes, you may have to divide the maxGScore parameter by 1.22.
NavGraph.GetNearest now always takes constraints into account
NavGraph.GetNearest will now always take the constraint into account. Previously, this depended on the graph type, and it was generally hard to use to get consistent results across graph types.
The NavGraph.GetNearestForce method has been removed and replaced by the NavGraph.GetNearest method, as they now do the same thing.
Not to be confused with AstarPath.GetNearest, which has always taken constraints into account.
Movement scripts no longer use Update and FixedUpdate
The AIPath and RichAI scripts no longer use the Update and FixedUpdate methods. Instead, a separate script ( BatchedEvents) is used which allows all components of a specific type to be processed at once. This is slightly faster and is also required for the AIBase.rvoDensityBehavior implementation. If you have been overriding the Update or FixedUpdate methods, you will need to change your code to instead override the OnUpdate method.
ProceduralGridMover is now ProceduralGraphMover
The ProceduralGridMover component has been renamed to ProceduralGraphMover, since it now supports more than just grids.
If you have been referencing it in a script, you'll need to change the name you use to reference it.
Namespace Structure
Namespace structure has been significantly improved. I have tried to keep most changes to internal classes that are not used often by users, but you may have to adjust your using statements slightly. Here's a summary of the changes:
Pathfinding.Graphs.Grid has been added, and a lot of classes related to the grid graph have been moved there.
Pathfinding.Graphs.Grid.Jobs is new and contains a lot of internal grid scanning code.
Pathfinding.Graphs.Grid.Rules is new and contains grid graph rules.
Pathfinding.Graphs.Navmesh replaces Pathfinding.Recast.
Pathfinding.Graphs.Navmesh.Voxelization replaces Pathfinding.Voxels.
Sadly, C# doesn't support public namespace aliases, so these changes cannot be made backwards compatible.
General upgrade notes
If you are having problems upgrading, try to delete the AstarPathfindingProject folder in Unity and import the package again. This can help remove old scripts which are not included in the project anymore but since UnityPackages merges directories, they are still there.
If you have problems with some compiler messages saying that some members or functions do not exist in a class, it is likely that your project contains a class with that name in the global namespace. This causes a conflict between the classes. To solve it, the simplest solution is to put the conflicting class in a namespace or just rename it.