Skip to content

2D Grid-Based Pathfinding Using C# and Unity

2D Grid-Based Pathfinding Using C# and Unity

This tutorial will solve 2D grid-based pathfinding using C# and demonstrate the application in Unity.

We will approach the solution by first modelling the problem, building the fundamental blocks and applying our generic pathfinder to solve the pathfinding problem.

Pathfinding Playground

Click on the image to launch the Pathfinding Playground in WebGL

Contact me

Find the GitHub repository of this tutorial at https://github.com/shamim-akhtar/tutorial-pathfinding/tree/part-3-rectgrid.

This tutorial is Part 3 of my tutorial series on Pathfinding Using C# in Unity.

Our last tutorial, Implement a Generic Pathfinder in Unity using C#, implemented a generic pathfinder in Unity using C#. In this tutorial, we will use that generic pathfinder and apply it to a 2D grid.

The prerequisite for this tutorial is Part 1 – Implement a Generic Pathfinder in Unity using C#.


In Part 1, we implemented a Generic Pathfinder (Prerequisite).

In Part 2, we solved the 8-puzzle problem by applying our Generic Pathfinder (Not a prerequisite but another example use-case of the generic pathfinder).

In Part 3, we solved the graph-based problem by applying our Generic Pathfinder (Not a prerequisite but another example use-case of the generic pathfinder).


To start, let’s create a new Unity scene and name it Demo_RectGridPathFinding.

In this tutorial, we want to visualise our pathfinding. So, we will create some tools and game objects that will allow us to visualise our pathfinding related data. This data includes the cost, the current node, the nodes in the closed list, and the open list.

The Rect Grid Cell Prefab

Let’s create a grid cell prefab. The purpose of this grid cell prefab is to show the state of the cell, whether it is walkable (meaning whether we can go through the cell), whether the pathfinder already searched this cell, is this cell the current node, is it in the open list or is it in the closed list. We also want some text (we will do this later) attached to this prefab to display the heuristic cost, the G cost and the total cost.

  • Create an empty game object and name it RectGridCell_Prefab. Reset the transform to make sure that it is at 0, 0, 0.
  • Create two sprites with RectGridCell_Prefab as the parent. Name one sprite Inner and the other sprite outer.
  • For the outer sprite, make the colour light grey and change the transform values as shown below.
  • For the Inner sprite, make the colour light blue and change the transform values as shown below. Note the change in the Scale values.
  • Your RectGridCell_Prefab should now look like the image below.
  • Select the RectGridCell_Prefab game object from the Hierarchy and add a new script component called RectGridCell_Viz. Open the script in Visual Studio.
  • Add the following serializable variables.
  [SerializeField]
  SpriteRenderer InnerSprite;
  [SerializeField]
  SpriteRenderer OuterSprite;

Code language: C# (cs)
  • Associate these two fields in Unity editor with the Inner and Outer sprites respectively as shown below.
  • Add two methods that will allow us to set the colours of the Inner and Outer sprites through the script.
  public void SetInnerColor(Color col)
  {
    InnerSprite.color = col;
  }
  public void SetOuterColor(Color col)
  {
    OuterSprite.color = col;
  }
Code language: C# (cs)
  • Add a BoxCollider2D component to the RectGridCell_Prefab game object. We will revert to this class later for adding further functionalities.
  • Create a folder called Prefabs in the Resources folder. (Assets/Resources/Prefabs). Drag and drop the RectGridCell_Prefab game object into the Prefabs folder to make it a prefab. After that, you can either delete or hide the original game object. I will prefer to hide it for now.

The Rect Grid

We will now create the rectangular grid so that we can apply our pathfinding in that grid. Go ahead and create a new empty game object. Call this empty game object as RectGrid_Viz. We are adding the _Viz behind to represent that we are creating the game object just for visualisation and nothing else.

  • Select RectGrid_Viz game object, go to the Inspector and add a new script component. Name it RectGrid_Viz. Double-click and open the script in Visual Studio.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RectGrid_Viz : MonoBehaviour
{
}

