0% found this document useful (0 votes)
85 views

Computer Organization

This document provides details about the requirements and design for a final project creating a "Number Crush" game. It describes the core gameplay of matching 3 or more numbers to clear them from the board. It outlines the levels, with Level 1 having a 10x10 board filled with random 1-5 numbers and bombs. It provides class diagrams and descriptions for key game components like Shapes to represent game objects, enums for game states and bonuses, and utilities for things like sound playback and animation. The goal is to create a never-ending game where the user can swap and clear numbers to increase their score indefinitely across multiple levels of increasing difficulty.

Uploaded by

IT'S SIMPLE
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
85 views

Computer Organization

This document provides details about the requirements and design for a final project creating a "Number Crush" game. It describes the core gameplay of matching 3 or more numbers to clear them from the board. It outlines the levels, with Level 1 having a 10x10 board filled with random 1-5 numbers and bombs. It provides class diagrams and descriptions for key game components like Shapes to represent game objects, enums for game states and bonuses, and utilities for things like sound playback and animation. The goal is to create a never-ending game where the user can swap and clear numbers to increase their score indefinitely across multiple levels of increasing difficulty.

Uploaded by

IT'S SIMPLE
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 82

Computer Organization & Assembly

Language Spring 2021 Final


Project

Number Crushing Game

Deadline:15 June 2021

Game Description
Number Crush is a "match-three" game, where the core game play is based on
swapping two adjacent random values among several on the game board to make
a row or column of at least 3 matching-random values. On this match, the
matched random values are removed from the board, and random values above
them fall into the empty spaces, with new random values appearing from the
top of the board. This may create a new matched set of random values, which is
automatically cleared in the same manner. The game is split among many levels,
which must be completed in sequence.

LEVELS
Level 1

Level 1 has a 10x10 board. When a number is swapped with another


number, if a combo exists, the combo is crushed, dropped, and score
updated accordingly. Otherwise, the numbers are swapped back! The
board is filled with random numbers from 1 to 5. It has bomb ‘B’ too. When
a number is swapped with bomb, all of its occurrences are destroyed.

Only external assets we’re using are some candy graphics (Public Domain, found on
OpenGameArt here) and a very cool sound (found on FreeSound here) to build our game. User
can drag (in an attempt to swap) one candy either horizontally or vertically. When the swap
happens, the game checks for a match. As soon as a vertical or horizontal match of three (or
more!) is encountered, the matched candies disappear. Remaining candies collapse, new candies
get created to replace them which collapse, too (imagine gravity acting upon them). The game
checks if another match of three is encountered (without any user intervention). If this happens,
the matched ones disappear again, remaining candies collapse, new candies fall and so on and so
forth. This goes on until no match of three exist and user intervention is required for the game to
go on. If the user does not touch the screen for a while, potential matches (candies that if one of
them gets swapped will form a match of three) start animating, to give the user a small hint in
order to continue the game.
The described game flow can be visualized in the below diagram
Such a game can have many types of bonuses. For the sake of this blog post, we have
implemented only one. This is created if the user’s drag/swap has a match of four (or more) as an
immediate result (i.e. it is not created in matches of four that occur in the subsequent loop of
collapses/creations). These bonus candy have a certain color (matching the one found in the
normal game candy). If the user later does a match that contains a bonus, then the whole row or
column is removed (depending on whether the match was horizontally or vertically oriented).
Our game never ends; user can swap and destroys candy (while having her score increased)
forever. In production games, user progresses through levels by achieving a certain score or by
other means, e.g. by destroying an amount of special bonus candy. The game has only one scene,
which we’ll describe. This scene has two buttons, one to restart the level and one to load a
predefined level (quite useful for debugging!).

Let’s dive into the code! As in our previous blog posts, we’ll see the code file by file.

Enums
Our game contains two enumerations. The BonusType contains info about the bonus that a
shape/candy can carry. It has been defined with the Flags attribute to allow multiple values in the
enumeration (check here for a nice article). The class BonusTypeUtilities contains only one
method, to determine whether an enumeration variable contains the specified bonus type.
Finally, the GameState enum contains the three states of our game.
– None: initial state (idle)

– SelectionStarted: when the user has started dragging

– Animating: when the game is animating (showing animations, collapsing, creating new candies
etc.)

[Flags]

public enum BonusType

{
None,

DestroyWholeRowColumn

public static class BonusTypeUtilities

public static bool ContainsDestroyWholeRowColumn(BonusType bt)

return (bt & BonusType.DestroyWholeRowColumn)

== BonusType.DestroyWholeRowColumn;

public enum GameState

None,

SelectionStarted,

Animating

Constants
Contains some useful constant and self-explainable variables for our game, regarding animation
durations, score, rows and columns for the array that will contain our candy and more.

public static class Constants

public static readonly int Rows = 12;

public static readonly int Columns = 8;

public static readonly float AnimationDuration = 0.2f;

public static readonly float MoveAnimationMinDuration = 0.05f;

public static readonly float ExplosionDuration = 0.3f;

public static readonly float WaitBeforePotentialMatchesCheck = 2f;

public static readonly float OpacityAnimationFrameDelay = 0.05f;

public static readonly int MinimumMatches = 3;

public static readonly int MinimumMatchesForBonus = 4;

public static readonly int Match3Score = 60;

public static readonly int SubsequentMatchScore = 1000;

}
Shape
The Shape class will be used to hold details for each individual candy. Each candy GameObject
on the screen will have a Shape component, so each Shape instance is a MonoBehaviour. It
contains info about potential bonus(es), the Type of the Shape (in our case, the candy color) the
Column and Row that the candy is placed, a constructor that initializes the bonus enumeration
and a method that compares the current Shape with another one, by comparing its Type. We use
a Row and Column since we’ll have a two dimensional array host our candies.

public class Shape : MonoBehaviour

public BonusType Bonus { get; set; }

public int Column { get; set; }

public int Row { get; set; }

public string Type { get; set; }

public Shape()

Bonus = BonusType.None;

public bool IsSameType(Shape otherShape)

if (otherShape == null || !(otherShape is Shape))

throw new ArgumentException("otherShape");


return string.Compare(this.Type, (otherShape as Shape).Type) == 0;

Since Shape is a MonoBehaviour that is attached to our prefabs (as we’ll see later), we cannot
use a constructor to initialize it. So, we’ve implemented an Assign method that sets the basic
properties of the Shape. The SwapColumnRow method swaps the row and the column properties
of two shape instances.

public void Assign(string type, int row, int column)

if (string.IsNullOrEmpty(type))

throw new ArgumentException("type");

Column = column;

Row = row;

Type = type;

public static void SwapColumnRow(Shape a, Shape b)

int temp = a.Row;

a.Row = b.Row;

b.Row = temp;
temp = a.Column;

a.Column = b.Column;

b.Column = temp;

SoundManager
The SoundManager is an easily extendable class that contains AudioClip and relevant
AudioSource for the crincle sound. Plus, there is a public method to play this sound.

If one wants to add more sounds, she could easily do that by adding an AudioClip, and
AudioSource, add that during Awake and create another “PlayCrincle” method, just like the code
below.

public class SoundManager : MonoBehaviour {

//crincle sound found here: http://freesound.org/people/volivieri/sounds/37171/

public AudioClip crincleAudioClip;

AudioSource crincle;

void Awake()

crincle = AddAudio(crincleAudioClip);

}
AudioSource AddAudio( AudioClip audioClip)

AudioSource audioSource = this.gameObject.AddComponent<AudioSource>();

audioSource.playOnAwake = false;

audioSource.clip = audioClip;

return audioSource;

public void PlayCrincle()

crincle.Play();

Utilities
This static class contains some static helper methods.

The AnimatePotentialMatches coroutine takes a list of GameObjects and modifies their opacity
(from 1.0 to 0.3 and then to 1.0) using a constant time delay. This is to animate the potential
matches that are given as a hint to the user.

public static IEnumerator AnimatePotentialMatches(IEnumerable<GameObject> potentialMatches)

for (float i = 1f; i >= 0.3f; i -= 0.1f)


{

foreach (var item in potentialMatches)

Color c = item.GetComponent<SpriteRenderer>().color;

c.a = i;

item.GetComponent<SpriteRenderer>().color = c;

yield return new WaitForSeconds(Constants.OpacityAnimationFrameDelay);

for (float i = 0.3f; i <= 1f; i += 0.1f)

foreach (var item in potentialMatches)

Color c = item.GetComponent<SpriteRenderer>().color;

c.a = i;

item.GetComponent<SpriteRenderer>().color = c;

yield return new WaitForSeconds(Constants.OpacityAnimationFrameDelay);

The AreVerticalOrHorizontalNeighbors method returns true if the two shapes that are passed as
parameters are next to each other, either vertically or horizontally.
public static bool AreVerticalOrHorizontalNeighbors(Shape s1, Shape s2)

return (s1.Column == s2.Column ||

s1.Row == s2.Row)

&& Mathf.Abs(s1.Column - s2.Column) <= 1

&& Mathf.Abs(s1.Row - s2.Row) <= 1;

The GetPotentialMatches method tries to find and return a list of possible matches for the game
to animate, as a hint to the user. It loops in all candy, calls six different methods that search for
potential matches and gathers their results. When we have more than 3 results (different sets of
matches), we return a random one of them. However, if we search half the array and we have
less than or equal to two matches, we return a random one. This, because we don’t want the
algorithm to search more, since by running on a mobile device i) we’ll have a performance
penalty which in turn ii) will lead to a battery drain.

