TGB/Tutorials/Match 3
From TDN
Match 3 Tutorial
Original source by: Issac Barbosa
Article by: Gavin Koh dated 27 Sep 2009 16:02 local time Singapore
with suggestions / corrections from Joe Rossi
Written for TGB Version: 1.74
Welcome to this tutorial on implementing the bare bones for a Match 3 engine.
The original Match 3 code was implemented by Issac Barbosa and released on 19 August 2008 in the TGB forums.
On 15 Sep 2009, more than a year later, I took the source code and went through it to optimize the engine. I (and with Isaac's permission) would like to share with you the new code for the Match 3 engine in this tutorial.
Without much further ado, let the Match 3 tutorial begin!
Tutorial Revision History
v1 - 15 Sep 2009: First version
v2 - 4 Jul 2010: Made it a bit more stable and readable. Hope you learn from this tutorial and if you build upon it, head to the forums to let us all know. Happy 4th of July and thanks for reading!
v3 - 16 Jul 2010: Made the game even more stable and decided to show more of my implementation. The game's far from complete and lacks all the bells and whistles of actual match 3 games, but I'd say there is a foundation for others to learn from here.
Part 1 - Getting Started
What's a match 3 game without graphics?
Let's start off by creating a new project in Torque Game Builder and import the required image maps for the game. In our tutorial, the image maps depict 7 animated gems having the colors of blue, cyan, green, orange, purple, red, white, and yellow.
1. Startup Torque Game Builder and create a new project -- Call it Match3Tutorial. If this is your first encounter with TGB, I recommend you refer to the Fish Demo that comes as an installed shortcut called "Documentation Overview" in your TGB folder.
2. Downloadable artwork of the animating gems used in the tutorial are available here: [1]. Place the 7 image maps in your Match3Tutorial\game\data\images\ folder.
3. Import the 7 image maps into TGB via the menu command:Project->Image Map Builder. Use an Image mode of CELL and set the following parameters:
Cell Count X = 8
Cell Count Y = 2
Cell Width = 32
Cell Height = 32
4. Select the menu command: Project->Animation Builder and repeat for each of the 7 image maps. Once you enter the builder, double-click on each of the 16 gems (of one color) to build up the animation sequence. For a nice animation, start from the top to the bottom row and select the gems from a left to right order. Click on save and the result will be an Animated Sprite of the gem.
5. Next, you will have to add a Tilemap to your scene. Position it near the middle of your scene and edit the following parameters:
Tile Map->Tile Count->X and Y = 8, 8
Tile Map->Tile Size->X and Y= 6, 6
Scripting->Name = "grid"
Scripting->Class = "Board"
6. Save your level as level1.t2d using File->Save.
PS. Sorry, there are a few more instructions right at the end to add in the debugging textObjects. The rest of your work is now to cut and paste and create the following Torquescript source files.
Part 2 - Creating the source files
The next phase of the tutorial involves coding the Match 3 engine. When you created your project, you will find 3 source files in the Match3Tutorial\game\gamescripts folder. Let's begin by modifying game.cs.
Part 2a - Changes to game.cs
Open game.cs and replace the entire function with the one shown below. There really isn't anything significant here, it just ensures the correct source files and level scenes are loaded. There are also a couple of globals that are initialised (this is still a work in progress for me to dabble with).
function startGame(%level)
{
Canvas.setContent(mainScreenGui);
Canvas.setCursor(DefaultCursor);
sceneWindow2D.setUseObjectMouseEvents(true);
new ActionMap(moveMap);
moveMap.push();
SetCanvasTitle("My First Match Three Game");
exec("./board.cs");
exec("./score.cs");
exec("./gem.cs");
exec("./debug.cs");
$swap = false;
$swapnotready = false;
$swapinprogress = false;
$checkingformatch = false;
$score = 0;
//debug flags
$dcx = 0;
$dcy = 0;
$enableDirectInput = true;
activateDirectInput();
enableJoystick();
//setScreenMode(640, 480, 32, false);
$LevelNumber = 1;
%level = "~/data/levels/level" @ $LevelNumber @ ".t2d";
if(isFile( %level ) || isFile( %level @ ".dso"))
sceneWindow2D.loadLevel(expandFilename(%level));
}
Part 2b - board.cs
Create a new file called board.cs and paste the following code within.
function Board::onLevelLoaded(%this, %scenegraph)
{
//just so we can use shorter names
$board = %this;
$board.xCount = $board.getTileCountX();
$board.yCount = $board.getTileCountY();
//Setting a repeatable scenario makes it very useful for debugging
//When playing the game, comment the next line
setrandomseed(5);
//Create a new set of all the pieces on the board
$PiecesInPlay = new SimSet();
//Create random gems for the entire board
for(%a = 0; %a < $board.xCount; %a++)
{
for(%b = 0; %b < $board.yCount; %b++)
{
//we're going to use the piece from the levelbuilder as a model for the rest
$board.pieces[%a,%b] = new t2dAnimatedSprite()
{
scenegraph = %this.getSceneGraph();
class = "gem";
layer = "10";
};
$board.pieces[%a,%b].setSize(6,6);
$board.pieces[%a,%b].match = false;
%color = GemRandomColor();
$board.pieces[%a,%b].gcolor = %color;
$board.pieces[%a,%b].playAnimation(%color @ "_DiamondAnimation");
$PiecesInPlay.add($board.pieces[%a,%b]);
//set the position of the piece relative to the tile layer (grid) we made
$board.pieces[%a,%b].setPosition(getTileGridPosition(%a, %b));
//back up the current X and Y positions
%xpos = $board.pieces[%a,%b].getPositionX();
%ypos = $board.pieces[%a,%b].getPositionY();
$board.pieces[%a,%b].prevX = %xpos;
$board.pieces[%a,%b].prevY = %ypos;
//make sure our gem piece is clickable!
$board.pieces[%a,%b].setUseMouseEvents(true);
}
}
//set the score to be updated every 500 msec
score.setTimerOn(500);
//the first thing we do is to schedule a check for matches 2000 msec later
//why 2000 msec later? this gives time for the grid to be setup
$checkingformatch = true;
schedule(2000, 0, CheckForMatches1);
}
//-----------------------------------------------------------------------------------------
//Private Methods
//-----------------------------------------------------------------------------------------
function getTileGridPosition(%x, %y)
{
// grab the Board layer's position
%tileMapPos = $board.getPosition();
// divide the position up into x and y variables
%tileMapPosX = getWord(%tileMapPos, 0);
%tileMapPosY = getWord(%tileMapPos, 1);
// grab the Board layer's size
%tileMapSize = $board.getSize();
// divide the size up into x and y variables
%tileMapSizeX = getWord(%tileMapSize, 0);
%tilemapSizeY = getWord(%tileMapSize, 1);
// calculate the start position
%tileMapStartX = %tileMapPosX - (%tileMapSizeX / 2);
%tileMapStartY = %tileMapPosY - (%tileMapSizeY / 2);
// currently size set to 6, 6
%tS = $board.getTileSize();
// calculate the position and pass it back
%pos = (%tileMapStartX + (%x * %tS)) + %tS/2 SPC (%tileMapStartY + (%y * %tS)) + %tS/2;
return %pos;
}
Upon "level-load", a random grid of gems is built via the onLevelLoaded function. The code is pretty straightforward and it involves creating gem objects and linking them to the pieces array. The pieces array stores important information such as:
match - boolean indicating a match
gcolor - gem color
prevx - a temporary storage for the x position of the gem in the pieces(x,y) array
prevy - a temporary storage for the y position of the gem in the pieces(x,y) array
size - the size of the gems is defaulted to 6x6, so there's no need to invoke the resize function to grow the gems
Notice that gems also have their setUseMouseEvents enabled. This means they will react to mouse clicks.
And, the score object is setup to be updated every 500 msec.
Part 2c - gem.cs
Create a new script file for gem.cs and paste the following code within.
//-----------------------------------------------------------------------------------------
//Public Methods
//-----------------------------------------------------------------------------------------
//On clicking the mouse button on the gem
function gem::onMouseDown(%this, %modifier, %worldPosition, %mouseClicks)
{
//are you trying to double-click?
if (%mouseClicks > 1) return;
//has the swap completed?
if ($swapnotready) return;
//the $swap flag is a toggle state only for this function
if ($swap $= false)
{
//prepare to swap this first gem...
$swap = true;
$swapcount = 0;
$swappreviousX = %this.getPositionX();
$swappreviousY = %this.getPositionY();
$swappreviousc = %this.gcolor;
$swappreviousGem = %this;
}
else
{
//...with this second gem
$swapforthisX = %this.getPositionX();
$swapforthisY = %this.getPositionY();
$swapforthisc = %this.gcolor;
$swapforthisGem = %this;
%noswap = false;
//don't allow diagonal and same spot swaps
if ((($swapforthisX != $swappreviousX) && ($swapforthisY != $swappreviousY)) || (($swapforthisX == $swappreviousX) && ($swapforthisY == $swappreviousY)))
%noswap = true;
//don't allow same row/column swaps of more than 2 spaces
if ((abs($swapforthisX - $swappreviousX) > 6) || (abs($swapforthisY - $swappreviousY) > 6))
%noswap = true;
if (%noswap == true)
{
$swap = false;
$swapforthisX = $swappreviousX = $swapforthisY = $swappreviousY = -1;
return;
}
else
{
//this is a valid swap
$swap = false;
$swapnotready = true;
$swapinprogress = true;
//proceed to swap the two gems
%speed = 20;
%this.moveTo($swappreviousX, $swappreviousY, %speed, true, true);
$swappreviousGem.moveTo($swapforthisX, $swapforthisY, %speed, true, true);
}
}
}
//This function is called when the gem has reached its target position via the moveTo command
//Both swapped gems will execute this code
function gem::onPositiontarget(%this)
{
//this portion of code is called from CheckForMatches1
if ($checkingformatch == true)
{
//give some time for the gems to move downwards into empty spaces
schedule(400, 0, CheckForMatches2);
}
if ($swapinprogress == true)
{
$swapcount++;
//this check makes sures both gems have reached their target positions
if ($swapcount == 2)
{
//make the gems vanish for a very short little while
$swapforthisGem.setSize(0,0);
$swappreviousGem.setSize(0,0);
//quickly teleport them back to their original positions
//the net effect is - no pieces actually moved
$swapforthisGem.setPosition($swapforthisX, $swapforthisY);
$swappreviousGem.setPosition($swappreviousX, $swappreviousY);
//now quickly swap the color
$swapforthisGem.gcolor = $swappreviousc;
$swapforthisGem.playAnimation($swapforthisGem.gcolor @ "_DiamondAnimation");
$swappreviousGem.gcolor = $swapforthisc;
$swappreviousGem.playAnimation($swappreviousGem.gcolor @ "_DiamondAnimation");
//aabracadabra - make the gems re-appear
$swapforthisGem.setSize(6, 6);
$swappreviousGem.setSize(6, 6);
//check for matches
schedule(20, 0, CheckForMatches1);
$swapinprogress = false;
}
}
}
//For debugging
function gem::onMouseEnter(%this, %modifier, %worldPosition, %clicks)
{
debugger(%this);
}
//-----------------------------------------------------------------------------------------
//Private Methods
//-----------------------------------------------------------------------------------------
function abs(%a)
{
return mAbs(%a);
}
//called when a new gem has been placed
function GrowGem()
{
%NumberToGrow = 0;
//scans entire board and places gems that need placing
for (%i = 0; %i < $board.xCount; %i++)
{
for (%j = 0; %j < $board.yCount; %j++)
{
if($board.pieces[%i,%j].getSizeX() < 6)
{
%NumberToGrow++;
$board.pieces[%i,%j].setSizeX($board.pieces[%i,%j].getSizeX() + 0.5);
$board.pieces[%i,%j].setSizeY($board.pieces[%i,%j].getSizeY() + 0.5);
}
}
}
if (%NumberToGrow > 0)
schedule(5, 0, GrowGem);
}
//called when a matched gem must disappear
function ShrinkMatchedGem()
{
%NumberToShrink = 0;
//scans entire board and places gems that need placing
for (%i = 0; %i < $board.xCount; %i++)
{
for (%j = 0; %j < $board.yCount; %j++)
{
if(($board.pieces[%i,%j].match == true) && (($board.pieces[%i,%j].getSizeX() > 0.01) && ($board.pieces[%i,%j].getSizeY() > 0.01)))
{
%NumberToShrink++;
$board.pieces[%i,%j].setSizeX($board.pieces[%i,%j].getSizeX() - 0.5);
$board.pieces[%i,%j].setSizeY($board.pieces[%i,%j].getSizeY() - 0.5);
}
}
}
//recurse until all that needs to shrink has been shrunk
if (%NumberToShrink > 0)
schedule(5, 0, ShrinkMatchedGem);
}
//gets a random color
function GemRandomColor()
{
%color = getRandom(7);
switch$(%color) {
case "0": %color = "red";
case "1": %color = "blue";
case "2": %color = "cyan";
case "3": %color = "green";
case "4": %color = "purple";
case "5": %color = "yellow";
case "6": %color = "orange";
case "7": %color = "white";
}
return %color;
}
function CheckForMatches1()
{
//Part 1 of CheckForMatches1
for (%i = 0; %i < $board.xCount; %i++)
{
for (%j = 0; %j < $board.yCount; %j++)
{
//check horizontal first
%matchcount = 0;
%a = %i;
%b = %j;
//get the matching color
%c = $board.pieces[%a,%b].gcolor;
//get the check start point one position to the right (i.e. don't count myself)
%a++;
//move right from the gem being checked without exceeding the right border
while (%a < $board.xCount)
{
if ($board.pieces[%a,%b].gcolor $= %c)
{
%a++;
%matchcount++;
}
else
%a = $board.xCount;
}
//get the check start point and move one position to the left
%a = %i - 1;
//move left from the gem being checked without exceeding the bounds of the left border
while (%a >= 0)
{
if ($board.pieces[%a,%b].gcolor $= %c)
{
%a--;
%matchcount++;
}
else
%a = -1;
}
//check if at least 2 minimum matches were achieved
//flag all the gems that match
if (%matchcount >= 2)
{
//get the check start point
%a = %i;
//move right
while (%a < $board.xCount)
{
if ($board.pieces[%a,%b].gcolor $= %c)
{
$board.pieces[%a,%b].match = true;
%a++;
}
else
%a = $board.xCount;
}
//get the check start point
%a = %i - 1;
//move left
while (%a >= 0)
{
if ($board.pieces[%a,%b].gcolor $= %c)
{
$board.pieces[%a,%b].match = true;
%a--;
}
else
%a = -1;
}
}
//check vertical next
%matchcount = 0;
//get the check start point but one position down (i.e don't count myself)
%a = %i;
%b = %j + 1;
//move down from the gem being checked
while (%b < $board.yCount)
{
if ($board.pieces[%a,%b].gcolor $= %c)
{
%b++;
%matchcount++;
}
else
%b = $board.yCount;
}
//get the check start point
%b = %j - 1;
//move up from the gem being checked
while (%b >= 0)
{
if ($board.pieces[%a,%b].gcolor $= %c)
{
%b--;
%matchcount++;
}
else
%b = -1;
}
//check if minimum matches were achieved
if (%matchcount >= 2)
{
//get the check start point
%b = %j;
//move down
while (%b < $board.yCount)
{
if ($board.pieces[%a,%b].gcolor $= %c)
{
$board.pieces[%a,%b].match = true;
%b++;
}
else
%b = $board.yCount;
}
//get the check start point
%b = %j - 1;
//move up
while (%b >= 0)
{
if (%this.pieces[%a,%b].gcolor $= %c)
{
%this.pieces[%a,%b].match = true;
%b--;
}
else
%b = -1;
}
}
}
}
//Count the actual number of gems marked as matched
%actualmatches = 0;
for (%i = 0; %i < $board.xCount; %i++)
{
for (%j = 0; %j < $board.yCount; %j++)
{
if ($board.pieces[%i,%j].match == true)
%actualmatches++;
}
}
//check if there are matches
//if there are, make the matched gems disappear
if (%actualmatches > 0)
{
schedule(5, 0, ShrinkMatchedGem);
}
else
{
//there were no matches!
$checkingformatch = false;
$swapnotready = false;
return;
}
//Part 2 of CheckForMatches1
%movedown = false;
for (%i = 0; %i < $board.xCount; %i++)
{
//Make all gems above matched gems move downwards
%contrib = 0;
for (%j = $board.yCount - 1; %j >= 0; %j--)
{
if ($board.pieces[%i,%j].match == true)
{
%contrib++;
}
if ((%contrib > 0) && ($board.pieces[%i,%j].match != true))
{
%xpos = $board.pieces[%i,%j].getPositionX();
%ypos = $board.pieces[%i,%j].getPositionY();
$board.pieces[%i,%j].prevX = %xpos;
$board.pieces[%i,%j].prevY = %ypos;
//The Callback flag is true, so code continues execution at OnPositionTarget
$board.pieces[%i,%j].moveto(%xpos, %ypos + (6 * %contrib), 64, true, true);
%movedown = true;
}
}
}
if (%movedown == true)
$checkingformatch = true;
//In case there is a match, and there is no need to make any gems move down
//(this is more for matches involving the top row)
if ((%actualmatches > 0) && (%movedown == false))
schedule(300, 0, CheckForMatches2);
}
function CheckForMatches2()
{
//Now that gems were moved downwards VISUALLY, we can proceed with the second stage
for (%i = 0; %i < $board.xCount; %i++)
{
//First, we move all matches upwards and gems downwards
%l = -1;
for (%j = 0; %j < $board.yCount; %j++)
{
if ($board.pieces[%i,%j].match == true)
{
$board.pieces[%i,%j].match = false;
%l++;
for (%k = %j; %k > %l; %k--)
{
$board.pieces[%i,%k].gcolor = $board.pieces[%i,%k - 1].gcolor;
$board.pieces[%i,%k].playAnimation($board.pieces[%i,%k - 1].gcolor @ "_DiamondAnimation");
$board.pieces[%i,%k].setsize(6,6);
}
$board.pieces[%i,%k].match = true;
}
}
//Second, we grow a new gem where there are matches and reposition all gems where they were supposed to be
for (%j = $board.yCount - 1; %j >= 0; %j--)
{
if ($board.pieces[%i,%j].match == true)
{
$board.pieces[%i,%j].match = false;
$board.pieces[%i,%j].setPosition($board.pieces[%i,%j].prevX SPC $board.pieces[%i,%j].prevY);
%color = GemRandomColor();
$board.pieces[%i,%j].gcolor = %color;
$board.pieces[%i,%j].playAnimation(%color @ "_DiamondAnimation");
$board.pieces[%i,%j].setSize(0,0);
$score+=10;
}
else
$board.pieces[%i,%j].setPosition($board.pieces[%i,%j].prevX SPC $board.pieces[%i,%j].prevY);
}
}
//Grow all new gems whose size are 0,0
schedule(5, 0, GrowGem);
//Check again in case new matches were formed as a result of growing new gems
$checkingformatch = false;
schedule(700, 0, CheckForMatches1);
}
There are truly two main parts to gem.cs.
The first involves selecting the two gems via the mouse. The player only needs to click once (onMouseDown function is executed) and he then follows this with a second click to swap these two gems (onMouseDown function is executed again but a different portion is executed thanks to the toggle flag called $swap). There are checks to prevent a swap when a check is in progress, or when an illegal swap is made. Next, the actual swapping of gems is performed via the .moveTo method. The .moveTo method calls have their callback variable set to true and this causes the method .onPositionTarget to be "called back" when the movement of the gems is completed.
For the code found in the .onPositionTarget method, notice that once the physical movement of the two gems has completed, the two gems are actually re-positioned back to their original locations (you must follow the $swapinprogress == true portion of code). But, in the array for pieces, the gems are swapped to the requested locations. All of this is done so fast that the player is not able to discern anything is happening. Next, we check for matches by scheduling CheckForMatches1, then exit.
For the second part, let's start off by talking about the CheckForMatches1 function. This function is divided into two areas of responsibility.
The first part is to scan through the entire board and flag the pieces.match variable to true if a match of at least 3 gems is found. The board is stepped through one location at a time, and matches are optimally searched for in a horizontal direction, then in a vertical direction. The searching is made in a manner such that the boundaries of the pieces array are not exceeded in any way.
After this, the entire board is checked again to see how many actual matches were found. If there were actually matches, these gems will be shrunk and made to disappear from the board (by a call to ShrinkMatchedGems).
The second task is to move all gems above matched gems fall downwards. Once again, the .moveTo method is invoked with a callback. This time though, a different flag called $checkingformatch is used. Under the .onPositionTarget method, you will see that once all gems have moved downwards, we schedule the CheckForMatches2 function.
CheckForMatches2 has two main tasks. The first is to float all matched gems to the top of the board, and to move all remaining gems to the bottom of the board. This movement is all done virtually in memory (recall we had already used the .moveTo method previously to actually show the gems falling downwards). The next task is to grow a new random color gem (via a call to GrowMatchedGems) on the spaces where we had matches earlier, and to reposition all gems to their original positions (prevX and prevY). Finally, we schedule the CheckForMatches1 again, and this is repeated ad infinitum until no more matches can be found.
Part 2d - score.cs
This one is simple. Just paste this into a new file called score.cs:
//-----------------------------------------------------------------------------------------
//Public Methods
//-----------------------------------------------------------------------------------------
function score::onTimer(%this)
{
%this.text = "score:" SPC $score;
}
Part 2e - debug.cs
// The following lines contains watch strings that can be pasted in the Torsion Watch tab
// --- Watch a column of the board. Initialise %i in the code to select the column of interest
// $board.pieces[%i,0].getPosition() SPC $board.pieces[%i,0].getSize() SPC $board.pieces[%i,0].match SPC $board.pieces[%i,0].gcolor SPC $board.pieces[%i,0].prevx SPC $board.pieces[%i,0].prevy
// $board.pieces[%i,1].getPosition() SPC $board.pieces[%i,1].getSize() SPC $board.pieces[%i,1].match SPC $board.pieces[%i,1].gcolor SPC $board.pieces[%i,1].prevx SPC $board.pieces[%i,1].prevy
// $board.pieces[%i,2].getPosition() SPC $board.pieces[%i,2].getSize() SPC $board.pieces[%i,2].match SPC $board.pieces[%i,2].gcolor SPC $board.pieces[%i,2].prevx SPC $board.pieces[%i,2].prevy
// $board.pieces[%i,3].getPosition() SPC $board.pieces[%i,3].getSize() SPC $board.pieces[%i,3].match SPC $board.pieces[%i,3].gcolor SPC $board.pieces[%i,3].prevx SPC $board.pieces[%i,3].prevy
// $board.pieces[%i,4].getPosition() SPC $board.pieces[%i,4].getSize() SPC $board.pieces[%i,4].match SPC $board.pieces[%i,4].gcolor SPC $board.pieces[%i,4].prevx SPC $board.pieces[%i,4].prevy
// $board.pieces[%i,5].getPosition() SPC $board.pieces[%i,5].getSize() SPC $board.pieces[%i,5].match SPC $board.pieces[%i,5].gcolor SPC $board.pieces[%i,5].prevx SPC $board.pieces[%i,5].prevy
// $board.pieces[%i,6].getPosition() SPC $board.pieces[%i,6].getSize() SPC $board.pieces[%i,6].match SPC $board.pieces[%i,6].gcolor SPC $board.pieces[%i,6].prevx SPC $board.pieces[%i,6].prevy
// $board.pieces[%i,7].getPosition() SPC $board.pieces[%i,7].getSize() SPC $board.pieces[%i,7].match SPC $board.pieces[%i,7].gcolor SPC $board.pieces[%i,7].prevx SPC $board.pieces[%i,7].prevy
// %i SPC %j
//-----------------------------------------------------------------------------------------
//Public Methods
//-----------------------------------------------------------------------------------------
function d4l::onMouseDown(%this, %modifier, %worldPosition, %mouseClicks)
{
if (%mouseClicks > 1) return;
if ($dcx < 1) return;
$dcx--;
debugger(NULL);
}
function d4r::onMouseDown(%this, %modifier, %worldPosition, %mouseClicks)
{
if (%mouseClicks > 1) return;
if ($dcx > 6) return;
$dcx++;
debugger(NULL);
}
function d5l::onMouseDown(%this, %modifier, %worldPosition, %mouseClicks)
{
if (%mouseClicks > 1) return;
if ($dcy < 1) return;
$dcy--;
debugger(NULL);
}
function d5r::onMouseDown(%this, %modifier, %worldPosition, %mouseClicks)
{
if (%mouseClicks > 1) return;
if ($dcy > 6) return;
$dcy++;
debugger(0);
}
function debugger(%this)
{
//show debug text based on which gem the mouse has landed on
if (%this != 0)
{
db1.text = "db1:" SPC %this.gcolor;
db2.text = "db2:" SPC %this.getPositionX() SPC %this.getPositionY();
db3.text = "db3:" SPC %this.match SPC %this.falldown;
}
db4.text = $dcx;
db5.text = $dcy;
db6.text = $board.pieces[$dcx,$dcy].gcolor;
db7.text = $board.pieces[$dcx,$dcy].getPositionX() SPC $board.pieces[$dcx,$dcy].getPositionY();
db8.text = $board.pieces[$dcx,$dcy].match SPC $board.pieces[$dcx,$dcy].falldown;
}
This part is self-explanatory. The db1 - db8 texts are meant to assist in debugging. They are called whenever you mouse over any gem (refer back to onMouseEnter method in gem.cs). Whereas for d4l, d4r, d5l, d5r, you will have to take any static image map and plunk them down on the scene. Once you click on these images, you can view debug information for the grid of gems real-time. d4l, d4r essentially let's you move along the x-axis (left / right) while d5l, d5r let's you move along the y-axis (up / down).
Part 3 - Finishing up
Wait, but there's more!
The Match 3 Tutorial game won't run if you don't create 8 text objects in the level scene. The 8 text objects have the same Scripting->Name and text. They are: score, and db1 to db8. Instead of using the echo command, I chose to use the db1 to db8 text objects to display variables real-time in the game. I find that showing your variables in-situ is very important and can help greatly in troubleshooting. You can remove these at the final release version of your game.
Place the four static image maps next to db4 and db5 and call them d4l, d4r, d5l, d5r. Their use is explained in debug.cs.
Now, save your scene. And, that's it. With all the required changes done, let's give it a test run by playing the scene.
Part 4 - End of the tutorial
We have come to the end of the Match 3 tutorial. Notice that this is just the bare bones for a Match 3 engine; there's actually a lot more you could implement. As for additional ideas, if you had downloaded Isaac's original implementation (follow the link below), you will find he had originally implemented a rusty gem that would freeze if it was moved too many spaces on the grid. Why not embark on this exercise to put this feature back into the game?
Well that's it for now. Hope you like the tutorial! For any feedback or questions, pls continue your discussion at this location.
Return to Tutorial Hub