Code language: C# (cs)
  • Add the following variables.
  // the max number of columns in the grid.
  public int mX;
  // the max number of rows in the grid
  public int mY;

  // The prefab for representing a grid cell. We will 
  // use the prefab to show/visualize the status of the cell
  // as we proceed with our pathfinding.
  [SerializeField]
  GameObject RectGridCell_Prefab;

  GameObject[,] mRectGridCellGameObjects;

  // the 2d array of Vecto2Int.
  // This stucture stores the 2d indices of the grid cells.
  protected Vector2Int[,] mIndices;

Code language: C# (cs)
  • For this tutorial, I am going to use Vector2Int to represent the index of a grid cell. You can use any other type of suitable data structure, like create a structure Point2D or use the Tuple<int, int>.
  • Creating the grid is simple, as shown below.
  // Construct a grid with the max cols and rows.
  protected void Construct(int numX, int numY)
  {
    mX = numX;
    mY = numY;

    mIndices = new Vector2Int[mX, mY];
    mRectGridCellGameObjects = new GameObject[mX, mY];

    // create all the grid cells (Index data) with default values.
    // also create the grid cell game ibjects from the prefab.
    for (int i = 0; i < mX; ++i)
    {
      for (int j = 0; j < mY; ++j)
      {
        mIndices[i, j] = new Vector2Int(i, j);
        mRectGridCellGameObjects[i, j] = Instantiate(
          RectGridCell_Prefab,
          new Vector3(i, j, 0.0f),
          Quaternion.identity);

        // Set the parent for the grid cell to this transform.
        mRectGridCellGameObjects[i, j].transform.SetParent(transform);
      }
    }
  }
Code language: C# (cs)
  • Next, create a new method to set the orthographic camera size and position based on the number of cells in the grid.
  void ResetCamera()
  {
    Camera.main.orthographicSize = mY / 2.0f + 1.0f;
    Camera.main.transform.position = new Vector3(mX / 2.0f - 0.5f, mY / 2.0f - 0.5f, -100.0f);
  }

Code language: C# (cs)
  • Finally, from the Start method call the above two functions.
  private void Start()
  {
    // Constryct the grid and the cell game objects.
    Construct(mX, mY);

    // Reset the camera to a proper size and position.
    ResetCamera();
  }

Code language: C# (cs)
  • In the Unity editor, set the values of MX and MY as 10 and then drag and drop the RectGridCell_Prefab into the Rect Grid Cell_Prefab field (as shown below).

Click Play on the Unity editor. You should see the below diagram.

You can see that all the names generated for the cells are the same. You can change this by setting the name to each instantiated grid cell. Let’s put a name based on the index of the cell. For example, we can set the name of each cell as cell_x_y (or any other form you may prefer).

  • Add the highlighted line to your code in the Construct method.
  // Construct a grid with the max cols and rows.
  protected void Construct(int numX, int numY)
  {
    mX = numX;
    mY = numY;

    mIndices = new Vector2Int[mX, mY];
    mRectGridCellGameObjects = new GameObject[mX, mY];

    // create all the grid cells (Index data) with default values.
    // also create the grid cell game ibjects from the prefab.
    for (int i = 0; i < mX; ++i)
    {
      for (int j = 0; j < mY; ++j)
      {
        mIndices[i, j] = new Vector2Int(i, j);
        mRectGridCellGameObjects[i, j] = Instantiate(
          RectGridCell_Prefab,
          new Vector3(i, j, 0.0f),
          Quaternion.identity);

        // Set the parent for the grid cell to this transform.
        mRectGridCellGameObjects[i, j].transform.SetParent(transform);

        // set a name to the instantiated cell.
        mRectGridCellGameObjects[i, j].name = "cell_" + i + "_" + j;
      }
    }
  }
Code language: JavaScript (javascript)

Now you will see that the names of the cells are a bit more meaningful, as shown below.

The Rect Grid Cell Data Structure

I will now introduce a new pure C# class called the RecdGridCell. We will derive this class from GameAI.Pathfinding.Node.

Add a new C# class called RectGridCell and add the following code into it. This class is the primary node type for a Rect grid-based map. We will derive this class from GameAI.Pathfinding.Node generic class. For a 2D rectangular grid-based map, we will use the Vector2Int type to represent the location of the cell.

using System.Collections.Generic;
using UnityEngine;
using GameAI.PathFinding;