public static IEnumerable<GameObject> GetPotentialMatches(ShapesArray shapes)

//list that will contain all the matches we find

List<List<GameObject>> matches = new List<List<GameObject>>();

for (int row = 0; row < Constants.Rows; row++)

for (int column = 0; column < Constants.Columns; column++)

var matches1 = CheckHorizontal1(row, column, shapes);


var matches2 = CheckHorizontal2(row, column, shapes);

var matches3 = CheckHorizontal3(row, column, shapes);

var matches4 = CheckVertical1(row, column, shapes);

var matches5 = CheckVertical2(row, column, shapes);

var matches6 = CheckVertical3(row, column, shapes);

if (matches1 != null) matches.Add(matches1);

if (matches2 != null) matches.Add(matches2);

if (matches3 != null) matches.Add(matches3);

if (matches4 != null) matches.Add(matches4);

if (matches5 != null) matches.Add(matches5);

if (matches6 != null) matches.Add(matches6);

//if we have >= 3 matches, return a random one

if (matches.Count >= 3)

return matches[UnityEngine.Random.Range(0, matches.Count - 1)];

//if we are in the middle of the calculations/loops

//and we have less than 3 matches, return a random one

if(row >= Constants.Rows / 2 && matches.Count > 0 && matches.Count <=2)

return matches[UnityEngine.Random.Range(0, matches.Count - 1)];


}

return null;

The code for the six “search” methods won’t be fully listed (it was longer than I originally
thought), we’ll just include the comments next to the code that visualize what kind of patterns
the methods are searching for.

CheckHorizontal methods search for these patterns (imagine this like a 5×5 array, those
elements marked with * are random shapes whereas the ones marked with & are of the same
color)

CheckVertical methods search for these patterns

AlteredCandyInfo
This class contains information about candy that  are about to be moved after a collapse/new
candy creation event. It contains

– a private list with all the candy to be moved

– a property that returns the Distinct (i.e. unique) result of the above list. This is necessary in
case the internal list contains the same shape twice

– a method to add a new candy to the private list

– a constructor that initializes the private list

public class AlteredCandyInfo

private List<GameObject> newCandy { get; set; }

public int MaxDistance { get; set; }

public IEnumerable<GameObject> AlteredCandy

get

return newCandy.Distinct();

public void AddCandy(GameObject go)

{
if (!newCandy.Contains(go))

newCandy.Add(go);

public AlteredCandyInfo()

newCandy = new List<GameObject>();

MatchesInfo
The MatchesInfo class contains useful information about the candies that were matches (either a
match of three or more). It looks a lot like the before mentioned AlteredCandyInfo class (we
could possible use some inheritance here) with the addition of the BonusType information for the
entire match.

public class MatchesInfo

private List<GameObject> matchedCandies;

public IEnumerable<GameObject> MatchedCandy

get

return matchedCandies.Distinct();
}

public void AddObject(GameObject go)

if (!matchedCandies.Contains(go))

matchedCandies.Add(go);

public void AddObjectRange(IEnumerable<GameObject> gos)

foreach (var item in gos)

AddObject(item);

public MatchesInfo()

matchedCandies = new List<GameObject>();

BonusesContained = BonusType.None;
}

public BonusType BonusesContained { get; set; }

ShapesArray
As we previously described, we’ll be using a two dimensional array to store our candy shapes.
One option would be to create an instance of the array and then do operations on it. However, a
much better option is to encapsulate this array (along with some useful operations and variables)
in a class. This is the purpose of the ShapesArray class.

Initially, we can see that a two dimensional array is declared. Its dimensions correspond to
values taken from the Constants class. There is also an indexer that returns the specific
GameObject via requested column/row.

public class ShapesArray

private GameObject[,] shapes = new GameObject[Constants.Rows, Constants.Columns];

public GameObject this[int row, int column]

get

try

return shapes[row, column];


}

catch (Exception ex)

throw ex;

set

shapes[row, column] = value;

The Swap method has the responsibility to swap two GameObjects. It starts by creating a backup
of them, in case there is no match and they need to get back to their original positions. Then, it
swaps their position in the array and, finally, it calls the SwapColumnRow static method in the
Shape class, to swap the individual properties of the two Shape components.

public void Swap(GameObject g1, GameObject g2)

//hold a backup in case no match is produced

backupG1 = g1;

backupG2 = g2;

var g1Shape = g1.GetComponent<Shape>();


var g2Shape = g2.GetComponent<Shape>();

//get array indexes

int g1Row = g1Shape.Row;

int g1Column = g1Shape.Column;

int g2Row = g2Shape.Row;

int g2Column = g2Shape.Column;

//swap them in the array

var temp = shapes[g1Row, g1Column];

shapes[g1Row, g1Column] = shapes[g2Row, g2Column];

shapes[g2Row, g2Column] = temp;

//swap their respective properties

Shape.SwapColumnRow(g1Shape, g2Shape);

The UndoSwap method will undo the swap by simply calling the Swap method on the backup
GameObjects.

public void UndoSwap()

if (backupG1 == null || backupG2 == null)


throw new Exception("Backup is null");

Swap(backupG1, backupG2);

private GameObject backupG1;

private GameObject backupG2;

The ShapesArray class contains two methods for matches checking. One of them does a
horizontal check whereas the other does a vertical one. They both accept a GameObject as a
parameter and will check either the row or the column in which this GameObject belongs to.
Also, this GameObject is always added to the list of matches. However, if they find less than
three matches, they return an empty list.

private IEnumerable<GameObject> GetMatchesHorizontally(GameObject go)

List<GameObject> matches = new List<GameObject>();

matches.Add(go);

var shape = go.GetComponent<Shape>();

//check left

if (shape.Column != 0)

for (int column = shape.Column - 1; column >= 0; column--)

if (shapes[shape.Row, column].GetComponent<Shape>().IsSameType(shape))

{
matches.Add(shapes[shape.Row, column]);

else

break;

//check right

if (shape.Column != Constants.Columns - 1)

for (int column = shape.Column + 1; column < Constants.Columns; column++)

if (shapes[shape.Row, column].GetComponent<Shape>().IsSameType(shape))

matches.Add(shapes[shape.Row, column]);

else

break;

//we want more than three matches

if (matches.Count < Constants.MinimumMatches)

matches.Clear();
return matches.Distinct();

private IEnumerable<GameObject> GetMatchesVertically(GameObject go)

List<GameObject> matches = new List<GameObject>();

matches.Add(go);

var shape = go.GetComponent<Shape>();

//check bottom

if (shape.Row != 0)

for (int row = shape.Row - 1; row >= 0; row--)

if (shapes[row, shape.Column] != null &&

shapes[row, shape.Column].GetComponent<Shape>().IsSameType(shape))

matches.Add(shapes[row, shape.Column]);

else

break;

}
//check top

if (shape.Row != Constants.Rows - 1)

for (int row = shape.Row + 1; row < Constants.Rows; row++)

if (shapes[row, shape.Column] != null &&

shapes[row, shape.Column].GetComponent<Shape>().IsSameType(shape))

matches.Add(shapes[row, shape.Column]);

else

break;

if (matches.Count < Constants.MinimumMatches)

matches.Clear();

return matches.Distinct();

The GetEntireRow and GetEntireColumn methods return the collection of GameObjects that
belong in a specific row or column. They are used when a match contains a bonus candy.

private IEnumerable<GameObject> GetEntireRow(GameObject go)


{

List<GameObject> matches = new List<GameObject>();

int row = go.GetComponent<Shape>().Row;

for (int column = 0; column < Constants.Columns; column++)

matches.Add(shapes[row, column]);

return matches;

private IEnumerable<GameObject> GetEntireColumn(GameObject go)

List<GameObject> matches = new List<GameObject>();

int column = go.GetComponent<Shape>().Column;

for (int row = 0; row < Constants.Rows; row++)

matches.Add(shapes[row, column]);

return matches;

}
The ContainsDestroyRowColumnBonus method checks if a collection of matches contains a
bonus candy with type “DestroyRowColumn”. This, in order to have the entire row/column
removed later.

private bool ContainsDestroyRowColumnBonus(IEnumerable<GameObject> matches)

if (matches.Count() >= Constants.MinimumMatches)

foreach (var go in matches)

if (BonusTypeUtilities.ContainsDestroyWholeRowColumn

(go.GetComponent<Shape>().Bonus))

return true;

return false;

The GetMatches method has two overloads. The first one takes a single GameObject as a
parameter. It sequentially

– checks for horizontal matches

– if there are any bonuses there, it will retrieve the entire row. It will also add the
DestroyWholeRowColumn bonus flag to the matchesInfo.BonusesContained property if it does
not already exist.

– adds the horizontal matches to the MatchesInfo instance


