A Match Three Game in Unity: Part 5

Now that we have the main game logic in place for a typical match three game it’s time to break stuff. In this tutorial, I’ll show you how to change the existing logic to create a match three game where the player must only tap the balls to check for matches.

In this type of match three game we don’t need a matchless initial grid, since there must be always at least one match in the grid. The player will tap one of the balls in the matching group and that way the group is removed.

So in the GameScene controller, user input is a lot simpler this time around:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void HandleTouchDown (Vector2 touch)
	{
		if (PAUSED)
			return;
 
		this.worldViewTouch = Camera.main.ScreenToWorldPoint (touch);
 
 
		var ball = BallCloseToPoint (touch);
 
		if (ball != null) {
			selectedBall = ball;
		}
	}
 
	public void HandleTouchUp (Vector2 touch)
	{
 
		if (PAUSED)
			return;
 
		if (selectedBall == null)
			return;
 
		grid.CheckMatchesForBall (selectedBall);
 
		selectedBall = null;
 
	}
 
	public void HandleTouchMove (Vector2 touch)	{
		//Nothing to do here
	}

The Grid class is a lot simpler too, now that we don’t need a matchless grid. This is how we build our grid now, selecting a random ball type for each ball:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void BuildGrid ()
	{
		gridBalls = new List<List<Ball>> ();
 
		GRID_OFFSET_X = (COLUMNS * TILE_SIZE) * 0.5f;
		GRID_OFFSET_Y = (ROWS * TILE_SIZE) * 0.5f;
 
		GRID_OFFSET_X -= TILE_SIZE * 0.5f;
		GRID_OFFSET_Y -= TILE_SIZE * 0.5f;
 
		for (int column = 0; column < COLUMNS; column++) {
 
			var columnBalls = new List<Ball>();
 
			for (int row = 0; row < ROWS; row++) {
 
				var item = Instantiate (gridBallGO) as GameObject;
				var ball = item.GetComponent<Ball>();
 
				ball.SetBallPosition(this, column, row);
				ball.SetType ((Ball.BALL_TYPE) Random.Range(0, 5));
 
				ball.transform.parent = gameObject.transform;
				columnBalls.Add (ball);
			}
			gridBalls.Add(columnBalls);
		}
 
	}

To be fair, an important aspect of this type of Match Three Game is the algorithms to ensure that you have X amount of matches in the grid. That way you can control difficulty. Doing it randomly might not be the best solution. You could use the GridMatches logic from the previous tutorial and make sure you have a grid with at least an X amount of matches. Or you can use a smaller pool of types for the first 25% of the grid, and then different pools for each remainder 25% of the grid. Whatever works best for your game.

The next big change is in the way we search for matches. We must concentrate around the ball the player has selected, so we use a scattershot algorithm from that selection outwards.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public void CheckMatchesForBall (Ball ball) {
 
		matchList.Clear ();
 
 
		for (int column = 0; column < COLUMNS; column++) {
			for (int row = 0; row < ROWS; row++) {
				gridBalls [column] [row].visited = false;
			}
		}
		//search for matches around ball
		var initialResult = GetMatches( ball );
		matchList.AddRange (initialResult);
 
		while (true) {
			var allVisited = true;
			for (var i = matchList.Count - 1; i >= 0 ; i--) {
				var b = matchList [i];
				if (!b.visited) {
					AddMatches (GetMatches (b));
					allVisited = false;
				}
			}
 
 
			if (allVisited) {
				if (matchList.Count > 2) {
					CollapseGrid ();
				}
				return;
			}
		}
	}
 
 
	List<Ball> GetMatches (Ball ball) {
		ball.visited = true;
		var result = new List<Ball> () { ball };
 
		//+ column
		if (ball.column + 1 < COLUMNS) {
			for (var r = -1; r <= 1; r++) {
				if (ball.row + r >= 0 && ball.row + r < ROWS) {
						if (gridBalls [ball.column + 1] [ball.row + r].type == ball.type) {
						result.Add (gridBalls [ball.column + 1] [ball.row + r]);
					}
				}
				r++;
			}
		}
 
		//- column
		if (ball.column - 1 >= 0) {
			for (var r = -1; r <= 1; r++) {
				if (ball.row + r >= 0 && ball.row + r < ROWS) {
					if (gridBalls [ball.column - 1] [ball.row + r].type == ball.type) {
						result.Add (gridBalls [ball.column - 1] [ball.row + r]);
					}
				}
				r++;
			}
		}
		//top
		if (ball.row - 1 >= 0) {
			if (gridBalls [ball.column] [ball.row - 1].type == ball.type) {
				result.Add (gridBalls [ball.column] [ball.row - 1]);
			}
		}
 
		//bottom
		if (ball.row + 1 < ROWS) {
			if (gridBalls [ball.column] [ball.row + 1].type == ball.type) {
				result.Add (gridBalls [ball.column] [ball.row + 1]);
			}
		}
 
		return result;
	}

The Ball class now has a visited property to help us with the check.

And then the last main change is that once the grid collapses and new balls are added we don’t automatically check for matches. Again, in this game the player must be the one finding all the matches, not our code.

1
2
3
4
5
6
7
8
9
10
11
12
private void CollapseTweenDone (Ball ball) {
 
		if (collapseTweens.Contains (ball))
			collapseTweens.Remove (ball);
 
		//are we done with all collapses?
		if (collapseTweens.Count == 0) {
			Debug.Log ("tween done");
			GameView.PAUSED = false;
		}
 
	}

The rest of the logic should be roughly the same.

Next, I’ll show something similar, but using a “connect the dots” logic, so the player can select balls in 8 directions looking for matches.

Here is the final project for the Match Three game with a tap selection engine.