How to Make a Chess Game with Unity

Not every successful game involves shooting aliens or saving the world. Board games, and chess, in particular, have a history that spans thousands of years. In this tutorial, you’ll build a 3D chess game in Unity. By Brian Broom.

Leave a rating/review
Download materials
Save for later
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Selecting the Piece

To select a piece, you need to check if the mouse button is down. Add this check inside the if block, just after the point where you enable the tile highlight:

if (Input.GetMouseButtonDown(0))
    GameObject selectedPiece = 
    // Reference Point 1: add ExitState call here later

If the mouse button is pressed, GameManager provides you the piece at that location. You also have to make sure this piece belongs to the current player since players can’t move their opponent’s pieces.

Note: In a complex game like this, it’s helpful to assign clear responsibilities to your components. Board deals only with displaying and highlighting pieces. GameManager keeps track of the GridPoint values of the piece locations. It also has helper methods to answer questions about where pieces are and to which player they belong.

Enter play mode and select a piece.

highlighted chess pieces

Now that you have a piece selected, it’s time to move it to a new tile.

Selecting a Move Target

At this point, TileSelector has done its job. It’s time to introduce the other component: MoveSelector.

This component is similar to TileSelector. Just like before, select the Board object in the Hierarchy, add a new component and name it MoveSelector.

Hand Off Control

The first thing you have to manage is how to hand off control from TileSelector to MoveSelector. You can use ExitState for this. In TileSelector.cs, add this method:

private void ExitState(GameObject movingPiece)
    this.enabled = false;
    MoveSelector move = GetComponent<MoveSelector>();

This hides the tile overlay and disables the TileSelector component. In Unity, you can’t call the Update method of disabled components. Since you want to call the Update method of the new component now, disabling the old component prevents any interference.

Call this method by adding this line to Update, just after Reference Point 1:


Now, open MoveSelector and add these instance variables at the top of the class:

public GameObject moveLocationPrefab;
public GameObject tileHighlightPrefab;
public GameObject attackLocationPrefab;

private GameObject tileHighlight;
private GameObject movingPiece;

These hold the mouse highlight, move locations and attack location tile overlays, as well as the instantiated highlight tile and the piece that was selected in the previous step.

Next, add the following set up code to Start:

this.enabled = false;
tileHighlight = Instantiate(tileHighlightPrefab, Geometry.PointFromGrid(new Vector2Int(0, 0)),
    Quaternion.identity, gameObject.transform);

This component has to start in the disabled state, since you need TileSelector to run first. Then, you load the highlight overlay like before.

Move the Piece

Next, add the EnterState method:

public void EnterState(GameObject piece)
    movingPiece = piece;
    this.enabled = true;

When this method is called, it stores the piece being moved and enables itself.

Add these lines to the Update method of MoveSelector:

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

RaycastHit hit;
if (Physics.Raycast(ray, out hit))
    Vector3 point = hit.point;
    Vector2Int gridPoint = Geometry.GridFromPoint(point);

    tileHighlight.transform.position = Geometry.PointFromGrid(gridPoint);
    if (Input.GetMouseButtonDown(0))
        // Reference Point 2: check for valid move location
        if (GameManager.instance.PieceAtGrid(gridPoint) == null)
            GameManager.instance.Move(movingPiece, gridPoint);
        // Reference Point 3: capture enemy piece here later

Update in this case is similar to TileSelector and uses the same Raycast check to see what tile the mouse is over. However, this time when the mouse button is clicked, you call GameManager to move the piece to the new tile.

Finally, add the ExitState method to clean up and prepare for the next move:

private void ExitState()
    this.enabled = false;
    movingPiece = null;
    TileSelector selector = GetComponent<TileSelector>();

You disable this component and hide the tile highlight overlay. Since the piece has moved, you can clear that value, and ask the GameManager to unhighlight the piece. Then, you call EnterState on TileSelector to start the process all over again.

Back in the editor, with Board selected, drag the tile overlay prefabs from the prefab folder to the slots in MoveSelector:


  • Move Location Prefab should be Selection-Blue
  • Tile Highlight Prefab should be Selection-Yellow.
  • Attack Location Prefab should be Selection-Red

You can tweak the colors by adjusting the materials.

assigning overlays to selections

Start play mode and move some pieces around.

chess board with pieces moved randomly

You’ll notice that you can move pieces to any unoccupied location. That can make for a very confusing game of chess! The next step is to make sure pieces move according to the rules of the game.

Finding Legal Moves

In Chess, each piece has different movements it can legally make. Some can move in any direction, some can move any number of spaces, and some can only move in one direction. How do you keep track of all the options?