– repeats the same 3 steps while checking vertically

public MatchesInfo GetMatches(GameObject go)

MatchesInfo matchesInfo = new MatchesInfo();

var horizontalMatches = GetMatchesHorizontally(go);

if (ContainsDestroyRowColumnBonus(horizontalMatches))

horizontalMatches = GetEntireRow(go);

if (!BonusTypeUtilities.ContainsDestroyWholeRowColumn(matchesInfo.BonusesContained))

matchesInfo.BonusesContained |= BonusType.DestroyWholeRowColumn;

matchesInfo.AddObjectRange(horizontalMatches);

var verticalMatches = GetMatchesVertically(go);

if (ContainsDestroyRowColumnBonus(verticalMatches))

verticalMatches = GetEntireColumn(go);

if (!BonusTypeUtilities.ContainsDestroyWholeRowColumn(matchesInfo.BonusesContained))

matchesInfo.BonusesContained |= BonusType.DestroyWholeRowColumn;

}
matchesInfo.AddObjectRange(verticalMatches);

return matchesInfo;

The other overload of the GetMatches method gets a collection of GameObjects as a parameter.
For each one, it will use the previously described overload to check for matches.

public IEnumerable<GameObject> GetMatches(IEnumerable<GameObject> gos)

List<GameObject> matches = new List<GameObject>();

foreach (var go in gos)

matches.AddRange(GetMatches(go).MatchedCandy);

return matches.Distinct();

The Remove method removes (sets as null) an item from the array. It will be called once for each
match encountered.

public void Remove(GameObject item)

shapes[item.GetComponent<Shape>().Row, item.GetComponent<Shape>().Column] = null;

The Collapse method will collapse the remaining candies in the specified columns, after the
matched candies removal. Basically, it searches for null items. If it finds any, it will move the
nearest top candy to the null item position. It will continue to do so until all null items are
stacked on the top positions of the column. Moreover, it will calculate the max distance a candy
will have to be moved (this will assist in calculating the animation duration). All the required
information is passed into an AlteredCandyInfo class, which is returned to the caller.

public AlteredCandyInfo Collapse(IEnumerable<int> columns)

AlteredCandyInfo collapseInfo = new AlteredCandyInfo();

///search in every column

foreach (var column in columns)

//begin from bottom row

for (int row = 0; row < Constants.Rows - 1; row++)

//if you find a null item

if (shapes[row, column] == null)

//start searching for the first non-null

