Bubble Shooter Game in Unity: Part 4

In a bubble shooter game, just like in a match three game the level of difficulty comes from how easy it is to find good matches.

But here the player is limited to the bottom of the grid only, at least when the game begins. So it’s a lot more important to ensure there are enough matches to be made.

A note: The logic in this step of the tutorial can be changed in a gazillion different ways, and it’s all down to how many matches you want available to the player and how to easily control difficulty.

Besides this, the Grid needs to display only a few rows at the beginning of the game, and I need the ability to add new lines.

(By the way, I’ll change this grid to a hex grid in a later step.)

Here’s the Grid class:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Grid : MonoBehaviour {

public int ROWS = 8;

public int COLUMNS = 6;

public float TILE_SIZE = 0.68f;

public float changeTypeRate = 0.5f;

public int lines = 5;

public GameObject gridBallGO;

[HideInInspector]
public float GRID_OFFSET_X = 0;

[HideInInspector]
public float GRID_OFFSET_Y = 0;

[HideInInspector]
public List> gridBalls;

private List typePool;

private Ball.BALL_TYPE lastType;

void Start () {

lastType = (Ball.BALL_TYPE)Random.Range (0, 5);
typePool = new List ();

var i = 0;
var total = 10000;
while (i < total) { typePool.Add (GetBallType ()); i++; } Shuffle(typePool); BuildGrid (); } public void AddLine () { //does top line have visible bubbles var emptyFirstRow = true; foreach (var b in gridBalls[0]) { if (b.gameObject.activeSelf) { emptyFirstRow = false; break; } } if (!emptyFirstRow) { var r = ROWS - 2; while (r >= 0) {
foreach (var b in gridBalls[r]) {
if (b.gameObject.activeSelf) {
gridBalls [r + 1] [b.column].gameObject.SetActive (true);
gridBalls [r + 1] [b.column].SetType (b.type);
} else {
gridBalls [r + 1] [b.column].gameObject.SetActive (false);
}
}
r--;
}
}

foreach (var b in gridBalls[0]) {
b.SetType (typePool [0]);
typePool.RemoveAt (0);
b.gameObject.SetActive (true);
}
}

void BuildGrid ()
{
gridBalls = new List> ();

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 row = 0; row < ROWS; row++) { var rowBalls = new List();

for (int column = 0; column < COLUMNS; column++) { var item = Instantiate (gridBallGO) as GameObject; var ball = item.GetComponent();

ball.SetBallPosition(this, column, row);
ball.SetType (typePool[0]);
typePool.RemoveAt (0);

ball.transform.parent = gameObject.transform;
rowBalls.Add (ball);

if (gridBalls.Count > lines) {
ball.gameObject.SetActive (false);
}
}

gridBalls.Add(rowBalls);
}
}

Ball.BALL_TYPE GetBallType () {
var random = Random.Range (0.0f, 1.0f);
if (random > changeTypeRate) {
lastType = (Ball.BALL_TYPE)Random.Range (0, 5);
}
return lastType;
}

private static System.Random rng = new System.Random();
public static void Shuffle(IList list) {
int n = list.Count;
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}

The properties controlling the number of matches are these:

private List typePool;
private Ball.BALL_TYPE lastType;
public float changeTypeRate = 0.5f;

I start the Grid by creating a pool of types which I shuffle:

var i = 0;
var total = 10000;
while (i < total) { typePool.Add (GetBallType ()); i++; } Shuffle(typePool); ... Ball.BALL_TYPE GetBallType () { var random = Random.Range (0.0f, 1.0f); if (random > changeTypeRate) {
lastType = (Ball.BALL_TYPE)Random.Range (0, 5);
}
return lastType;
}

The changeTypeRate property is used to determine if when I create new ball, I use the same ball type I used last time.

The higher the rate, the more repetitions we have. The lower the ratio, the harder the game gets.

There are many ways you can apply a similar logic here. Random might not be the best solution to be honest.

The other element that will influence how easy the grid is is the way you shuffle this pool.

When creating a ball I set its type from the first index of the pool and I remove that index.

void BuildGrid () {
gridBalls = new List> ();

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 row = 0; row < ROWS; row++) { var rowBalls = new List();

for (int column = 0; column < COLUMNS; column++) { var item = Instantiate (gridBallGO) as GameObject; var ball = item.GetComponent();

ball.SetBallPosition(this, column, row);
ball.SetType (typePool[0]);
typePool.RemoveAt (0);

ball.transform.parent = gameObject.transform;
rowBalls.Add (ball);

if (gridBalls.Count > lines) {
ball.gameObject.SetActive (false);
}
}

gridBalls.Add(rowBalls);
}
}

Notice the use of the property "lines" near the bottom of the BuildGrid method. This controls the number of visible lines. So the Grid is complete, but I only display the top X number of lines.

Notice that I don't have to set the type of balls in invisible rows, but I'm lazy.
I could have easily done this instead:

for (int row = 0; row < ROWS; row++) {

	var rowBalls = new List();

for (int column = 0; column < COLUMNS; column++) { var item = Instantiate (gridBallGO) as GameObject; var ball = item.GetComponent();

ball.SetBallPosition(this, column, row);

ball.transform.parent = gameObject.transform;
rowBalls.Add (ball);

if (gridBalls.Count > lines) {
ball.gameObject.SetActive (false);
} else {
ball.SetType (typePool[0]);
typePool.RemoveAt (0);

}
}

gridBalls.Add(rowBalls);
}

And here is how I add a new line:

public void AddLine () {
//does top line have visible bubbles
var emptyFirstRow = true;
foreach (var b in gridBalls[0]) {
if (b.gameObject.activeSelf) {
emptyFirstRow = false;
break;
}
}

if (!emptyFirstRow) {
var r = ROWS - 2;
while (r >= 0) {
foreach (var b in gridBalls[r]) {
if (b.gameObject.activeSelf) {
gridBalls [r + 1] [b.column].gameObject.SetActive (true);
gridBalls [r + 1] [b.column].SetType (b.type);
} else {
gridBalls [r + 1] [b.column].gameObject.SetActive (false);
}
}
r--;
}
}

foreach (var b in gridBalls[0]) {
b.SetType (typePool [0]);
typePool.RemoveAt (0);
b.gameObject.SetActive (true);
}
}

So I shift the rows down and switch the types on the first row balls.

Now I can keep track of bullet shot and add a new line after 10 bullets.

So In the shooter class I add this:

void SetNextType () {

foreach (var go in colorsGO) {
go.SetActive(false);
}

type = Random.Range (0, 5);
colorsGO [type].SetActive (true);

bullets++;

if (bullets > 10) {
bullets = 0;
grid.AddLine ();
}
}

Here is the project so far.