// This is the main node type for a Rect grid
// based map. We will derive this class from
// GameAI.Pathfinding.Node generic class.
// For a 2D rectangular grid based map we 
// will simply use the Vector2Int class to 
// represent the location of the cell.
public class RectGridCell : Node<Vector2Int>
{
  // Is this cell walkable?
  public bool IsWalkable { get; set; }

  // Keep a reference to the grid so that 
  // we can find the neighbours.
  private RectGrid_Viz mRectGrid_Viz;

  // construct the node with the grid and the location.
  public RectGridCell(RectGrid_Viz gridMap, Vector2Int value) 
    : base(value)
  {
    mRectGrid_Viz = gridMap;

    // by default we set the cell to be walkable.
    IsWalkable = true;
  }

  // get the neighbours for this cell.
  // here will will just throw the responsibility
  // to get the neighbours to the grid.
  public override List<Node<Vector2Int>> GetNeighbours()
  {
    return mRectGrid_Viz.GetNeighbourCells(this);
  }
}
Code language: C# (cs)

The highlighted lines of code above show the procedure to get the neighbours for the cell. In this case, we will reroute the call to the RectGrid_Viz script. We have not implemented that function in the RectGrid_Viz yet.

Changes to the RectGrid_Viz script class

Implement the following changes to the RectGrid_Viz class.

  • Add a variable to store the 2d array of RectGridCell.
  // the 2d array of the RectGridCell.
  protected RectGridCell[,] mRectGridCells;Code language: PHP (php)
  • Add the highlighted lines of codes in the Construct function.
// Construct a grid with the max cols and rows.
  protected void Construct(int numX, int numY)
  {
    mX = numX;
    mY = numY;

    mIndices = new Vector2Int[mX, mY];
    mRectGridCellGameObjects = new GameObject[mX, mY];
    mRectGridCells = new RectGridCell[mX, mY];

    // create all the grid cells (Index data) with default values.
    // also create the grid cell game ibjects from the prefab.
    for (int i = 0; i < mX; ++i)
    {
      for (int j = 0; j < mY; ++j)
      {
        mIndices[i, j] = new Vector2Int(i, j);
        mRectGridCellGameObjects[i, j] = Instantiate(
          RectGridCell_Prefab,
          new Vector3(i, j, 0.0f),
          Quaternion.identity);

        // Set the parent for the grid cell to this transform.
        mRectGridCellGameObjects[i, j].transform.SetParent(transform);

        // set a name to the instantiated cell.
        mRectGridCellGameObjects[i, j].name = "cell_" + i + "_" + j;

        // create the RectGridCells
        mRectGridCells[i, j] = new RectGridCell(this, mIndices[i, j]);

        // set a reference to the RectGridCell_Viz
        RectGridCell_Viz rectGridCell_Viz =
          mRectGridCellGameObjects[i, j].GetComponent<RectGridCell_Viz>();
        if(rectGridCell_Viz != null)
        {
          rectGridCell_Viz.RectGridCell = mRectGridCells[i, j];
        }
      }
    }
  }
Code language: C# (cs)
  • Add a property in RectGridCell_Viz to keep a reference to the corresponding RectGridCell.
  [SerializeField]
  SpriteRenderer InnerSprite;
  [SerializeField]
  SpriteRenderer OuterSprite;

  public RectGridCell RectGridCell;

