Table of Contents

Path Finding

This article describes the path finding in Grids 2.

The method AStar is used to find the shortest possible path in a grid. The path finding method takes a grid, a starting point, a goal, a few tuning parameters, and returns a list of points that represents the path.

This is the method with all the arguments:

public static IEnumerable<TPoint> AStar<TPoint>(
    IGrid<TPoint> grid,
    TPoint start,
    TPoint goal,
    Func<TPoint, TPoint, float> heuristicCostEstimate,
    Func<TPoint, bool> isAccessible,
    Func<TPoint, IEnumerable<TPoint>> getConnectedPoints,
    Func<TPoint, TPoint, float> neighborToNeighborCost)

Here is a simple call using the above method in a rect grid:

var path = Algorithms.AStar(
    grid, 
    start, 
    goal, 
    (p,q) => RectPoint.ManhattentNorm(p - q), 
    c => true, 
    RectPoint.GetOrthogonalNeighbors,
    (p, q) => 1);

The rest of the tutorial will explain what you need to do to make this call and how the various parameters work.

Calling the AStar method

You will typically have some information about whether a cell is accessible, and how much it costs to travel through the cell. One way to do this is to define your own cell and use this cell in your grid.

For example, you can make a cell like this:
public class PathCell : SpriteCell
{
    public bool IsAccessible { get; set; }
    public float Cost { get; set; }
}

Use it instead of the default SpriteCell on your cell prefab.

However, this is not strictly necessary, since the path-finding algorithm takes custom functions, and you are free to give it any function that can give the required information. For the rest of the article, we will assume that the cell above is used.

Work out what types all your parameters should be. (If you are fluent in generics and C# interfaces, this step is probably just making a mental note or two.)

All the parameter types will depend on the grid that pass as the first parameter. To work out the other types, follow these steps:

  1. The point type TPoint is determined by the number of dimensions of your grid. For 2D it will be GridPoint2, and for 3D GridPoint3 . It can also be a custom type if you defined your own grid point type.
  2. heuristicCostEstimate is a function that takes two points and returns a float value.
  3. isAccessible is a function that takes a point and returns a bool.
  4. getConnectedPoints is a function that takes a point and returns a list of points.
  5. neighborToNeighborCost is a function that takes two points and returns a float.

Write a method or lambda expression to decide whether a cell is accessible or not.

If all cells are accessible, you can pass in the lambda expression c => true (it always returns true, regardless of the cell).

Otherwise, if you implemented your own cell as above, you can access the IsAccessible property in the lambda expression. This will make the function skip points that cannot be reached.

var path = Algorithms.AStar(
    grid, 
    start, 
    goal, 
    (p,q) => RectPoint.ManhattanNorm(p - q), 
    p => grid[p].IsAccessible, 
    RectPoint.GetOrthogonalNeighbors,
    (p, q) => 1);

You can also write a method instead of using the lambda expression. This is preferable if your access logic is more complex.

public bool IsCellAccessible(GridPoint point)
{
    return grid[point].IsAccessible;
}

You can then call the AStar method like this:

var path = Algorithms.AStar(
    grid, 
    start, 
    goal, 
    (p,q) => RectPoint.ManhattanNorm(p - q), 
    IsCellAccessible, 
    RectPoint.GetOrthogonalNeighbors,
    (p, q) => 1);

Write a lambda expression or method to get all the neighbors of a point. You can go from a point to a neighbor of that point without going through another cell. You can read more about neighbors in Neighbors, and find in that article a list of predefined static methods defined for common situations. One is the method GetOrthogonalNeighbors .

Below is what the path finding algorithm returns for various neighbor setups. Note that the cell costs are not taken into account.

Orthogonal Diagonal Orthogonal and Diagonal
The image shows a grid of squares in which you can see a pattern of orthogonal dots following a path through an obstacle. The image shows a grid of squares in which a pattern of diagonal dots can be seen following a path through an obstacle. The image shows a grid of squares in which a pattern of orthogonal and diagonal dots can be seen following a path through an obstacle.

Write a lambda expression or method for determining the true cost of reaching two points if they are neighbors.

  • If you want the cost between all neighbors to be the same, then you can simply use the expression (p, q) => 1.

  • Another common possibility is to use the Euclidean distance between points. This will tend to take parts that seem more natural.

  • The euclidean norm is defined for rect and hex grids: EuclideanNorm(GridPoint2) and EuclideanNorm(GridPoint2) . To get the distance between two points, calculate the norm on their difference:

    var distance = Norm(p - q);
    

Here is how using the euclidean distance affects the path when you use all 8 neighbors. (When you use only 4 neighbors, the euclidean distance is the same between all four neighbors, so it is the same as using a constant cost between cells.)

Orthogonal and Diagonal
The image shows a grid of squares in which a pattern of orthogonal and diagonal points can be seen following a Euclidean path through an obstacle.

A third possibility is to use costs for cells. The algorithm requires the cost between two cells; to get this, we take the average of the costs of each cell.

(p, q) => (grid[p].Cost + grid[q].Cost) / 2

Here is how the paths look when weighted costs are used. Note the preferred path is now through the blue cells, the cells with lower cost.

Orthogonal Diagonal Orthogonal and Diagonal
The image shows a grid of squares in which a pattern of orthogonal dots can be seen following a weighted path through an obstacle. The image shows a grid of squares in which a pattern of diagonal dots can be seen following a weighted path through an obstacle. The image shows a grid of squares in which a pattern of diagonal and orthogonal dots can be seen following a weighted path through an obstacle.

Write a lambda expression or method for a cost heuristic between two points. The cost heuristic should always be smaller or equal than the real distance. Here are the heuristic costs you should use in typical situations:

Grid properties Cost Heuristic
Rect grid, orthogonal neighbors (p,q) => 1 (p,q) => RectPoint.ManhattanNorm(p - q)
Rect grid, diagonal neighbors (p,q) => 1 (p,q) => RectPoint.ChebyshevNorm(p - q)
Rect grid, orthogonal and diagonal neighbors (p,q) => 1 (p,q) => RectPoint.ChebyshevNorm(p - q)
Hex grid, orthogonal neighbors (p,q) => 1 (p,q) => RectPoint.HexNorm(p - q)
Any grid, irregular neighbors (p,q) => 1 (p,q) => 1
Rect grid, any neighbors (p,q) => RectPoint.EuclideanNorm(p - q) (p,q) => RectPoint.EuclideanNorm(p - q)
Hex grid, any neighbors (p,q) => PointyHexPoint.EuclideanNorm(p - q) (p,q) => PointyHexPoint.EuclideanNorm(p - q)
Any grid (p,q) => (grid[p].Cost + grid[q].Cost)/2 Use the same heuristic as for (p, q) => 1 but multiply it with the minimum cost between neighbors.