for (int row2 = row + 1; row2 < Constants.Rows; row2++) { //if you find
one, bring it down (i.e. replace it with the null you found) if (shapes[row2, column] != null)
{ shapes[row, column] = shapes[row2, column]; shapes[row2, column] = null;
//calculate the biggest distance if (row2 - row > collapseInfo.MaxDistance)

collapseInfo.MaxDistance = row2 - row;

//assign new row and column (name does not change)


shapes[row, column].GetComponent<Shape>().Row = row;

shapes[row, column].GetComponent<Shape>().Column = column;

collapseInfo.AddCandy(shapes[row, column]);

break;

return collapseInfo;

The GetEmptyItemsOnColumn method gets a specified column as a parameter. It will return the
Shape details (more specifically, the positions) via the ShapeInfo class in this column which are
empty (null).

public IEnumerable<ShapeInfo> GetEmptyItemsOnColumn(int column)

List<ShapeInfo> emptyItems = new List<ShapeInfo>();

for (int row = 0; row < Constants.Rows; row++)

if (shapes[row, column] == null)


emptyItems.Add(new ShapeInfo() { Row = row, Column = column });

return emptyItems;

The ShapeInfo class contains details about row and column for a shape.

public class ShapeInfo

public int Column { get; set; }

public int Row { get; set; }

ShapesManager
The ShapesManager class is the main class of our game. It handles the array creation, score
keeping and the candy GameObjects’ creation and destruction.

It is attached to the ShapesManager GameObject. We pass some prefabs and GameObjects  via
the Editor to the ShapesManager public fields. Specifically, the CandyPrefabs array contains our
candy GameObjects, the explosion prefabs contains some animated GameObjects that will run
when any candy is destroyed and the BonusPrefabs array contains 5 candy GameObjects, with
each one having a corresponding color with our normal candy (for correct matching). The
DebugText and ScoreText fields contain UI Text GameObject references, whereas the
ShowDebugInfo boolean variable allows the game to show some debug information, on
developer’s request.
Let’s see the code! In the beginning, there are some private members’ declarations, along with
the public ones that we previously described. Candy size is also specified, along with the first
candy (the one at [0,0]) position in the scene (called BottomRight). We also declare two
IEnumerator variables, which will hold references to coroutines instantiated throughout this
class, to make their termination easier.

public class ShapesManager : MonoBehaviour

public Text DebugText, ScoreText;

public bool ShowDebugInfo = false;

//candy graphics taken from http://opengameart.org/content/candy-pack-1


public ShapesArray shapes;

private int score;

public readonly Vector2 BottomRight = new Vector2(-2.37f, -4.27f);

public readonly Vector2 CandySize = new Vector2(0.7f, 0.7f);

private GameState state = GameState.None;

private GameObject hitGo = null;

private Vector2[] SpawnPositions;

public GameObject[] CandyPrefabs;

public GameObject[] ExplosionPrefabs;

public GameObject[] BonusPrefabs;

private IEnumerator CheckPotentialMatchesCoroutine;

private IEnumerator AnimatePotentialMatchesCoroutine;

IEnumerable<GameObject> potentialMatches;

public SoundManager soundManager;

The Awake method enables or disables a UI Text GameObject. This GameObject, if enabled,
shows some debug info during the game.
The Start method calls 3 methods to initialize our game.

void Awake()

DebugText.enabled = ShowDebugInfo;

// Use this for initialization

void Start()

InitializeTypesOnPrefabShapesAndBonuses();

InitializeCandyAndSpawnPositions();

StartCheckForPotentialMatches();

The InitializeTypesOnPrefabShapesAndBonuses method does two things

– sets the Type of each prefab Shape component with the name of the GameObject (e.g.
bean_blue)

– sets the Type of each prefab Bonus Shape component with the name of the corresponding
prefab (e.g. the swirl_blue bonus candy will get bean_blue as a type). This, in order to be
precisely matched (the blue bonus matches the blue candy etc.).
private void InitializeTypesOnPrefabShapesAndBonuses()

{
//just assign the name of the prefab

foreach (var item in CandyPrefabs)

item.GetComponent<Shape>().Type = item.name;

//assign the name of the respective "normal" candy as the type of the Bonus

foreach (var item in BonusPrefabs)

item.GetComponent<Shape>().Type = CandyPrefabs.

Where(x => x.GetComponent<Shape>().Type.Contains(item.name.Split('_')[1].Trim())).Single().name;

InitializeCandyAndSpawnPositions
The InitializeCandyAndSpawnPositions method is based on some other methods and functions.

The score related methods are listed below, featuring a simple initialization and UI updates.

private void InitializeVariables()

score = 0;

ShowScore();
}

private void IncreaseScore(int amount)

score += amount;

ShowScore();

private void ShowScore()

ScoreText.text = "Score: " + score.ToString();

The GetRandomCandy method returns a random candy prefab from the candy prefabs collection.

private GameObject GetRandomCandy()

return CandyPrefabs[Random.Range(0, CandyPrefabs.Length)];

The InstantiateAndPlaceNewCandy method creates a new candy GameObject (prefab


instantiation) at the specified row and column and at the specified position. It uses the Assign
method of the Shape component to give some initial values to it and it places it into the candy
array.

private void InstantiateAndPlaceNewCandy(int row, int column, GameObject newCandy)

{
GameObject go = Instantiate(newCandy,

BottomRight + new Vector2(column * CandySize.x, row * CandySize.y), Quaternion.identity)

as GameObject;

//assign the specific properties

go.GetComponent<Shape>().Assign(newCandy.GetComponent<Shape>().Type, row, column);

shapes[row, column] = go;

The SetupSpawnPositions method gives initial values to the spawn positions. Those are the
positions that new candy will be created to replace the ones that were removed because of a
match of three or four. After their creation at the designated positions, they’ll be animated to the
positions they’ll cover (the null/empty positions in the array).

private void SetupSpawnPositions()

//create the spawn positions for the new shapes (will pop from the 'ceiling')

for (int column = 0; column < Constants.Columns; column++)

SpawnPositions[column] = BottomRight

+ new Vector2(column * CandySize.x, Constants.Rows * CandySize.y);

}
The DestroyAllCandy method calls the GameObject.Destroy method on all candy in the array, in
order to remove them from our scene.

private void DestroyAllCandy()

for (int row = 0; row < Constants.Rows; row++)

for (int column = 0; column < Constants.Columns; column++)

Destroy(shapes[row, column]);

The InitializeCandyAndSpawnPositions

– initializes the score variables

– destroys all elements in the array

– reinitializes the array and the spawn positions for the new candy

– loops through all the array elements and creates new candy taking caution *not* to initially
create any matches of three. It’s up to the user to do that, via her swaps!

public void InitializeCandyAndSpawnPositions()

InitializeVariables();
if (shapes != null)

DestroyAllCandy();

shapes = new ShapesArray();

SpawnPositions = new Vector2[Constants.Columns];

for (int row = 0; row < Constants.Rows; row++)

for (int column = 0; column < Constants.Columns; column++)

GameObject newCandy = GetRandomCandy();

//check if two previous horizontal are of the same type

while (column >= 2 && shapes[row, column - 1].GetComponent<Shape>()

.IsSameType(newCandy.GetComponent<Shape>())

&& shapes[row, column -


2].GetComponent<Shape>().IsSameType(newCandy.GetComponent<Shape>()))

newCandy = GetRandomCandy();

}
//check if two previous vertical are of the same type

while (row >= 2 && shapes[row - 1, column].GetComponent<Shape>()

.IsSameType(newCandy.GetComponent<Shape>())

&& shapes[row - 2,
column].GetComponent<Shape>().IsSameType(newCandy.GetComponent<Shape>()))

newCandy = GetRandomCandy();

InstantiateAndPlaceNewCandy(row, column, newCandy);

SetupSpawnPositions();

The FixSortingLayer method is used during a user swap, to make sure that the candy that was
dragged will appear on top of the other one, for better visual results.

private void FixSortingLayer(GameObject hitGo, GameObject hitGo2)

SpriteRenderer sp1 = hitGo.GetComponent<SpriteRenderer>();


SpriteRenderer sp2 = hitGo2.GetComponent<SpriteRenderer>();

if (sp1.sortingOrder <= sp2.sortingOrder)

sp1.sortingOrder = 1;

sp2.sortingOrder = 0;

Hint related methods


As we previously described, if a user does not touch the screen for a specified amount of time,
hints will appear on the screen, showing potential matches if she swaps the proper candy shapes.
Let’s take a look at these methods.

The CheckPotentialMatches coroutine uses the GetPotentialMatches method in the Utilities


class. If there are any matches, it will animate them using the AnimatePotentialMatches (again in
the Utilities class). Moreover, a reference to the coroutine for the animation is saved, in order for
it to be possibly stopped at a later time via the StopCoroutine method.

private IEnumerator CheckPotentialMatches()

yield return new WaitForSeconds(Constants.WaitBeforePotentialMatchesCheck);

potentialMatches = Utilities.GetPotentialMatches(shapes);

if (potentialMatches != null)

while (true)

{
AnimatePotentialMatchesCoroutine = Utilities.AnimatePotentialMatches(potentialMatches);

StartCoroutine(AnimatePotentialMatchesCoroutine);

yield return new WaitForSeconds(Constants.WaitBeforePotentialMatchesCheck);

The ResetOpacityOnPotentialMatches sets the opacity to default (1.0f) at the candy that were
animated, as potential matches.

private void ResetOpacityOnPotentialMatches()

if (potentialMatches != null)

foreach (var item in potentialMatches)

if (item == null) break;

Color c = item.GetComponent<SpriteRenderer>().color;

c.a = 1.0f;

item.GetComponent<SpriteRenderer>().color = c;

The StartCheckForPotentialMatches method stops the check if it’s already running and starts the
CheckPotentialMatches coroutine, storing a reference to it so it can be stopped at a later time.
private void StartCheckForPotentialMatches()

StopCheckForPotentialMatches();

//get a reference to stop it later

CheckPotentialMatchesCoroutine = CheckPotentialMatches();

StartCoroutine(CheckPotentialMatchesCoroutine);

The StopCheckForPotentialMatches will attempt to stop both the AnimatePotentialMatches and


the CheckPotentialMatches coroutines (via the use of the StopCoroutine method). Plus, it will
reset the opacity on the items that were previously animated.

private void StopCheckForPotentialMatches()

if (AnimatePotentialMatchesCoroutine != null)

StopCoroutine(AnimatePotentialMatchesCoroutine);

if (CheckPotentialMatchesCoroutine != null)

StopCoroutine(CheckPotentialMatchesCoroutine);

ResetOpacityOnPotentialMatches();

Matching, collapsing and creating new candy


Let’s dive into the hardest part of the ShapesManager. We’ll see the Update method and the rest
of the code that handles the core logic of our game.

The GetRandomExplosion method returns a random explosion prefab.

private GameObject GetRandomExplosion()


{

return ExplosionPrefabs[Random.Range(0, ExplosionPrefabs.Length)];

The GetBonusFromType method will return the bonus prefab that corresponds to a normal candy
type. For example, if the parameter type is a blue candy, it will return the blue bonus prefab.

private GameObject GetBonusFromType(string type)

string color = type.Split('_')[1].Trim();

foreach (var item in BonusPrefabs)

if (item.GetComponent<Shape>().Type.Contains(color))

return item;

throw new System.Exception("Wrong type");

The RemoveFromScene method creates a new explosion, sets it to be destroyed after a specified
amount of seconds and destroys the candy which is passed as a parameter. This method makes
for a nice “disappear with a bang” effect!

private void RemoveFromScene(GameObject item)

GameObject explosion = GetRandomExplosion();

var newExplosion = Instantiate(explosion, item.transform.position, Quaternion.identity) as GameObject;

Destroy(newExplosion, Constants.ExplosionDuration);
Destroy(item);

The MoveAndAnimate method utilizes the awesome GoKit animation library to animate a


collection of GameObjects to their new position. It is used to animate any candy that were
collapsed and new candy that was created to replace the empty positions on the array (that was
left from the matched candy, which was eventually removed).
private void MoveAndAnimate(IEnumerable<GameObject> movedGameObjects, int distance)

foreach (var item in movedGameObjects)

item.transform.positionTo(Constants.MoveAnimationMinDuration * distance, BottomRight +

new Vector2(item.GetComponent<Shape>().Column * CandySize.x, item.GetComponent<Shape>().Row


* CandySize.y));

The CreateNewCandyInSpecificColumns takes the columns that have missing candy (null
values) as a parameter. For each column

– it gets the empty items’ info (row + column)

– for each such empty item

– a new random candy is created

– its shape component is assigned with the necessary values

– max distance is calculated (to assist in the animation duration calculation)

– its info is added to a AlteredCandyInfo collection, to be returned and eventually animated to


their new location in the scene
private AlteredCandyInfo CreateNewCandyInSpecificColumns(IEnumerable<int> columnsWithMissingCandy)

AlteredCandyInfo newCandyInfo = new AlteredCandyInfo();

//find how many null values the column has

foreach (int column in columnsWithMissingCandy)

var emptyItems = shapes.GetEmptyItemsOnColumn(column);

foreach (var item in emptyItems)

var go = GetRandomCandy();

GameObject newCandy = Instantiate(go, SpawnPositions[column], Quaternion.identity)

as GameObject;

newCandy.GetComponent<Shape>().Assign(go.GetComponent<Shape>().Type, item.Row,
item.Column);

if (Constants.Rows - item.Row > newCandyInfo.MaxDistance)

newCandyInfo.MaxDistance = Constants.Rows - item.Row;

shapes[item.Row, item.Column] = newCandy;

newCandyInfo.AddCandy(newCandy);
}

return newCandyInfo;

The CreateBonus method

– creates a new bonus (copied from the prefab) based on the candy type given as parameter

– assigns the new GameObject to its proper position in the array

– sets necessary variables via the Assign method

– adds the DestroyWholeRowColumn bonus type to the Bonus property

private void CreateBonus(Shape hitGoCache)

GameObject Bonus = Instantiate(GetBonusFromType(hitGoCache.Type), BottomRight

+ new Vector2(hitGoCache.Column * CandySize.x,

hitGoCache.Row * CandySize.y), Quaternion.identity)

as GameObject;

shapes[hitGoCache.Row, hitGoCache.Column] = Bonus;

var BonusShape = Bonus.GetComponent<Shape>();

//will have the same type as the "normal" candy

BonusShape.Assign(hitGoCache.Type, hitGoCache.Row, hitGoCache.Column);

//add the proper Bonus type

BonusShape.Bonus |= BonusType.DestroyWholeRowColumn;
}

The Update method is split into two parts, each one handling a different state.

In the none/initial/idle state, game checks if the user has touched a candy. If this happens, the
game transitions to the SelectionStarted page.

void Update()

if (ShowDebugInfo)

DebugText.text = DebugUtilities.GetArrayContents(shapes);

if (state == GameState.None)

//user has clicked or touched

if (Input.GetMouseButtonDown(0))

//get the hit position

var hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);

if (hit.collider != null) //we have a hit!!!

hitGo = hit.collider.gameObject;

state = GameState.SelectionStarted;

}
}

In the SelectionStarted page

– we get a reference of the second GameObject (the second part of the swap operation)

– we stop the check for potential matches

– if user dragged diagonally or very quickly (skipped a GameObject), state changes to idle/none

– else, we transition to the animating state, fix the sorting layer of the two GameObjects and
initialize the FindMatchesAndCollapse coroutine, to detect potential matches as a result of the
swap and proceed accordingly

else if (state == GameState.SelectionStarted)

//user dragged

if (Input.GetMouseButton(0))

var hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);

//we have a hit

if (hit.collider != null && hitGo != hit.collider.gameObject)