Code language: C# (cs)
  • Add the GetNeighbourCells function in the RectGrid_Viz class.
  // get neighbour cells for a given cell.
  public List<Node<Vector2Int>> GetNeighbourCells(Node<Vector2Int> loc)
  {
    List<Node<Vector2Int>> neighbours = new List<Node<Vector2Int>>();

    int x = loc.Value.x;
    int y = loc.Value.y;

    // Check up.
    if (y < mY - 1)
    {
      int i = x;
      int j = y + 1;

      if (mRectGridCells[i, j].IsWalkable)
      {
        neighbours.Add(mRectGridCells[i, j]);
      }
    }
    // Check top-right
    if (y < mY - 1 && x < mX - 1)
    {
      int i = x + 1;
      int j = y + 1;

      if (mRectGridCells[i, j].IsWalkable)
      {
        neighbours.Add(mRectGridCells[i, j]);
      }
    }
    // Check right
    if (x < mX - 1)
    {
      int i = x + 1;
      int j = y;

      if (mRectGridCells[i, j].IsWalkable)
      {
        neighbours.Add(mRectGridCells[i, j]);
      }
    }
    // Check right-down
    if (x < mX - 1 && y > 0)
    {
      int i = x + 1;
      int j = y - 1;

      if (mRectGridCells[i, j].IsWalkable)
      {
        neighbours.Add(mRectGridCells[i, j]);
      }
    }
    // Check down
    if (y > 0)
    {
      int i = x;
      int j = y - 1;

      if (mRectGridCells[i, j].IsWalkable)
      {
        neighbours.Add(mRectGridCells[i, j]);
      }
    }
    // Check down-left
    if (y > 0 && x > 0)
    {
      int i = x - 1;
      int j = y - 1;

      if (mRectGridCells[i, j].IsWalkable)
      {
        neighbours.Add(mRectGridCells[i, j]);
      }
    }
    // Check left
    if (x > 0)
    {
      int i = x - 1;
      int j = y;

      Vector2Int v = mIndices[i, j];

      if (mRectGridCells[i, j].IsWalkable)
      {
        neighbours.Add(mRectGridCells[i, j]);
      }
    }
    // Check left-top
    if (x > 0 && y < mY - 1)
    {
      int i = x - 1;
      int j = y + 1;

      if (mRectGridCells[i, j].IsWalkable)
      {
        neighbours.Add(mRectGridCells[i, j]);
      }
    }

    return neighbours;
  }

Code language: PHP (php)

The above function returns the neighbouring cells for a given cell. It will ignore any cell that is marked as non-walkable.

The neighbouring cells for a rectangular grid-based map where an NPC can traverse all the adjacent cells, including diagonal cells.

If you do not allow the movement to diagonal cells, you will have to change the above function and remove the diagonal cells from the neighbours’ list.

We are almost done with our setup for testing and visualising our pathfinding on a 2d rectangular grid-based map. However, before starting with the pathfinding, let’s add two more functionalities.

The first functionality will allow toggling a cell to be walkable/non-walkable by left-clicking on the cell, and the second functionality will visualise our non-playable character, NPC.

Toggle Walkable/Non-Walkable

We will allow toggling a cell to be non-walkable and walkable by clicking on the cell with the left mouse button for testing and flexibility. This feature will enable us to change a cell at runtime and see how our pathfinder behave.

  • Add the Update method as following.
  private void Update()
  {
    if(Input.GetMouseButtonDown(0))
    {
      RayCastAndToggleWalkable();
    }
  }
Code language: C# (cs)
  • Implement the RayCastAndToggleWalkable method.
  // toggling of walkable/non-walkable cells.
  public void RayCastAndToggleWalkable()
  {
    Vector2 rayPos = new Vector2(
        Camera.main.ScreenToWorldPoint(Input.mousePosition).x,
        Camera.main.ScreenToWorldPoint(Input.mousePosition).y);
    RaycastHit2D hit = Physics2D.Raycast(rayPos, Vector2.zero, 0f);

    if (hit)
    {
      GameObject obj = hit.transform.gameObject;
      RectGridCell_Viz sc = obj.GetComponent<RectGridCell_Viz>();
      ToggleWalkable(sc);
    }
  }

Code language: C# (cs)
  • Now, add the ToggleWalkable function.
  public void ToggleWalkable(RectGridCell_Viz sc)
  {
    if (sc == null)
      return;

    int x = sc.RectGridCell.Value.x;
    int y = sc.RectGridCell.Value.y;

    sc.RectGridCell.IsWalkable = !sc.RectGridCell.IsWalkable;

    if (sc.RectGridCell.IsWalkable)
    {
      sc.SetInnerColor(COLOR_WALKABLE);
    }
    else
    {
      sc.SetInnerColor(COLOR_NON_WALKABLE);
    }
  }

Code language: C# (cs)
  • Add two public variables called COLOR_WALKABLE and COLOR_NON_WALKABLE of type Color to allow the user to set the values through the Unity editor.
  public Color COLOR_WALKABLE = new Color(42/255.0f, 99/255.0f, 164/255.0f, 1.0f);
  public Color COLOR_NON_WALKABLE = new Color(0.0f, 0.0f, 0.0f, 1.0f);

