In this tutorial, we are going to try to build a 3D Minesweeper game in Unity. This is part 3. The complete project is here.

Modify the Brick script as below.

public class Brick : MonoBehaviour
{
    public bool mine = false;

    public float radius = 1.42f;

    private List<Brick> mNeighbors;

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

    // Update is called once per frame
    void Update()
    {
        
    }

    private void FindNeighbors()
    {
        var allBricks = GameObject.FindGameObjectsWithTag("Brick");

        mNeighbors = new List<Brick>();

        for (int i = 0; i < allBricks.Length; i++) {
            var brick = allBricks[i];
            var distance = Vector3.Distance(transform.position, brick.transform.position);
            if (0 < distance && distance <= radius) {
                mNeighbors.Add(brick.GetComponent<Brick>());
            }
        }
    }
}

It has two public fields. mine indicates whether Brick has a mine. radius is used to detect whether another Brick is its neighbor, and the default value is 1.42f.

Now you can select some Bricks in the scene to tick the Mine checkbox of them.

NOTE: For improving the runtime performance, you can also make mNeighbors to be public, and invoke FindNeighbors in the OnValidate method instead of in Start so that this work is given to the editor.

Next, we write a method to show the secret of a Brick: If it has a mine, showing a bomb, else showing the number of mines in its neighbors. Before that, we need to build a sprites map. Add the following line inside the Brick class:

private static Dictionary<string, Sprite> mTileImages;

Then write a method BuildSpritesMap and invoke it in the Start (Make sure that MinesweeperSpritesheet.png is placed in the folder Resources/Sprites).

public static void BuildSpritesMap()
{
    if (mTileImages == null) {
        Sprite[] sprites = Resources.LoadAll<Sprite>("Sprites/MinesweeperSpritesheet");
        mTileImages = new Dictionary<string, Sprite>();
        for (int i = 0; i < sprites.Length; i++) {
            mTileImages.Add(sprites[i].name, (Sprite) sprites[i]);
        }
    }
}

Now, we can write our ShowSecret method:

public void ShowSecret()
{
    if (mShowed) return;

    mShowed = true;

    string name;

    if (mine) {
        name = "TileMine";
    } else {
        int num = 0;
        mNeighbors.ForEach(brick => {
            if (brick.mine) num += 1;
        });
        name = $"Tile{num}";
    }

    Sprite sprite;
    if (mTileImages.TryGetValue(name, out sprite))
        tile.sprite = sprite;
}

tile is a public field of type SpriteRenderer. Don’t forget to bind it to the tile sprite renderer which we already have set up to the Brick prefab in part 1 of this tutorial.

When to show the secret of a brick? At the time the character steps on the brick. How to detect this event? Again, we are going to summon the magic of Raycasting. We cast a ray from the character along the downward direction. When this ray hit a brick, we invoke its ShowSecret method.

Add the method below to the CharacterController class and invoke it in the Update method:

private void DetectMine()
{
    Ray ray = new Ray(transform.position, -transform.up);
    RaycastHit hit;
    if (Physics.SphereCast(ray, 0.2f, out hit)) {
        GameObject hitObject = hit.transform.gameObject;
        Brick brick = hitObject.GetComponent<Brick>();
        if (brick != null) {
            brick.ShowSecret();
        }
    }
}

Now our game is almost done. You can play it.

Further Reading