//user did a hit, no need to show him hints

StopCheckForPotentialMatches();
//if the two shapes are diagonally aligned (different row and column), just return

if (!Utilities.AreVerticalOrHorizontalNeighbors(hitGo.GetComponent<Shape>(),

hit.collider.gameObject.GetComponent<Shape>()))

state = GameState.None;

else

state = GameState.Animating;

FixSortingLayer(hitGo, hit.collider.gameObject);

StartCoroutine(FindMatchesAndCollapse(hit));

The FindMatchesAndCollapse method is a big one, we’ll split it into smaller parts to property
dissect it.

At the beginning, the method swaps and moves the two candies. Eventually, it gets the matches
(matched candies) around the two candies. If they are less than three, then the swap is undone.
Otherwise, we hold a boolean variable to indicate that a bonus will be created if

– we have more than four matches


– the matches from both candies do not already contain a bonus

private IEnumerator FindMatchesAndCollapse(RaycastHit2D hit2)

//get the second item that was part of the swipe

var hitGo2 = hit2.collider.gameObject;

shapes.Swap(hitGo, hitGo2);

//move the swapped ones

hitGo.transform.positionTo(Constants.AnimationDuration, hitGo2.transform.position);

hitGo2.transform.positionTo(Constants.AnimationDuration, hitGo.transform.position);

yield return new WaitForSeconds(Constants.AnimationDuration);

//get the matches via the helper methods

var hitGomatchesInfo = shapes.GetMatches(hitGo);

var hitGo2matchesInfo = shapes.GetMatches(hitGo2);

var totalMatches = hitGomatchesInfo.MatchedCandy

.Union(hitGo2matchesInfo.MatchedCandy).Distinct();

//if user's swap didn't create at least a 3-match, undo their swap

if (totalMatches.Count() < Constants.MinimumMatches)


{

hitGo.transform.positionTo(Constants.AnimationDuration, hitGo2.transform.position);

hitGo2.transform.positionTo(Constants.AnimationDuration, hitGo.transform.position);

yield return new WaitForSeconds(Constants.AnimationDuration);

shapes.UndoSwap();

//if more than 3 matches and no Bonus is contained in the line, we will award a new Bonus

bool addBonus = totalMatches.Count() >= Constants.MinimumMatchesForBonus &&

!BonusTypeUtilities.ContainsDestroyWholeRowColumn(hitGomatchesInfo.BonusesContained) &&

!BonusTypeUtilities.ContainsDestroyWholeRowColumn(hitGo2matchesInfo.BonusesContained);

Afterwards, if the addBonus variable is equal to true, we get a reference to the GameObject that
is part of the match of four. We create a temporary Shape (hitGoCache) to store the necessary
details (type, row, column) of this GameObject.

Shape hitGoCache = null;

if (addBonus)

//get the game object that was of the same type

var sameTypeGo = hitGomatchesInfo.MatchedCandy.Count() > 0 ? hitGo : hitGo2;

hitGoCache = sameTypeGo.GetComponent<Shape>();

}
If the total matches are more than three, a while loop starts. There, the score is increased and the
matches are removed from the array and destroyed from the scene. If we have to add a bonus
candy, we create a bonus GameObject. The addBonus boolean is set to false, so that the bonus
can be added only in the first run of the while loop. After that, we get the indices of the columns
that have null/empty items (have had matches destroyed).

int timesRun = 1;

while (totalMatches.Count() >= Constants.MinimumMatches)

//increase score

IncreaseScore((totalMatches.Count() - 2) * Constants.Match3Score);

if (timesRun >= 2)

IncreaseScore(Constants.SubsequentMatchScore);

soundManager.PlayCrincle();

foreach (var item in totalMatches)

shapes.Remove(item);

RemoveFromScene(item);

//check and instantiate Bonus if needed


if (addBonus)

CreateBonus(hitGoCache);

addBonus = false;

//get the columns that we had a collapse

var columns = totalMatches.Select(go => go.GetComponent<Shape>().Column).Distinct();

We continue by collapsing the candy in these columns, creating new candy in them and
calculating the max distance needed for animations. These animations are executed and then, we
again check for new matches (after candies have collapsed and new candies have been created).
We continue the while loop, doing the same stuff.

Eventually, in a subsequent run of the while loop, the matches encountered are less than three.
We exit the loop, transition to the none/idle state and run the method that checks for potential
matches (as hint for the user).

//the order the 2 methods below get called is important!!!

//collapse the ones gone

var collapsedCandyInfo = shapes.Collapse(columns);

//create new ones

var newCandyInfo = CreateNewCandyInSpecificColumns(columns);

int maxDistance = Mathf.Max(collapsedCandyInfo.MaxDistance, newCandyInfo.MaxDistance);

MoveAndAnimate(newCandyInfo.AlteredCandy, maxDistance);

MoveAndAnimate(collapsedCandyInfo.AlteredCandy, maxDistance);
//will wait for both of the above animations

yield return new WaitForSeconds(Constants.MoveAnimationMinDuration * maxDistance);

//search if there are matches with the new/collapsed items

totalMatches = shapes.GetMatches(collapsedCandyInfo.AlteredCandy).

Union(shapes.GetMatches(newCandyInfo.AlteredCandy)).Distinct();

timesRun++;

state = GameState.None;

StartCheckForPotentialMatches();

Debugging
During the development of the game, there was the need to test specific scenarios. E.g. can we
test multiple collapses at the same time? Can we easily get a match of five to see the algorithm’s
behavior? As you saw, the algorithm is pretty random so we couldn’t easily test such scenarios.
This is the reason we developed a way to load custom levels. Take a look at the level.txt file,
found in the Resources folder.

The pipe character (|) is used to separate the items in the same row, the new line character (n)
acts as a row separator and blanks are ignored (trimmed). The candies that are created
correspond to the defined color. If there is a “_B” at the end of the color, then the respective
bonus candy is created.
In the DebugUtilities file there is a static method to load this file into a two dimensional string
array.

public static string[,] FillShapesArrayFromResourcesData()

string[,] shapes = new string[Constants.Rows, Constants.Columns];

TextAsset txt = Resources.Load("level") as TextAsset;

string level = txt.text;

string[] lines = level.Split(new string[] { System.Environment.NewLine },


StringSplitOptions.RemoveEmptyEntries);

for (int row = Constants.Rows - 1; row >= 0; row--)

string[] items = lines[row].Split('|');

for (int column = 0; column < Constants.Columns; column++)

shapes[row, column] = items[column];


}

return shapes;

In the ShapesManager file, the GetSpecificCandyOrBonusForPremadeLevel loads the specific


candy (or bonus candy) from a specified string.

private GameObject GetSpecificCandyOrBonusForPremadeLevel(string info)

var tokens = info.Split('_');

if (tokens.Count() == 1)

foreach (var item in CandyPrefabs)

if (item.GetComponent<Shape>().Type.Contains(tokens[0].Trim()))

return item;

else if (tokens.Count() == 2 && tokens[1].Trim() == "B")