Code language: PHP (php)

Click Play. You should now be able to left-click on a cell and toggle it from walkable to non-walkable and vice-versa. Below is a sample screenshot after I clicked some of the cells to make them non-walkable.

Add an NPC

We will now implement our simple NPC that will move to a selected destination within the grid. Of course, for now, the movement will not take into account pathfinding as we have not yet implemented pathfinding.

  • Create a new empty game object and name it NPC. Reset the transform so that it is at the origin. Add a Circle sprite with NPC as the parent. Change the scale of the Circle to be 0.6, 0.6, 0.6, as shown below.

You can also choose a colour for the circle, as shown above.

  • Duplicate the NPC game object and rename it to Destination. Change the Circle for the Destination sprite to be red in colour and the scale to be 0.8,0.8, 1.0. We will use this game object to show our destination cell for the NPC to move to.
  • Select the NPC and add a new script component. Name it NPCMovement. Double-click and open the script in Visual Studio.
  • Add the following code to the NPCMovement class.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NPCMovement : MonoBehaviour
{
  public float Speed = 1.0f;
  public Queue<Vector2> mWayPoints = new Queue<Vector2>();

  // Start is called before the first frame update
  void Start()
  {
    StartCoroutine(Coroutine_MoveTo());
  }

  public void AddWayPoint(Vector2 pt)
  {
    mWayPoints.Enqueue(pt);
  }

public void SetDestination(
    RectGrid_Viz map, 
    RectGridCell destination)
  {
    // we do not have pathfinding yet, so
    // we just add the destination as a waypoint.
    AddWayPoint(destination.Value);
  }

  public IEnumerator Coroutine_MoveTo()
  {
    while (true)
    {
      while (mWayPoints.Count > 0)
      {
        yield return StartCoroutine(
          Coroutine_MoveToPoint(
            mWayPoints.Dequeue(), 
            Speed));
      }
      yield return null;
    }
  }

  // coroutine to move smoothly
  private IEnumerator Coroutine_MoveOverSeconds(
    GameObject objectToMove, 
    Vector3 end, 
    float seconds)
  {
    float elapsedTime = 0;
    Vector3 startingPos = objectToMove.transform.position;
    while (elapsedTime < seconds)
    {
      objectToMove.transform.position = 
        Vector3.Lerp(startingPos, end, (elapsedTime / seconds));
      elapsedTime += Time.deltaTime;

      yield return new WaitForEndOfFrame();
    }
    objectToMove.transform.position = end;
  }

  IEnumerator Coroutine_MoveToPoint(Vector2 p, float speed)
  {
    Vector3 endP = new Vector3(p.x, p.y, transform.position.z);
    float duration = (transform.position - endP).magnitude / speed;
    yield return StartCoroutine(
      Coroutine_MoveOverSeconds(
        transform.gameObject,
        endP,
        duration));
  }
}

Code language: C# (cs)

In the above code, we run a coroutine continuously that moves the NPC to the set of waypoints. In the SetDestination function, we add the destination point as a waypoint. Once we implement our pathfinding, we will modify this function to use the pathfinder.

Make the following changes to the RectGrid_Viz script.

  • Add the variables to hold the references for the NPCMovement and the Destination transform.
  public Transform mDestination;
  public NPCMovement mNPCMovement;
Code language: C# (cs)
  • Then we modify the Update method to allow the right mouse button-click to set the destination cell.
  private void Update()
  {
    if (Input.GetMouseButtonDown(0))
    {
      RayCastAndToggleWalkable();
    }
    if (Input.GetMouseButtonDown(1))
    {
      RayCastAndSetDestination();
    }
  }

Code language: C# (cs)
  • Now, we add the RayCastAndSetDestination method.
  void RayCastAndSetDestination()
  {
    Vector2 rayPos = new Vector2(
        Camera.main.ScreenToWorldPoint(Input.mousePosition).x,
        Camera.main.ScreenToWorldPoint(Input.mousePosition).y);
    RaycastHit2D hit = Physics2D.Raycast(rayPos, Vector2.zero, 0f);

    if (hit)
    {
      GameObject obj = hit.transform.gameObject;
      RectGridCell_Viz sc = obj.GetComponent<RectGridCell_Viz>();
      if (sc == null) return;

      Vector3 pos = mDestination.position;
      pos.x = sc.RectGridCell.Value.x;
      pos.y = sc.RectGridCell.Value.y;
      mDestination.position = pos;

      // Set the destination to the NPC.
      mNPCMovement.SetDestination(this, sc.RectGridCell);
    }
  }


