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. Not only are they fun to play, but they’re also fun to port from a real-life board game to a video game. In this tutorial, you’ll build a 3D chess game in Unity. Along the way, you’ll learn how to:
By the time you’ve finished this tutorial, you’ll have created a feature-rich chess game that you can use as a starting point for other board games.
Note: You should have some familiarity with Unity and the C# language. If you want to skill up in C#, the Beginning C# with Unity Screencast series is a great place to start.
Getting StartedDownload the project materials for this tutorial. You can find a link at the top and the bottom of this page. Open the starter project in Unity to get going. Chess is often implemented as a simple 2D game. However, this version is 3D to mimic sitting at a table playing with your friend. Besides… 3D is cool. =] Open the Main scene in the Scenes folder. You’ll see a Board object representing the game board and an object for the GameManager. These objects already have scripts attached.
Enter play mode to view the board and get the pieces set up and ready to go. Moving PiecesThe first step is figuring out which piece to move. Raycasting is a way to find out which tile the user is mousing over. If you aren’t familiar with raycasting in Unity, check out our Introduction to Unity Scripting tutorial or our popular Bomberman tutorial. Once the player selects a piece, you need to generate valid tiles where the piece can move. Then, you need to pick one. You’ll add two new scripts to handle this functionality. Both components have the same basic methods:
This is a basic implementation of the State Machine pattern. If you need more states, you can make this more formal; however, you’ll add complexity. Selecting a TileSelect Board in the Hierarchy. Then, in the Inspector window, click the Add Component button. Now, type TileSelector in the box and click New Script. Finally, click Create and Add to attach the script.
Note: Whenever you create new scripts, take a moment to move them into the appropriate folder. This keeps your Assets folder organized.
Highlighting the Selected TileDouble-click TileSelector.cs to open it and add the following variables inside the class definition:
public GameObject tileHighlightPrefab;
private GameObject tileHighlight;
These variables store the transparent overlay to help indicate which tile you’re pointing at. The prefab is assigned in edit mode and the component tracks and moves around the highlight. Next, add the following lines to
Vector2Int gridPoint = Geometry.GridPoint(0, 0);
Vector3 point = Geometry.PointFromGrid(gridPoint);
tileHighlight = Instantiate(tileHighlightPrefab, point, Quaternion.identity, gameObject.transform);
tileHighlight.SetActive(false);
Note: It’s helpful to refer to coordinates by column and row, which takes the form of a
Vector2Int and is referred to as a GridPoint . Vector2Int has two integer values: x and y. When you need to place an object in the scene, you need the Vector3 point. Vector3 has three float values: x, y and z.
Geometry.cs has helper methods for these conversions:
Next, add
public void EnterState()
{
enabled = true;
}
This re-enables the component when it’s time to select another piece. Then, add the following to
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.SetActive(true);
tileHighlight.transform.position =
Geometry.PointFromGrid(gridPoint);
}
else
{
tileHighlight.SetActive(false);
}
Here, you create a
If the ray intersects a collider, then Since the mouse pointer is over the board, you also enable the highlight tile, so it’s displayed. Finally, select Board in the Hierarchy and click Prefabs in the Project window. Then, drag the Selection-Yellow prefab into the Tile Highlight Prefab slot in the Tile Selector component of the board. Now when you enter play mode, there will be a yellow highlight tile that follows the mouse pointer around. Selecting the PieceTo select a piece, you need to check if the mouse button is down. Add this check inside the
if (Input.GetMouseButtonDown(0))
{
GameObject selectedPiece =
GameManager.instance.PieceAtGrid(gridPoint);
if(GameManager.instance.DoesPieceBelongToCurrentPlayer(selectedPiece))
{
GameManager.instance.SelectPiece(selectedPiece);
// Reference Point 1: add ExitState call here later
}
}
If the mouse button is pressed,
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. Now that you have a piece selected, it’s time to move it to a new tile. Selecting a Move TargetAt this point, This component is similar to Hand Off ControlThe first thing you have to manage is how to hand off control from
private void ExitState(GameObject movingPiece)
{
this.enabled = false;
tileHighlight.SetActive(false);
MoveSelector move = GetComponent<MoveSelector>();
move.EnterState(movingPiece);
}
This hides the tile overlay and disables the Call this method by adding this line to
ExitState(selectedPiece);
Now, open
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);
tileHighlight.SetActive(false);
This component has to start in the disabled state, since you need Move the PieceNext, add the
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
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.SetActive(true);
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
ExitState();
}
}
else
{
tileHighlight.SetActive(false);
}
Finally, add the
private void ExitState()
{
this.enabled = false;
tileHighlight.SetActive(false);
GameManager.instance.DeselectPiece(movingPiece);
movingPiece = null;
TileSelector selector = GetComponent<TileSelector>();
selector.EnterState();
}
You disable this component and hide the tile highlight overlay. Since the piece has moved, you can clear that value, and ask the Back in the editor, with Board selected, drag the tile overlay prefabs from the prefab folder to the slots in
Start play mode and move some pieces around. 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 MovesIn 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 Generate List of Valid TargetsThe general strategy is to take the selected piece and ask This filtered list is passed back to The pawn has the most basic move, so it makes sense to start there. Open Pawn.cs in Pieces, and modify
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)
{
locations.Add(forward);
}
Vector2Int forwardRight = new Vector2Int(gridPoint.x + 1, gridPoint.y + forwardDirection);
if (GameManager.instance.PieceAtGrid(forwardRight))
{
locations.Add(forwardRight);
}
Vector2Int forwardLeft = new Vector2Int(gridPoint.x - 1, gridPoint.y + forwardDirection);
if (GameManager.instance.PieceAtGrid(forwardLeft))
{
locations.Add(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 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
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 Next, you ask
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 Add the following to the bottom of the
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);
}
else
{
highlight = Instantiate(moveLocationPrefab, Geometry.PointFromGrid(loc),
Quaternion.identity, gameObject.transform);
}
locationHighlights.Add(highlight);
}
This section does several things: First, it gets a list of valid locations from the Enemy locations get the attack overlay, and the remainder get the move overlay. Execute the MoveAdd this section below
if (!moveLocations.Contains(gridPoint))
{
return;
}
If the player clicks on a tile that isn’t a valid move, exit from this function. Finally, in MoveSelector.cs, add this code to the end of
foreach (GameObject highlight in locationHighlights)
{
Destroy(highlight);
}
At this point, the player has selected a move so you can remove the overlay objects. Whew! Those were a lot of code changes just to get the pawns to move. Now that you’ve done all the hard work, it’ll be easy to move the other pieces. Next PlayerIt’s not much of a game if only one side gets to move. It’s time to fix that! To let both players play, you’ll have to figure out how to switch between players and where to add the code. Since The actual switch is straightforward. There are variables for the current and other player in The trickier question is: where do you call the swap? A player’s turn is over once they have moved a piece. In GameManager.cs, add the following method to the end of the class:
public void NextPlayer()
{
Player tempPlayer = currentPlayer;
currentPlayer = otherPlayer;
otherPlayer = tempPlayer;
}
Swapping two values requires a third variable to act as a placeholder; otherwise, you’d overwrite one of the values before it can be copied. Switch over to MoveSelector.cs and add the following line to
GameManager.instance.NextPlayer();
That’s it! Enter play mode, and you can now move pieces for both sides. You’re getting close to a real game at this point Capturing PiecesCapturing pieces is an important part of chess. As the saying goes, “It’s all fun and games until someone loses a Knight”. Since the game rules go in
public void CapturePieceAt(Vector2Int gridPoint)
{
GameObject pieceToCapture = PieceAtGrid(gridPoint);
currentPlayer.capturedPieces.Add(pieceToCapture);
pieces[gridPoint.x, gridPoint.y] = null;
Destroy(pieceToCapture);
}
Here, To capture a piece, you move on top of it. So the code to call this step should go in MoveSelector.cs. In
else
{
GameManager.instance.CapturePieceAt(gridPoint);
GameManager.instance.Move(movingPiece, gridPoint);
}
The previous After the enemy piece is gone, the selected piece can move in. Click on play and move the pawns around until you can capture one. Ending the GameA chess game ends when a player captures the opposing King. When you capture a piece, check to see if it’s a King. If so, the game is over. But how do you stop the game? One way is to remove both the In GameManager.cs, in
if (pieceToCapture.GetComponent<Piece>().type == PieceType.King)
{
Debug.Log(currentPlayer.name + " wins!");
Destroy(board.GetComponent<TileSelector>());
Destroy(board.GetComponent<MoveSelector>());
}
It’s not enough to disable these components. The next Destroy is not just for Hit play. Manouever a pawn and take the enemy king. You’ll see a win message printed to the Unity console. As a personal challenge, you can add UI elements to display a “Game Over” message or transition back to a menu screen. Now it’s time to bring out the big guns and move the more powerful pieces! Special Movement
You can use techniques from Have a look at the finished project code if you need a hint. Moving Multiple SpacesPieces that can move multiple spaces in one direction are more challenging. These are the Bishop, Rook and Queen pieces. The Bishop is easier to demonstrate, so let’s start with that one.
Open Bishop.cs, and replace
public override List<Vector2Int> MoveLocations(Vector2Int gridPoint)
{
List<Vector2Int> locations = new List<Vector2Int>();
foreach (Vector2Int dir in BishopDirections)
{
for (int i = 1; i < 8; i++)
{
Vector2Int nextGridPoint = new Vector2Int(gridPoint.x + i * dir.x, gridPoint.y + i * dir.y);
locations.Add(nextGridPoint);
if (GameManager.instance.PieceAtGrid(nextGridPoint))
{
break;
}
}
}
return locations;
}
The In each step, generate a The
Note: If you need to distinguish the forward from the backward direction, or the left from the right, you need to take into account that the black and white pieces are moving in different directions.
For chess, this only matters for pawns, but other games might require that distinction. That’s it! Hit play mode and try it out. Moving the QueenThe Queen is the most powerful piece, so that’s an excellent place to finish. The Queen’s movement is a combination of the Bishop and Rook; the base class has an array of directions for each piece. It would be helpful if you could combine the two. In Queen.cs, replace
public override List<Vector2Int> MoveLocations(Vector2Int gridPoint)
{
List<Vector2Int> locations = new List<Vector2Int>();
List<Vector2Int> directions = new List<Vector2Int>(BishopDirections);
directions.AddRange(RookDirections);
foreach (Vector2Int dir in directions)
{
for (int i = 1; i < 8; i++)
{
Vector2Int nextGridPoint = new Vector2Int(gridPoint.x + i * dir.x, gridPoint.y + i * dir.y);
locations.Add(nextGridPoint);
if (GameManager.instance.PieceAtGrid(nextGridPoint))
{
break;
}
}
}
return locations;
}
The only thing that’s different here is that you’re turning the direction array into a The advantage of the Hit play again, and get the pawns out of the way to make sure everything works. Where to Go From Here?There are several things you can do at this point, like finish the movement for the King, Knight and Rook. If you’re stuck at any point, check out the final project code in the project materials download. There are a few special rules that are not implemented here, such as allowing a Pawn’s first move to be two spaces instead of just one, castling and a few others. The general pattern is to add variables and methods to There are also visual enhancements you can make. For example, the pieces can move smoothly to their target location or the camera can rotate to show the other player’s view during their turn. If you have any questions or comments, or just want to show off your cool 3D chess game, join the discussion below! The post How to Make a Chess Game with Unity appeared first on Ray Wenderlich. How to Make a Chess Game with Unity published first on https://medium.com/@koresol via Tumblr How to Make a Chess Game with Unity
0 Comments
Leave a Reply. |
IYAR
I have worked out on Web Platform Team at leading software company, but this blog IYAR, its content and opinions are my own. I blog about technology, culture, gadgets, diversity, code, the web, where we're going and where we've been. I'm excited about community, social equity, media, entrepreneurship and above all, the open web. Personal Links |