{
foreach (var item in BonusPrefabs)

if (item.name.Contains(tokens[0].Trim()))

return item;

throw new System.Exception("Wrong type, check your premade level");

The InitializeCandyAndSpawnPositionsFromPremadeLevel method uses the


FillShapesArrayFromResourcesData utility method to fill the two dimensional string array. For
each string there, the GetSpecificCandyOrBonusForPremadeLevel method is called, which in
turn returns a new GameObject candy which is instantiated into our game scene.

public void InitializeCandyAndSpawnPositionsFromPremadeLevel()

InitializeVariables();

var premadeLevel = DebugUtilities.FillShapesArrayFromResourcesData();

if (shapes != null)

DestroyAllCandy();

shapes = new ShapesArray();


SpawnPositions = new Vector2[Constants.Columns];

for (int row = 0; row < Constants.Rows; row++)

for (int column = 0; column < Constants.Columns; column++)

GameObject newCandy = null;

newCandy = GetSpecificCandyOrBonusForPremadeLevel(premadeLevel[row, column]);

InstantiateAndPlaceNewCandy(row, column, newCandy);

SetupSpawnPositions();

We saw that there are two buttons on our scene. The “Restart” button calls the
IntializeCandyAndSpawnPositions method whereas the “Premade level” calls the
InitializeCandyAndSpawnPositionsFromPremadeLevel method.
Moreover, since we use Visual Studio for our development, we saved invaluable time though the
use of Visual Studio Tools for Unity that allow for easy integration of Unity and Visual Studio
plus setting breakpoints and debugging. Highly recommended!

The end
Game is ready for all platforms, including mouse and touch input. Here is a screenshot of the
game running in Windows Phone 8.1 emulator (512 MB devices are supported!). The “Premade
level” button needs, of course, removal for production use.
Thanks for reading this! Hope it’s helpful for your next game. As always, you can try the
game here and find the source code here on GitHub.
If you are new to Unity, check out a cool intro video series here. For instructions on how to
deploy your existing game onto Windows Store/Phone, check out the Microsoft Virtual
Academy video here: http://www.microsoftvirtualacademy.com/training-courses/porting-unity-
games-to-windows-store-and-windows-phone
Share this:

 Twitter
 Facebook

Loading...

Post navigation
PREVIOUS POST
Playing music on netduino via a web browser

NEXT POST
My articles featured on Gamasutra!

90 thoughts on “Building a match-3 game (like Candy Crush)


in Unity”

1. chocolatekiss says:
JANUARY 9, 2016 AT 5:43 AM
Hello! Love your tutorial. Will the code work even if we only use MonoDevelop?

Like
REPLY

 dgkanatsios says:
JANUARY 9, 2016 AT 10:38 AM
Yes, of course. In the end, Unity will compile and run the code, the editor is just a tool to
help you write your scripts.

Like
REPLY
2. Building the 2048 game in Unity via C# and Visual Studio – Dimitris-Ilias
Gkanatsios says:

JANUARY 23, 2016 AT 2:50 PM


[…] is a method similar to one I used when I developed the match-3 game mechanism. It loads a
file called “debugLevel” from the Resources folder. File contains integers (that […]

Like
REPLY

3. Can a Raspberry Pi 2 with Windows 10 IoT Core run a game made in Unity? –
Dimitris-Ilias Gkanatsios says:

JANUARY 29, 2016 AT 9:42 PM

[…] to jump into the mediocre performance conclusion were two pretty simple 2D games I’ve
built; the match-3 game and the puzzle one. Frame rate was 2-3 frames per second, making the
performance totally […]

Like
REPLY

4. Shad says:
FEBRUARY 1, 2016 AT 1:50 AM
Thank you for such a wonderful tutorial. Seeing a really well made Match-3 really helped me
think about the deeper issues of this kind of project.

Liked by 1 person


REPLY

5. shadract says:
FEBRUARY 1, 2016 AT 1:51 AM
Thank you for doing such a wonderful tutorial! Seeing such a well made match-3 example really
helps me to think about the deeper issues of such a project.

Liked by 2 people


REPLY

6. psxbrasil says:
FEBRUARY 23, 2016 AT 1:46 PM
Hi, great tutorial! I want to know if I can use this source code as a base to create my own match
3 game, I plan to create more functions to the game. I ask that because I want to sell the source
code to other developers? Do you give me permission to do that?

Regards
Like
REPLY

 dgkanatsios says:
FEBRUARY 23, 2016 AT 1:59 PM
You can do whatever you wish, good luck! Let me know if you find any bugs :)

Like
REPLY

7. psxbrasil says:
FEBRUARY 24, 2016 AT 12:04 AM
Hi, thanks dgkanatsios

Like
REPLY

8. Marry says:
MARCH 21, 2016 AT 11:00 AM
can u plz provide code…u give link but it’s didn’t work…:(

Liked by 1 person


REPLY

 dgkanatsios says:
MARCH 21, 2016 AT 11:10 AM
Hi, what link is not working? Links to my GitHub profile do work
correctly: https://github.com/dgkanatsios/matchthreegame
Like
REPLY

9. Marry says:
MARCH 21, 2016 AT 11:38 AM
thanku dgkanatsios:):) thanku so much..:):)

Like
REPLY

10. Rafael Hasbun (@rhasce) says:


MARCH 22, 2016 AT 2:29 AM
Can anyone use it for their own game?

Like
REPLY

 dgkanatsios says:
MARCH 22, 2016 AT 5:47 AM
Of course! A link to this blog post would be great, but not obligatory. Also, feel free to let
me know about the game as I’d love to share and play it!

Like
REPLY

11. Tim Cooley says:


JUNE 3, 2016 AT 7:09 AM
Would you be able to do a tutorial like this one on a match 3 like in Disco Panda? I can’t seem to
find a tutorial with that mechanic.

Like
REPLY

 dgkanatsios says:
JUNE 8, 2016 AT 3:57 PM
Can you point me to the Disco Panda game? Can’t find it

Like
REPLY

 Tim Cooley says:
AUGUST 24, 2016 AT 9:09 PM
it is like best fiends, that should be easier to find.

Like

 dgkanatsios says:
AUGUST 25, 2016 AT 12:47 PM
So, the mechanism in Best Fiends is as follows, from what I can tell. User starts dragging
her finger from one gem to one nearby (vertically, horizontally or diagonally). You push
the initial get type to a stack. On the subsequent gems, if they are the same type as the
original one, you continue to add them to the stack. Upon the end of the drag, you can
check if this stack contains more than 3 items. If this is the case, you remove them from
the game board and new gems fall from the top (as in my match three game tutorial).
Is this clear enough for you?

Like
12. Niel Vil says:
JUNE 24, 2016 AT 10:32 PM
Match 3 games are incredibly complex to learn , I might just buy a match 3 game kit on the unity
store. This project is great however for learning programing in C#. Thanks.

Like
REPLY

 dgkanatsios says:
JUNE 24, 2016 AT 10:53 PM
Thanks for the kind words :)

Like
REPLY

13. Ewolf says:
JULY 18, 2016 AT 8:23 AM
uhmmm sir dgk what version of unity did u use for making this project?

Like
REPLY

14. Aluad Eubie says:


JULY 18, 2016 AT 8:28 AM
hello sir dgk would like to ask what ver. of Unity did you used for this project? Btw this looks
cool!

Like
REPLY

 dgkanatsios says:
JULY 18, 2016 AT 8:34 AM
It was created on 4.6, have tested it with 5.2. Thanks!

Like
REPLY

15. westerbuins says:
AUGUST 24, 2016 AT 6:15 PM
Hi there, i was wondering how would you go about adding another special candy in? i’ve taken
your project and adjusted it to include a new star that the user gets for matching 5 (it can be used
with any type to remove all of that type), but my problem is that at random times the game will
break.

any help as to how you would do the match 5 would be appreciated

Like
REPLY

 dgkanatsios says:
AUGUST 25, 2016 AT 12:50 PM
The question is how to do match 5, correct? Here is the part that I’m getting all of the candy
matches in an array. You can easily check if this array has more than or equal to 5 items and
do your stuff!
https://github.com/dgkanatsios/MatchThreeGame/blob/master/Assets/Scripts/ShapesManage
r.cs#L268
Hope that helps,
Dimitris

Like
REPLY

 westerbuins says:
AUGUST 25, 2016 AT 3:14 PM
Hi Dimitris,

thanks for your advice,

if you could take a look at my repo and give some help that would be appreciated. I have
the 5 match working, but it randomly bugs out with the following message:

NullReferenceException: Object reference not set to an instance of an object


ShapesArray.GetMatchesHorizontally (UnityEngine.GameObject go) (at
Assets/Scripts/ShapesArray.cs:244)
ShapesArray.GetMatches (UnityEngine.GameObject go) (at
Assets/Scripts/ShapesArray.cs:113)
ShapesArray.GetMatches (IEnumerable`1 gos) (at Assets/Scripts/ShapesArray.cs:100)
ShapesManager+c__Iterator1.MoveNext () (at Assets/Scripts/ShapesManager.cs:412)

Any help would be appreciated as i cant seem to wrap my head around it.

Cheers,

Like
 westerbuins says:
AUGUST 25, 2016 AT 3:14 PM
forgot the repositry link! https://github.com/Westerveld/starRush
Like

 dgkanatsios says:
AUGUST 25, 2016 AT 3:25 PM
Have you tried using Visual Studio tools for Unity to debug your code? It could be
proven really useful in finding why this exception is thrown.
https://unity3d.com/learn/tutorials/topics/scripting/debugging-unity-games-visual-studio
Like

 westerbuins says:
AUGUST 26, 2016 AT 11:37 AM
I’ve tried using the debug method within visual studio, i know where the code breaks,
but i can’t seem to find a work around. any ideas? (its within shapesarray line 227)

Like

16. dgkanatsios says:
AUGUST 26, 2016 AT 11:53 AM
This line, right?
if (shapes [shape.Row, column].GetComponent ().IsSameType (shape)) {
well, hard to say what is null there without debugging. I would suggest you insert a breakpoint
and find the exact variable which is null, this would help you understand what is going on.

Like
REPLY

 westerbuins says:
AUGUST 26, 2016 AT 11:57 AM
Okay, will give that a go in a bit, thanks for your quick replies!

Like
REPLY

17. Winston says:
SEPTEMBER 9, 2016 AT 10:35 AM
There is a bug in InitializeCandyAndSpawnPositions method. When checking the two previous
vertical are of the same type, you may generate a type which is the same as the two previous
horizontal type.
Like
REPLY

 dgkanatsios says:
SEPTEMBER 9, 2016 AT 10:37 AM
Can you please issue a GitHub issue (if you can also submit a fix, that would be great!)?

Like
REPLY

18. nandu1301Nanda Kishore says:


SEPTEMBER 24, 2016 AT 4:07 PM
How to add target score

Like
REPLY

 dgkanatsios says:
SEPTEMBER 24, 2016 AT 4:08 PM
When the player does a match and the game increases the score, you can compare it to a
target one and act accordingly. Hope I understood your question correctly!

Like
REPLY

19. budi says:
NOVEMBER 11, 2016 AT 8:25 AM
hai dgkanatsios, i have a problem when i try to change the prefabs, it gives me an error like this :
InvalidOperationException: Operation is not valid due to the current state of the object
System.Linq.Enumerable.Single[GameObject] (IEnumerable`1 source, System.Func`2 predicate,
Fallback fallback)
System.Linq.Enumerable.Single[GameObject] (IEnumerable`1 source)
ShapesManager.InitializeTypesOnPrefabShapesAndBonuses () (at
Assets/Scripts/ShapesManager.cs:67)
ShapesManager.Start () (at Assets/Scripts/ShapesManager.cs:45)