Code language: C# (cs)

Now associate the NPCMovement and the Destination to the corresponding fields of the RectGrid_Viz fields in the Unity editor.

Click Play. You should be now able to set the destination by right-clicking on any cell. This setting of destination will also move the NPC to the selected cell. I have shown this in the video below.

In the video above, the left mouse button click toggles a cell walkable and non-walkable; the right mouse button click selects the destination and moves the NPC. This video shows the behaviour before we implement pathfinding. So the NPC will ignore the non-walkable cells.

We have implemented most of our scaffolds to finally use the generic pathfinder to apply pathfinding into a rectangular grid-based map.

Finally, Apply Generic Pathfinder

We will now apply our generic pathfinder for pathfinding on the grid. To do so, we will add a PathFinder to our NPCMovement class. We can use the AStarPathFinder.

  • Open the NPCMomement script in Visual Studio and add the following highlighted line.
public class NPCMovement : MonoBehaviour
{
  public float Speed = 1.0f;
  public Queue<Vector2> mWayPoints = new Queue<Vector2>();

  PathFinder<Vector2Int> mPathFinder = new AStarPathFinder<Vector2Int>();

Code language: C# (cs)

Amend the Start method by adding the highlighted lines.

  // Start is called before the first frame update
  void Start()
  {
    mPathFinder.onSuccess = OnSuccessPathFinding;
    mPathFinder.onFailure = OnFailurePathFinding;
    mPathFinder.HeuristicCost = RectGrid_Viz.GetManhattanCost;
    mPathFinder.NodeTraversalCost = RectGrid_Viz.GetEuclideanCost;
    StartCoroutine(Coroutine_MoveTo());
  }

Code language: C# (cs)

We did not implement the GetManhattanCost and the GetEuclideanCost yet. So, open up RectGrid_Viz and add the following functions.

  public static float GetManhattanCost(
    Vector2Int a, 
    Vector2Int b)
  {
    return Mathf.Abs(a.x - b.x) + Mathf.Abs(a.y - b.y);
  }

  public static float GetEuclideanCost(
    Vector2Int a, 
    Vector2Int b)
  {
    return GetCostBetweenTwoCells(a, b);
  }

  public static float GetCostBetweenTwoCells(
    Vector2Int a, 
    Vector2Int b)
  {
    return Mathf.Sqrt(
            (a.x - b.x) * (a.x - b.x) +
            (a.y - b.y) * (a.y - b.y)
        );
  }

Code language: C# (cs)

We will also add another helper method that returns a RectGridCell based on x and y values.

  public RectGridCell GetRectGridCell(int x, int y)
  {
    if(x >= 0 && x < mX && y >=0 && y < mY)
    {
      return mRectGridCells[x, y];
    }
    return null;
  }

Code language: C# (cs)

In the NPCMovement class, amend the SetDestination method as following

  public void SetDestination(
    RectGrid_Viz map, 
    RectGridCell destination)
  {
    //// we do not have pathfinding yet, so
    //// we just add the destination as a waypoint.
    //AddWayPoint(destination.Value);
    // Now we have a pathfinder.
    if (mPathFinder.Status == PathFinderStatus.RUNNING)
    {
      Debug.Log("Pathfinder already running. Cannot set destination now");
      return;
    }
    // remove all waypoints from the queue.
    mWayPoints.Clear();
    // new start location is previous destination.
    RectGridCell start = map.GetRectGridCell(
      (int)transform.position.x, 
      (int)transform.position.y);
    if (start == null) return;
    mPathFinder.Initialize(start, destination);
    StartCoroutine(Coroutine_FindPathSteps());
  }
Code language: JavaScript (javascript)

In the above code, we can abort all previous waypoints from the queue as soon as we are setting a new destination. We do this activity so that we can apply pathfinding immediately from wherever the NPC is currently.

Then, we implement the Coroutine_FindPathSteps.