One way is to have an abstract base class that represents all pieces, and then have concrete subclasses override a method to generate move locations.

Another question to answer is: “Where should you generate the list of moves?”

One place that makes sense is EnterState in MoveSelector. This is where you generate overlay tiles to show the player where they can move, so it makes the most sense.

Generate List of Valid Targets

The general strategy is to take the selected piece and ask GameManager for a list of valid targets (a.k.a. moves). GameManager will use the piece subclass to generate a list of possible targets. Then, it will filter out positions that are off the board or occupied.

This filtered list is passed back to MoveSelector, which highlights the legal moves and waits for the player’s selection.

The pawn has the most basic move, so it makes sense to start there.

Open Pawn.cs in Pieces, and modify MoveLocations so that it looks like this:

public override List MoveLocations(Vector2Int gridPoint) 
    var locations = new List<Vector2Int>();

    int forwardDirection = GameManager.instance.currentPlayer.forward;
    Vector2Int forward = new Vector2Int(gridPoint.x, gridPoint.y + forwardDirection);
    if (GameManager.instance.PieceAtGrid(forward) == false)

    Vector2Int forwardRight = new Vector2Int(gridPoint.x + 1, gridPoint.y + forwardDirection);
    if (GameManager.instance.PieceAtGrid(forwardRight))

    Vector2Int forwardLeft = new Vector2Int(gridPoint.x - 1, gridPoint.y + forwardDirection);
    if (GameManager.instance.PieceAtGrid(forwardLeft))

    return locations;

This does several things:

This code first creates an empty list to store locations. Next, it creates a location representing “forward” one square.

Since the white and black pawns move in different directions, the Player object stores a value representing which way the pawns can move. For one player this value is +1, while the value is -1 for the opponent.

Pawns have a peculiar movement profile and several special rules. Although they can move forward one square, they can’t capture an opposing piece in that square; they can only capture on the forward diagonals. Before adding the forward tile as a valid location, you have to check to see if there’s already another piece occupying that spot. If not, you can add the forward tile to the list.

For the capture spots, again, you have to check to see if there’s already a piece at that location. If there is, you can capture it.

You don’t need to worry just yet about checking if it’s the player’s or the opponent’s piece — you’ll work that out later.

In GameManager.cs, add this method just after the Move method:

public List MovesForPiece(GameObject pieceObject)
    Piece piece = pieceObject.GetComponent();
    Vector2Int gridPoint = GridForPiece(pieceObject);
    var locations = piece.MoveLocations(gridPoint);

    // filter out offboard locations
    locations.RemoveAll(tile => tile.x < 0 || tile.x > 7
        || tile.y < 0 || tile.y > 7);

    // filter out locations with friendly piece
    locations.RemoveAll(tile => FriendlyPieceAt(tile));

    return locations;

Here, you get the Piece component from the game piece, as well as its current location.

Next, you ask GameManager for a list of locations for this piece and filter out any invalid values.

RemoveAll is a useful function that uses a callback expression. This method looks at each value in the list, passing it into an expression as tile. If that expression evaluates to true, then the value is removed from the list.

This first expression removes locations with an x or y value that would place the piece off of the board. The second filter is similar, but it removes any locations that have a friendly piece.

In MoveSelector.cs, add these instance variables at the top of the class:

private List<Vector2Int> moveLocations;
private List<GameObject> locationHighlights;

The first stores a list of GridPoint values for move locations; the second stores a list of overlay tiles showing whether the player can move to that location.

Add the following to the bottom of the EnterState method:

moveLocations = GameManager.instance.MovesForPiece(movingPiece);
locationHighlights = new List<GameObject>();

foreach (Vector2Int loc in moveLocations)
    GameObject highlight;
    if (GameManager.instance.PieceAtGrid(loc))
        highlight = Instantiate(attackLocationPrefab, Geometry.PointFromGrid(loc),
            Quaternion.identity, gameObject.transform);
        highlight = Instantiate(moveLocationPrefab, Geometry.PointFromGrid(loc),
            Quaternion.identity, gameObject.transform);

This section does several things:

First, it gets a list of valid locations from the GameManager and makes an empty list to store the tile overlay objects. Next, it loops over each location in the list. If there is already a piece at that location, then it must be an enemy piece, because the friendly ones were already filtered out.

Enemy locations get the attack overlay, and the remainder get the move overlay.

Brian Broom


Brian Broom


Toby Flint

Tech Editor

Sean Duffy

Final Pass Editor

Over 300 content creators. Join our team.