im sorry but i still learning c# and newbie to unity. Any help would be appreciated, thank you

Like
REPLY

 dgkanatsios says:
NOVEMBER 11, 2016 AT 11:58 AM
Single() throws this, probably because there are more than one elements on the list.

Like
REPLY

 budi says:
NOVEMBER 11, 2016 AT 5:39 PM
thanks for you response. i think i need to learn more, thanks anyway

Like

20. michel says:
NOVEMBER 19, 2016 AT 9:01 AM
Hi dimitris I’m completely new to game development so maybe my question is stupid but i
wonder how i can make level animation. You know the animation between each level that we
can see in many game for example a character progressing on a road every times you achieve a
level http://www.gamasutra.com/db_area/images/blog/262755/Title.jpg
Like
REPLY

 dgkanatsios says:
NOVEMBER 19, 2016 AT 9:54 AM
Hi, you could use the GoKit library (or any other animation library you like) to animate a
GameObject from one position to another.

Like
REPLY

 Lukos says:
JANUARY 12, 2017 AT 10:51 PM
Hi, can u tell, how I can add all prefabs to canvas? I want change background image and
etc but prefabs haven’t parents for this. I tried create canvas and used setParent(); in
insilization but it’s not working.Thanks for your answer

Like

 dgkanatsios says:
FEBRUARY 11, 2017 AT 2:13 AM
Hi, you can place an empty GameObject on the screen in the position you wish and add
an image to it programmatically.

Like
21. michel says:
NOVEMBER 19, 2016 AT 1:45 PM
thank you for your response

Like
REPLY

22. lenten says:
FEBRUARY 27, 2017 AT 9:45 AM
Hi, great project for learning. I’m newbie. I want to add one more lightning effect not just
random explosion when we has bonus which kill whole row or column. How can i do that? took
me a week but still can’t figure it out. Something like:

if (bonus in totalmatches) {
do animation at that position();
shapes.remove(item);
removefromscene(item);
}

Like
REPLY

 dgkanatsios says:
MARCH 1, 2017 AT 2:29 PM
Are you starting an animation there? If yes, you could yield return new
WaitForSeconds(animation_duration) there before removing the shapes.

Like
REPLY

23. lenten says:
MARCH 1, 2017 AT 5:38 PM
Well, it’s not just something like that, I know how to do animation or call an yield. I mean a real
code actually work in your project. How do i call an If like that? How do i figure out when bonus
kill a column to start animation from bottom or start from left when it kill a row? I played many
match 3 games on store they have a lot of effects, make their games more exciting. Thanks for
replying, i think i will do it myself, try & learn more.

Like
REPLY
 dgkanatsios says:
MARCH 3, 2017 AT 9:29 PM
Have you had any luck? All magic happens in this
function: https://github.com/dgkanatsios/MatchThreeGame/blob/master/Assets/Scripts/Const
ants.cs#L9 I should have made it smaller, to be honest, but anyway you can see what
happens when a bonus is to be added.
Like
REPLY

24. Janny says:
MARCH 3, 2017 AT 9:23 PM
Hi just want to know how to make the gridsize declared because I want to make a new level of it
with different grid sizes. I don’t know how to make another set cause the Gridsize is constant.

Like
REPLY

 dgkanatsios says:
MARCH 3, 2017 AT 9:26 PM
Hi, have you tried changing the values on Constants.cs
file? https://github.com/dgkanatsios/MatchThreeGame/blob/master/Assets/Scripts/Constants.
cs#L9
Like
REPLY

 Janny says:
MARCH 3, 2017 AT 9:31 PM
Yes but when I did all of the 5 scenes GridSize I created by following your tutorial will
be equal. I just want it to be different for example

Level1 8×8
Level2 9×8
Level3 9×9
Level2 8×10
Level3 10×10

Like

 dgkanatsios says:
MARCH 3, 2017 AT 9:32 PM
Ah OK. In this case, I would have different variables for each level.
Like

 Janny says:
MARCH 3, 2017 AT 9:32 PM
Don’t mind the level names

Like

 Janny says:
MARCH 3, 2017 AT 9:34 PM
Can you please give me a sample sir?

Like

 dgkanatsios says:
MARCH 3, 2017 AT 9:39 PM
Well, the most simple way, if you are sure you will have a certain amount of levels is
load them into an array. Hence, for 3 levels you could do

public static readonly int[] Rows = {4,5,6}; //level 1: 4 rows, level 2: 5 rows etc.
public static readonly int[] Columns = {8,9,10}; //level 1: 8 rows, level 2: 9 rows etc.

And then,
in https://github.com/dgkanatsios/MatchThreeGame/blob/master/Assets/Scripts/Shapes
Manager.cs#L109
you could write Constants.Rows[0] for level 1, Constants.Rows[1] for level 2 etc.
Hope this helps!

Like

 Janny says:
MARCH 3, 2017 AT 9:46 PM
Will that work in a multiple scenes?

Like

 dgkanatsios says:
MARCH 3, 2017 AT 9:47 PM
well, what you have to do is select the proper value in the array depending on the scene
you are!
Like

 Janny says:
MARCH 3, 2017 AT 9:51 PM
Can you help me sir on how to do that? That will help me a lot sir.

Like

 Janny says:
MARCH 3, 2017 AT 10:04 PM
public static readonly int[] Rows = {4,5,6}; //level 1: 4 rows, level 2: 5 rows etc.
public static readonly int[] Columns = {8,9,10}; //level 1: 8 rows, level 2: 9 rows etc.

And then,
in https://github.com/dgkanatsios/MatchThreeGame/blob/master/Assets/Scripts/Shapes
Manager.cs#L109
you could write Constants.Rows[0] for level 1, Constants.Rows[1] for level 2 etc.

I tried this method but it gives me a lot of error in ShapesArray.cs, DebugUtilities.cs, and
in Utilities.cs

Like

 dgkanatsios says:
MARCH 4, 2017 AT 12:53 PM
Yup, you need to make some changes in the code, depending on how you have built your
level/scene flow.

Like

25. Nelson says:
MARCH 31, 2017 AT 1:26 PM
Mr. Dgkanatsios

I am recently learning how to make match3 game in Unity for my first commercial game app.
Your tutorial is very easy to understand, and I found that you code base is really good for me to
start coding and build new rule on top. I also understand that there are a lot of developers was
ask you the same question about if it is fine to use your match3 source code for development.
However, i just want to inquire you direct, to let me use and modify your source code freely
without charges and legal issue.
Of course, It would be very much my pleasure to info you when the game is released and have
you the enjoy it.

Thank you very much!

Like
REPLY

 dgkanatsios says:
MARCH 31, 2017 AT 1:28 PM
Thanks for the kind worlds. Feel free to use the code, check the license on the GitHub
repository: https://github.com/dgkanatsios/MatchThreeGame/blob/master/License.md
Like
REPLY

 Nelson says:
MARCH 31, 2017 AT 3:25 PM
Mr. Dgkanatsios, thanks for your speed reply. (just to double check, you wrote “feel free
to SUE the code”, you guess you meant “feel free to USE the code”, right?), so for being
so careful!
you have a great day!
Cheers!

Like

 dgkanatsios says:
MARCH 31, 2017 AT 3:27 PM
Haha, correct, fixed now :)

Like

26. Nelson says:
MARCH 31, 2017 AT 3:32 PM
wow. so cool, I did not know that you can actually fix the previous message?! that’s very cool. (I
am not a tech person!!! sorry!!!). I fully understood! You have a great day, Mr. Dgkanatsios.

Like
REPLY

27. Shahzaib says:
APRIL 5, 2017 AT 10:30 AM
Thanks for the Game i made a Flag Destroy with Match 3 i also added Admob advertisement its
working fine for me but after 5 to 10 Min its disappear what will be the problem i am still finding

Like
REPLY

28. Armyn says:
APRIL 7, 2017 AT 11:34 PM
Hi Mr. Gkanatsios, about the Shape class, you said;
“It contains … a constructor that initializes the bonus enumeration …”
“Since Shape is a MonoBehaviour that is attached to our prefabs (as we’ll see later), we cannot
use a constructor to initialize it.”

Could you please clarify;


– Why did you write a constructor, if Shape is a MonoBehaviour and cannot be used. What is its
purpose if it’s not used.
– Exactly when & where in your code the Shape() constructor is used/called, if it has a purpose.

Thanks for your tutorial and answers.

Like
REPLY

 dgkanatsios says:
APRIL 9, 2017 AT 9:57 AM
Hi, yup, we’re not calling the Shape constructor anywhere. This is (probably) called
internally by Unity, so the one line it contains is executed at that time. More proper way
would have been to either implement this line at Start() or Awake(), or just initialize the field
in the class (public BonusType Bonus = BonusType.None). Thanks!

Like
REPLY

29. Nelson says:
APRIL 8, 2017 AT 5:35 AM
Mr. Dgkanatsios
I was trying to put a new gameplay logic into the system, that is eventually base on many match
patterns. But first, I want to keep it simple and step by step. That’s of course, it is something I
should figure it out by myself, but I was wondering if there is way to find out the matches is a
vertical or horizontal match? I was looking into the MatchesInfo class, but it seems it does not
doing any bookkeeping on directional information. I think it is a right class to extend the
functionalities. If you have any good advice or pointers, it would be very much appreciated!
thank you sir!
Like
REPLY

 dgkanatsios says:
APRIL 9, 2017 AT 9:58 AM
Check here for the GetMatchesHorizontally
method https://github.com/dgkanatsios/MatchThreeGame/blob/master/Assets/Scripts/Shapes
Array.cs#L177
You’ll also see the GetMatchesVertically method nearby. thanks!
Like
REPLY

30. Nelson says:
APRIL 9, 2017 AT 5:30 PM
Thank you so much for your pointers! it works perfectly. :)

Like
REPLY

31. TheSuperhero says:
APRIL 30, 2017 AT 4:12 AM
Hello dgkanatsios,
Thanks for the tutorial, it was good that you even provided the code.
I want to know is there a possibility to add obstacles that do not move and the candy or shapes
move around the obstacle. like how we see in many match 3 games. If yes can you guide me how
to add them to the project.

Like
REPLY

 dgkanatsios says:
MAY 2, 2017 AT 8:28 PM
Hi and thanks for the kind words. So, what you ask can be done (of course) but it can be a
little tricky. There are two basic parts that obstacle-like functionality should be included.
1. At the candy comparison. When you compare candies either vertically or horizontally, you
should stop when you encounter an obstacle.
2. At the candy drop, when the user makes a successful match. Candy should *not* drop
vertically when an obstacle is directly below them.
Let me know if you have any more questions!

Like
REPLY
32. Paulo Edward John says:
JUNE 10, 2017 AT 3:15 AM
thanks for this wonderful tutorial!
guys how to add levels??

Like
REPLY

 dgkanatsios says:
JUNE 10, 2017 AT 8:39 AM
Thanks! You could replicate the one that is demonstrated here

Like
REPLY

33. Manuel says:
JUNE 26, 2017 AT 9:34 PM
Hello dgkanatsios, I have a quick question concerning the Shapes Manager. When I import the
code as a new asset in Unity 5.6.1 and open it in visual studio I encounter 5 errors. All five are
errors are CS1061 errors and state “error CS1061: Type `UnityEngine.Transform’ does not
contain a definition for `positionTo’ and no extension method `positionTo’ of type
`UnityEngine.Transform’ could be found. Are you missing an assembly reference?” I was
wondering what the problem is, and how I can fix it in order for the game to work.
-Thanks

Like
REPLY

 dgkanatsios says:
JUNE 26, 2017 AT 9:52 PM
positionTo is implemented in
GoKit https://github.com/dgkanatsios/MatchThreeGame/blob/586054556ef8fb093d50faab18
c30d71842daafc/Assets/Plugins/GoKit/extensions/GoKitTweenExtensions.cs
Like
REPLY

34. xindaar says:
DECEMBER 4, 2017 AT 1:14 AM
Hello and thanks for the match3 game code.
My problem is when there are no matches left on board (possible whit a 4×4 or 5×5 game board),
a new random board is not created and the game becomes stuck.
Can you show a workaround for this?
Like
REPLY

 dgkanatsios says:
DECEMBER 4, 2017 AT 10:01 AM
Thanks for the comment. Yeah, I tried to make the tutorial as simple as possible (even
though it became much bigger than I originally imagined!). Anyway, what I would suggest
you could do is use the Utilities.GetPotentialMatches method. This method returns null if it
cannot find any matches at all. So you could use this method to check for null, and, if this is
the case, take the user to a next level or restart the level or show a “congrats” message or
whatever you like. Hope this helps, Dimitris

Like
REPLY
35. URLS says:

DECEMBER 28, 2017 AT 2:02 AM

[…] Building a match-3 game (like Candy Crush) in Unity […]

Like
REPLY

36. Christian says:
DECEMBER 30, 2017 AT 3:05 PM
@dgkanatsios: can you tell me how much time you’ve invested to create this code?

Like
REPLY

 dgkanatsios says:
DECEMBER 30, 2017 AT 3:29 PM
Not sure, since it’s been a couple of years since I wrote it. I would guess two weeks part-
time, tops

Like
REPLY

37. Doi says:
JULY 26, 2018 AT 5:15 AM
what if I want to get matches diagonally?
Like
REPLY

 dgkanatsios says:
JULY 26, 2018 AT 10:20 AM
You could create another method that does exactly that. It would be similar to the
GetMatchesHorizontally and GetMatchesVertically methods

Like
REPLY

38. BPG says:
SEPTEMBER 25, 2018 AT 10:00 AM
Hello, thanks for the tutorial!

I was wondering how I would go about referencing specific colors of the candy to increase a
score for each. For example clearing blue candies would increase the score for “blue”, and pink
candies increase “pink”. How can I “check” for the color and increase the score from there?
Thank you!

Like
REPLY

 dgkanatsios says:
SEPTEMBER 25, 2018 AT 10:53 AM
Thanks for the comment. We’re already checking the Color via the “IsSameType” method
on the “Shape” class. That’s where we’re comparing candies to see if they are of the same
color.
To solve your problem, you could have an array (or better, a Dictionary) of scores where
each entry in the Dictionary would hold the score for the specified color.

Like
REPLY

 BPG says:
DECEMBER 19, 2018 AT 6:47 AM
Thanks for the solution!

I’m now trying to create a new bonus that clears all candies of the same color. I’m
following the same steps of the previous bonus but I’m unsure of how to create an
IEnumerable like GetEntireColumn, albeit for getting all the candy of the color with
which the bonus is swapped.
Like
39. Week 6 – (18/02/19 – 24/02/19) – task 4 – Gameplay and Prototyping says:

FEBRUARY 18, 2019 AT 4:07 PM

[…] taken from this tutorial https://dgkanatsios.com/2015/02/25/building-a-match-3-game-in-


unity-3/ Script: AlteredCandyInfo.cs, Constants.cs, DebugUtilities.cs, Enums.cs, MatchesInfo.cs,
Shape.cs, […]
Like
REPLY

40. Jacques says:
APRIL 14, 2019 AT 9:16 AM
Even though I’m no hardcore coder your efficient code and detailed description is, as far as I
have seen, the best example for making unique match 3 games. After trying out several other
match 3 tutorials/codes this is still the most streamlined. Although it has taken me nearly a week
to get to grips with how your routines talk to each other, I have managed to (finally) get a
reshuffle routine up and running as well as 2 different bonus styles (adjacent and same color) . I
just have a couple of questions though, f#1 is probably easy but I’m a noob with gokit, i use
scaleTo (in place of your color tinting for hint tiles, but how to make sure all game objects no
longer tween in case I need to restart a puzzleboard? GoTween does capture the error with a
yellow warning icon but any easier way of detecting which gameobjects are scaling with
goTween? #2 has me pulling out my hair for the last week, how on earth do we make it so that
new incoming tiles will also create bonus tiles for match 4/5 instead of just destroying them?
Appreciate any help and curious to know what are you doing these days? :)

Like
REPLY

41. Tac Tacelosky says:


OCTOBER 2, 2020 AT 1:18 PM
Terrific tutorial. I’ve forked the repo and updated the project to run in Unity
2019.4. https://github.com/tacman/MatchThreeGame/ I can make a PR to your repo if you’d like
these changes.
I also published the game at https://tacman1123.itch.io/match-three, the link to the demo at this
beginning of this tutorial is broken.
Like
REPLY

 dgkanatsios says:
OCTOBER 2, 2020 AT 6:35 PM
wow, thank you! Yes, I’d love a PR, also edit the blog post to point to your website!
Like
REPLY
Leave a Reply

DIMITRIOS ILIAS GKANATSIOS


Software Engineer, Azure PlayFab Multiplayer Servers

SOCIAL
 Twitter
 
 GitHub
 
 LinkedIn

RECENT POSTS
 Dedicated Game Server Scaling on Azure Kubernetes Service – OpenConf presentation

 Online webinar – Cloud-native architecture and containers


 Certified Kubernetes Application Developer
 Presenting at Microsoft Build 2018 about game development and Azure
 Big data reference architecture for an online multiplayer game on Azure
 Using Azure Functions, Node.js and Cosmos DB to create a game  leaderboard
 Designing a general purpose game leaderboard
 Deploying a Docker Swarm Mode cluster on Azure Container Service
 Using SSL/TLS for a service hosted on a Kubernetes cluster
 Greek language support on Azure Text Analytics API for sentiment analysis
Search for:

ARCHIVES
Archives                                                                                                                                             

Back to top

You might also like