unity

How to Build a Grid-Based Puzzle Game Like Minesweeper in Unity

By Matthias Zarzecki

Puzzle games often take place on a grid containing tiles that have behaviors and properties, and react to rules and inputs. In this series I’ll show you how to build a simple, basic version of the classic game Minesweeper, which is the perfect example to start from for creating your own puzzle games.

Whether you’re making a children’s memory game or a complex strategy title, implementing the basic building blocks of Minesweeper are a great place to start. In the first part of this three-part tutorial series, we’ll build a playing field you can then use to create your own version of the game.

You’ll need Unity for this, and a basic understanding of it. (Check out Build Arkanoid With Unity if you’re new at it.) The source code can be downloaded, but is not necessary to complete this tutorial.

The Rules of Game Minesweeper

Minesweeper is a puzzle game in which you have to locate all the mines in a field. The field size varies by difficulty, and can range from 9×9 tiles (easy) to 16×30 tiles (hard), or any custom dimensions.


By clicking on a tile you “uncover” it. If it is a mine, you lose; if it is empty and at least one mine is in one of the bordering tiles, a number appears, showing the number of mines in the neighboring tiles. If there are no mines in the adjacent tiles, all adjacent tiles are uncovered too.

A tile can marked by right-clicking on it, thereby putting a flag on it. Once all tiles with mines have been correctly marked, the game is won.

Try it out here:

Elements We’ll Need

From the rules above, we can extrapolate the different elements that our simple version of minesweeper will need. These are

  • A grid of tiles
  • Tiles that can contain a mine
  • Tiles that can be interacted with via mouse clicks
  • Tiles that take neighboring tiles into account when reacting to mouse clicks

Building a Basic Tile

Create a new Unity project. Create a Cube and name it Tile. Drag it over to the project folder to turn it into a prefab. We’ll use this non-functional tile to build the playing field, and then later add functionality to it.

Building the Grid Generator

Create a new empty object and name it Grid, and turn it into a prefab as well. This will be the generator of the game field and all tiles within it.

Create a new JS file, name it Grid as well, and add it to the Grid object.

Add the following lines to the Grid script, so that we can begin creating a field:

public var tilePrefab: Tile;
public var numberOfTiles: int = 10;
public var distanceBetweenTiles: float = 1.0;

function Start()
{
    CreateTiles();
}

function CreateTiles()
{

}

Then, drag the Tile prefab onto the Tile Prefab slot of the Grid object. It should look like this:

The numberOfTiles variable will allow you to set the number of tiles to be created. DistanceBetweenTiles defines the distance between them, so that we can adjust the spacing to our liking.

Right now, the grid generator doesn’t do anything. In order to have it create several tiles, add this code the CreateTiles() function:

var xOffset: float = 0.0;

for(var tilesCreated: int = 0; tilesCreated < numberOfTiles; tiles += 1)
{
    xOffset += distanceBetweenTiles;
    Instantiate(tilePrefab, Vector3(transform.position.x + xOffset, transform.position.y, transform.position.z), transform.rotation);
}

If you run the current scene, it should create a line of our tiles, like this:

The function creates copies of the tile prefab—as many as we specified—and places them in a row, a distance of distanceBetweenTiles apart. Try out some different values to find a good spacing.

But for Minesweeper we’ll need a grid, not a line. In order to accomplish that, add this variable at the beginning of the Grid code:

public var tilesPerRow: int = 4;

…and adapt the CreateTiles() function to look like this:

function CreateTiles()
{
    var xOffset: float = 0.0;
    var zOffset: float = 0.0;

    for(var tilesCreated: int = 0; tilesCreated < numberOfTiles; tilesCreated += 1)
    {
        xOffset += distanceBetweenTiles;
        
        if(tilesCreated % tilesPerRow == 0)
        {
            zOffset += distanceBetweenTiles;
            xOffset = 0;
        }
    
        Instantiate(tilePrefab, Vector3(transform.position.x + xOffset, transform.position.y, transform.position.z + zOffset), transform.rotation);
    }
}

If you run it, you should end up with several lines of tiles:

If you set the tilesPerRow variable correctly (like 24 tiles in 6 rows), the generator should create a nice rectangular playing field. If your programming skills are advanced enough, you can try to figure out how to further automate the process. (The version of Minesweeper we’re building will also work with irregularly-shaped fields.)

Turning the Tiles Into Mines

Now that we can create a basic, custom Minesweeper field, we can work on adding actual mines to it.

Create a new JS file, name it Tile, and add it to the Tile prefab. Then, add the following variable to it:

public var isMined: boolean = false;

This will tell us whether the tile has a mine in it.

Assigning mines is a bit trickier. We’ll do this from the Grid generator.

First, three arrays to hold our tiles to the Grid code:

static var tilesAll: Tile[];
static var tilesMined: Array;
static var tilesUnmined: Array;

We also need to initialize them. Put the following lines at the beginning of the CreateTiles() function:

tilesAll = new Tile[numberOfTiles];
tilesMined = new Array();
tilesUnmined = new Array();

Then, change the instantiate command in the CreateTiles() function to look like this:

var newTile = Instantiate(tilePrefab, Vector3(transform.position.x + xOffset, transform.position.y, transform.position.z + zOffset), transform.rotation);
tilesAll[tilesCreated] = newTile;

Now add this command at the end of the CreateTiles() function to start the AssignMines() process:

AssignMines();

The CreateTiles() function should look like this:

function CreateTiles()
{
    tilesAll = new Tile[numberOfTiles];
    tilesMined = new Array();
    tilesUnmined = new Array();
    
    var xOffset: float = 0.0;
    var zOffset: float = 0.0;
    
    for(var tilesCreated: int = 0; tilesCreated < numberOfTiles; tilesCreated += 1)
    {
        xOffset += distanceBetweenTiles;
    
        if(tilesCreated % tilesPerRow == 0)
        {
            zOffset += distanceBetweenTiles;
            xOffset = 0;
        }
    
        var newTile = Instantiate(tilePrefab, Vector3(transform.position.x + xOffset, transform.position.y, transform.position.z + zOffset), transform.rotation);
        tilesAll[tilesCreated] = newTile;
    }
    
    AssignMines();
}

Also add the function AssignMines(), so that we can actually use it:

function AssignMines()
{
    tilesUnmined = tilesAll;

    for(var minesAssigned: int = 0; minesAssigned < numberOfMines; minesAssigned += 1)
    {
        var currentTile: Tile = tilesUnmined[Random.Range(0, tilesUnmined.length)];
        
        tilesMined.Push(currentTile);
        tilesUnmined.Remove(currentTile);
        
        currentTile.GetComponent(Tile).isMined = true;
    }
}

Here is what happens: When a new tile is created in the CreateTiles-function, it is added to the tilesAll-array. All of the tiles in there are then copied into the tilesUnmined-array. From this array we randomly chose one tile to add a mine to. We add a mine to by setting the isMined-variable to true, remove it from the tilesUnmined-array and add it to the tilesMined-array (which we’ll use later). In the end we have randomly placed the specified amount of mines on the playing field.

Right now, a mined tile is not visibly different from an unmined tile. The goal of the game is to figure that out, after all!

In this build you can test out how it is supposed to work. For demonstration purposes, mined tiles show up as red.

And voilá: you now have a Minesweeper field with a custom size!

Making the Tiles More Visually Interesting

Right now, the tiles are standard Unity cubes. Let’s turn them into actual tiles.

In the source files, you’ll find a 3D file called puzzleObjects.fbx. Copy it into your asset folder, so it we can use it in our game. Make sure the file is imported with its size set to 1, so that it fits with the settings we’ve been using so far.

The import settings of the file should look like this:

Then, go the settings of the tile prefab, and swap out the cube mesh for the tileImproved mesh.

While we’re here, press Reset on the Box Collider component of the tile. This will make the collider fit closely around the tile again.

Finally, give the tile a new material, so it doesn’t have the standard white look.

Remember to apply all the changes to the tile-prefab as well, so that new
ones get created when we start a new game. If you try it out, the game
should create the grid using these new tiles instead of the old cubes.

Adding a Number Display to the Tiles

We need a way to display a number in
order for a tile to show us how many mines are adjacent to it. A simple way
to accomplish this is by using a 3D Text, which comes with Unity.

Create one by clicking GameObject > Create Other > 3D Text, and add it to the tile. It should look like this:

Let’s improve that. Rotate the text so it faces upwards. Set the string it currently displays to 0, so that we know what size the text will be. Also adapt the Font Size and Text Size, so that it doesn’t look blurry.

Great!
Now we need to be able to access the 3D text in code. Add the following
variable to the Tile code:

public var displayText: TextMesh;

Drag the 3D text onto the open slot, so that we can access it via code later on.

Remember to apply everything to the tile prefab, and try it out. The new and imporved tiles should now be created.

Conclusion

We have created a functional basis for out puzzle game, but can’t actually play it yet. We’ll add that functionality in the second part of this series.

Source: Nettuts+


0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.