  IEnumerator Coroutine_FindPathSteps()
  {
    while(mPathFinder.Status == PathFinderStatus.RUNNING)
    {
      mPathFinder.Step();
      yield return null;
    }
  }Code language: C# (cs)

NOTE: There can be a minor misalignment due to converting float to an integer to identify the cell. You can overcome this in many ways. I will leave that for you to figure out.

Implement the OnSuccessPathFinding and OnFailurePathFinding delegates as follows

  void OnSuccessPathFinding()
  {
    PathFinder<Vector2Int>.PathFinderNode node = mPathFinder.CurrentNode;
    List<Vector2Int> reverse_indices = new List<Vector2Int>();
    while(node != null)
    {
      reverse_indices.Add(node.Location.Value);
      node = node.Parent;
    }
    for(int i = reverse_indices.Count -1; i >= 0; i--)
    {
      AddWayPoint(new Vector2(reverse_indices[i].x, reverse_indices[i].y));
    }
  }

  void OnFailurePathFinding()
  {
    Debug.Log("Error: Cannot find path");
  }

Code language: C# (cs)

Note that we accumulate the points from the destination to the start. So, we traverse the list from behind and add the waypoints.

And we are done! Click Play and see the behaviour. Left mouse button click to toggle walkable, non-walkable cells and right mouse button click to set destination. This time around, the NPC will find the correct path and reach the target.

See below the video.

In this tutorial, you may have realised that we have spent most of the time doing the necessary setup and scaffolding to visualise pathfinding. The actual pathfinding integration with our generic pathfinder was a very minimal effort.

Read My Other Tutorials

  1. 8-Puzzle Problem Using A* in C# and Unity
  2. Create a Jigsaw Puzzle Game in Unity
  3. Implement a Generic Pathfinder in Unity using C#
  4. Create a Jigsaw Puzzle Game in Unity
  5. Generic Finite State Machine Using C#
  6. Implement Bezier Curve using C# in Unity
  7. Create a Jigsaw Tile from an Existing Image
  8. Create a Jigsaw Board from an Existing Image
  9. Solving 8 puzzle problem using A* star search
  10. A Configurable Third-Person Camera in Unity
  11. Player Controls With Finite State Machine Using C# in Unity
  12. Finite State Machine Using C# Delegates in Unity
  13. Enemy Behaviour With Finite State Machine Using C# Delegates in Unity
  14. Augmented Reality – Fire Effect using Vuforia and Unity
  15. Implementing a Finite State Machine Using C# in Unity
  16. Solving 8 puzzle problem using A* star search in C++
  17. What Are C# Delegates And How To Use Them
  18. How to Generate Mazes Using Depth-First Algorithm

8 thoughts on “2D Grid-Based Pathfinding Using C# and Unity”

  1. Thanks for a great tutorial,
    You seem to be missing this coroutine: StartCoroutine(Coroutine_FindPathSteps());
    and where node.Parent is set (I assume during find path).

    1. Hello,
      Thank you for pointing that out.
      I have amended the tutorial to include the coroutine.

      IEnumerator Coroutine_FindPathSteps()
      {
      while(mPathFinder.Status == PathFinderStatus.RUNNING)
      {
      mPathFinder.Step();
      yield return null;
      }
      }

      Do note that you can read the implementation of the generic PathFinder in Part 1 of the tutorial. “Implement a Generic Pathfinder in Unity using C#

      You can also find the entire source code in my GitHub.

      Good luck! Do let me know if you require any further assistance.

  2. Good Evening Shamim,

    Loving this tutorial so far.

    I am curious if you could add another part to this series where you go about modifying the grid along with the pathfinding to Isometric view.

    Regards,

    1. Hi there. Okay, I will work on such a tutorial and inform you once I have completed it. But, I can assure you that once you have gotten the basic idea of pathfinding on a 2D grid, then you could apply the same underlying methodology. For an isometric view, only the camera view and controls would be different.

  3. Pretty nice post. I just stumbled upon your blog
    and wanted to say that I have truly enjoyed browsing your blog posts.
    After all I’ll be subscribing to your rss feed and I hope you write again soon!

Leave a Reply

Your email address will not be published. Required fields are marked *