Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Game Design
Gamedevelopment

Create Your Own Match 3 Game With Flixel: Polish

by
Difficulty:IntermediateLength:LongLanguages:

This week in Active Premium, we'll show you how to complete your Bejeweled-style Match 3 game, adding a title screen, a scoring system, and a Hint button; improving the game mechanics; and incorporating sound effects. In other words, a lot of polish!


Also available in this series:

  1. Create Your Own Match 3 Game With Flixel: Core Game Mechanics
  2. Create Your Own Match 3 Game With Flixel: Polish

Step 43: Check, Swap, Clear and Fall PART 10

We need to make new gems fall after we clear the match. For now let's indicate that the clearing does take place, and we'll handle the falling after that:

And while we are at it, we can make our timer start counting.

Now let's declare new function that will spawn new gems, let's call it spawn():


Step 44: Check, Swap, Clear and Fall PART 11

Remember what kind of arguments the fall() function took? Those were columnID, rowID and gemCount. We have neither columndID nor rowID of swapped gems, so we'll need to calculate them:

All right, since we've taken care of that, now let's start the actual function. Firstly, we'll need to handle similar cases as in clear() function. That means we need to separately deal with the cases such as both swapped gems found a match, or only one of them did. That's because our fall()function isn't really prepared for handling more than one fall() in a single column. That also means that there will be a few additional cases in a case that both gems found a match. :)


Step 45: Check, Swap, Clear and Fall PART 12

Now let's create our clearLeft, clearRight, clearUp, clearDown variables, because using st and nd arrays would be tedious:

While we are at it, let's find the gem that's in our first column in which we need to use fall(), the left-most one. We'll need to look for a right-most one too. Those will let us create the boundaries in which we'll be making our gems fall(), the gems beyound that bound mean little to us at the moment.


Step 46: Check, Swap, Clear and Fall PART 13

Before we start dropping all those gems, it's good to visualize what the possible gems' positions are in this case.


Yellow and red circles are gems of two different types. The gems with a hole are the once that player swapped.

Now let's start writing the biggest loop in this function. It'll start iterating from the leftMost column we need to cover, and finish on the rightMost one - the last one. In every step, we'll be calculating where and how to use fall() function.

Let's start adding particular cases to our loop. The first case we'll cover is that both, columnID and columndID2 are equal to i. It's quite a specific case because we know that both swapped gems are in the same column, we also know that this is probably the column with the most cleared gems, and we need to handle this case separately. It isn't hard to do so, because we know the number of gems above and below the swapped gems.


Now we need to know which gem is the lowest one, because our fall() function takes as an argument the row and the column of the lowest gem. We need to add to handle separately the case when the first swapped gem is lower than the second, and vice versa:

And finally we can have enough data to use our fall() function:

The row and the column are dependant on which of the gems is lower, but the number of cleared gems is always the same, it's simply all the gems above the swapped ones, below them and finally the swapped gems themselves (the constant value of 2). Let's also add continue command, so the loop will know that we solved this column and we can go to the next one:


Step 47: Check, Swap, Clear and Fall PART 14

Let's continue and solve next case that we need to handle. We can try to solve for the columns where only one type of gem is cleared. Checking this is pretty straightforward: we can check in which columns one of the types exists, and if there are any gems in other columns, we need to make them fall().

All right, as you noticed I already put the conditional to find "single type" columns for each of type respectively. I also added else, because now if none of the previous conditionals will be fulfilled, that means we're dealing with a column where there are two types of gems. For now let's deal with the cases on hand, the ones that I explained earlier. We start from the case that there are only gems of the second swapped gem's type. There are another two cases here, either i is equal to swapped gem's columnID or it's not. If it is, we need to make all the cleared gems from above and below of the swapped gem to disappear.


If it's not, we simply need to make only 1 gem fall(), because that's the case when the horizontal match has been formed.


I hope that didn't confuse you, we need to repeat pretty much the same thing for another of the swapped gems, and that simply means copying the code we just wrote and changing variables that were associated with the second swapped gem, to the ones associated with the first one.




Step 48: Check, Swap, Clear and Fall PART 15

Now the only cases that are left and are needed to cover are the ones with two kinds of gems in one column, and since we covered the case that columnID == columnID2, there is only one left to solve, that is, if there are two gems cleared in one column and each of them is of a different type. We need only to decide which of them is the lowest one, and we can deal with that case pretty quickly:


And that's all! We didn't finish the whole function yet, but the rest will be, as I noted earlier, a piece of cake. We only need to go through one match at a time. :)


Step 49: Check, Swap, Clear and Fall PART 16

Let's write up what should we do when the first swapped gem found a match:

Firstly, let's make the gems fall() in the swapped gem's column, we know that we need to point the fall() function to the lowest gem and tell it how many cleared gems are in the column:

Nothing complicated here. Secondly, let's go from the first gem from the left to the first gem from the right, making every cleared gem fall() there. Of course we shouldn't make fall() twice the gems in the swapped gem's column, so we'll naturally skip that one:

And we're done with this case! I told you it will be piece of cake compared to the huge function up there that we just finished too!


Step 50: Check, Swap, Clear and Fall PART 17

Finally let's finish the whole function by doing the same thing we did in a previous step, but this time if only the second swapped gem found a match:

Now we repreat the process from the previous case, firstly let's take care of the swapped gem's column:

And now the rest of them:

And we're done with the whole function! It may seem like a big and inefficient function that may slow things down, but that really isn't the case. That's because we really call it very rarely, even if there is a match. :) The most important thing is to keep our update function as clean as possible!


Step 51: Check, Swap, Clear and Fall PART 17

Now we should go to our update() function, we're still not leaving GameState.as. Let's add our spawn() function to match handling:

Let's also note that now the gems are falling, and we need to wait a bit until we can move anything around:

Now we need to let the game know when we can start interacting with the game again:

After the time we specified elapsed, we should simply return to our original state, that is, all three variables (falling, clearing, swapping) should be false. Two of them are already in this state, so we need to set only the falling:

Let's make our click-checking be used only when we're not handling a match, by simply adding an else statement. We also should make selID and swapID equal to -1, since now the match is completed we don't want to have any unneeded values in them. Especially selID is vital here, because we're also using it at our click-handling part of the update() function:

We're done! Now everything should work properly!


Step 52: Check, Swap, Clear and Fall FINAL PART

It's time to test everything out. The best way to test for errors is to play the game, and now we can really call it one. :) I changed the values of swapTime, clearTime, fallTime to the ones that felt right -- one second was just too much time. Here's what you should see after running the project:

Everything seems to work properly! Now the only thing that's obstructing the gameplay is those unmatched sets of gems that shouldn't really exist! We'll take care of them in the next few steps.


Step 52: Start with no Matches Part 1

The thing we should start from is preventing any matches to be made during the board creation. It's simple, because all we need to do, is to make sure that the latest gem is not in any match. If it is, recreate it (change its type), until the match is broken! Let's check our code in GameState.as that fills the board with gems:

After we push() the gem in our gems array, we should simply check that the pushed gem isn't in a match. Note that we can't really use our check() function, because it also checks the gems ahead. In this case there aren't any gems ahead, so we would just break the game. Let's duplicate our check() function, and change it so it won't care about the gems to the right of and below the gem we are checking (because these are created later):

Firstly, let's change the arguments. We don't really need to hold the data aquired in this check, so we won't be saving any properties to st or nd arrays. We don't really need to skip any IDs either, since we're not moving any gems. The next thing would be to delete all the code that's useless to us and create temporary variables to hold data for our check:

And we're done! Note that we also had to change the conditions by which we either return true or false, but not by much though, we actually simplified it like the whole function.


Step 53: Start with no Matches Part 2

Again, let's go back to our constructor function (GameState()), to the part where we're pushing the gems to our gems array. While we are at it, let's add the code that we spoke about. We'll be using a while loop, because we don't really know whether the next random type will be the same but will still create a match with other gems. That's why we'll be checking all the time whether there's a match or not until there is no match:

And that's it. Let's press F5 or whatever the shortcut for testing the project in your IDE is, and see if there indeed aren't any matches.


You can check it twice or thrice to make sure and the result will be the same. No matches on the screen!


Step 54: Chain Matches

It's time to finish the basic gameplay features, and that of course includes chain matches. If, after making a match, the dropped gems will pair up and create a new one, right now they do nothing. That shouldn't be so, because there should be no match unhandled in our game. We basically need to check all the gems that were dropped, and see if there are any randomly created matches.



I hope the sketches make it clear. How are we going to keep the list of gems that need to be checked? We need to create an array for that. We'll fill it with the gems that need to be checked in fall() function, because we know exactly which gems are falling there, and we'll start checking when all the new gems will be dropped on their "new positions". Let's create that array now.


Step 54: Pushing the Gems to Check

Let's go to our fall() function in GameState.as, and add the gems that need to be checked to our checkIDs array. We need to push only those IDs that have been dropped by any number of rows.

Basically, we just push IDs from the last up, to the highest placed gem in this column. Now that we know which gems to check, we should let the game know what to do with these gems.


Step 55: Handling Chain Matches

Let's go to our update() function in GameState.as. Since the process of finding chain matches can't run only once after finding a match (because after a chain match there can appear yet another one), we need to write our handling here. Let's start out by adding a simple condition to the loop. Its task will be to let us know whether there are any gems to check.

Notice that if we're handling a match we shouldn't really check for a chain, we do that after all the gems are on their new positions. Now let's check if any of the gems in our checkIDs array found a match. To do that, we'll use our check() function. Let's treat the currently checked ID as a selID.

Now we should use our check function to see whether this gem is in a match or not. To hold the returned value, we'll use stCheck variable, because it's attached to the selID. Notice that we don't want to really skip any ID; we're not dealing with swapping anymore so it's unnecessary. Let's just put -1 in the place of the skipID argument -- since there is no such ID on the board, no gem will be skipped.

A little problem arises. Our check() function takes uint as a type of argument skipID. That means we can't really use -1. We could change the value of skipID to something like rows*columns, but that won't look very pretty, so let's just edit our check() function a little bit:

The problem is solved. We just changed last argument's type from uint to int.

Let's continue with the handling of our gems. Now that stCheck holds the knowledge about a match for the currently checked gem, we should make a condition to let the game know what to do if the match was found, and what to do if it wasn't.

In the case that no match was found for this gem, let's simply pop() that gem out from the checkIDs array, so in the next game loop the next gem will be checked, of course we can't forget about setting the selID and stCheck to their initial values:

All right, we took care of that. Now what to do if the chain match was found? Then, we need to invoke similar procedures that took care of the match when the player manually swapped the gem. Since there is no swapping involved, because the gems have already been dropped, we're one step ahead. We only need to call the clear() function, as it is done after swapping, and set the clearing variable to true, which is also done after swapping. So practically the only difference is that we won't have to wait until swapTime has passed. That kind of time would make it seem as if the algorithms were very slow, which, of course, can't be the case!

That's it, now the chains should be picked up and cleared, if any will occur, that is.


Step 56: Test the Chain Matches

It's time to test all the functionality we've been implementing in the last few steps, press F5 and give the game a try:

No errors pop up and everything looks just as it's supposed to look! The game feels finally fully playable, but there are quite a few things missing. We'll be adding more and more functionality in future steps, though they won't really affect the core mechanics too much from now on.


Step 57: Adding the Animations

It's time to make our game a little more attractive. You should remember than in our gems.png file, we, aside from frames for static sprites, had additional frames for every gem. When we bring together all the frames, we'll have an animation that looks as if the gem is shining or blinking. Knowing that, we can add the animations to our Gem class, so let's switch our current document to Gem.as, and do so:

We should give all the new animations a similar form, to do that we can simply decide to add _shine to its name. The name will be then constructed from the color of the gem that's shining, and the "_shine".


Step 58: Run the Animations

All right, now that we've got our animations created, we could go back to the GameState.as and simply run the proper shine animation, if the mouse is over the gem. Unfortunately, that would require a use of switch statement, in which we would play() an animation corresponding to every type of a gem. Since this wouldn't look too pretty and certainly put us to do additional work whenever we decide to add yet another kind of a gem, let's do it more elegantly. Let's simply create a String that will hold the base of the gem's name (red, blue, yellow etc.). To play the shine animation, we'll simply need to call play(base + "_shine"), because every animation we just created ends with "_shine" string. Let's start from adding the base variable.

Now we need to edit our InitType() function, because we want it to set the base to the appropiate string, so instead of starting an animation, it needs to set the base.

Naturally, if we only set the base, then the animation won't start, that's why we need to add one line to play() the basic animation.


Step 58: Let the Gems Shine

It's a good idea to set the animations within the Gem.as, so let's create a simple function here that will play the shine animation. We will do that in a similar way to how we run the basic animation, but this time we'll add "_shine" to the base:

Let's go back to our GameState.as, and let's make any gem that's under our cursor shine. We simply need to use the function we just created.

As you can see, we calculate id only if we click on the gem. We need to change that, to calculate it when our cursor is in the board area instead. To do that, let's split the condition in which the released must be true, and the condition in which the cursor must be in the board area, then we can simply move the code that calculates the id.

Now the id is always calculated when our cursor is in the board area. Knowing that, we can simply make shine() every gem our cursor points at.

If you run the game now, you'll notice that the gems shine when you move the mouse over them.


If you play a little longer, you'll notice that something is wrong. Soon the gems blink no longer and no matter how many times you move the cursor over them, none react! We'll be taking care of that in the next step.


Step 59: Create a Callback

The reason why the gems stop reacting to our mouse cursor, is that they stop reacting to our shine() function. That's because when our animation finishes, it doesn't go back to the previous animation, so if we try to run the same animation again, it won't really do anything. We have two choices now, either to force to replay the animation, or to create a callback. The callback would set the animation to the basic, idle animation once the shining has finished. Since it's really easy to use the first method, let's jump to Gem.as, and change our shine() function:

We simply set Force parameter to true, we didn't have to fill it earlier because it's set to false by default. Let's run our game and see how it works now.


The drawback of this method is, once we keep the cursor over the gem, it will enable gems to play the animation even if they didn't finish the previous one. Of course, this is quite a minor problem, we don't really need to fix them and the player probably won't consider this a problem, but since the fix for it is quite easy let's do it. The cure for these problems is the second method we considered earlier, to create a callback. Of course, before we start, let's reset Forced argument to false.

Callback is nothing more than a function that is called by another to provide us with some useful data. In this case, it's called when we animate the sprite, and because we can't just have one callback per animation, it needs to provide us some useful parameters.

As you can see, the parameters for an animation callback are as follows:

  • aniName, which is the name of the animation that has been finished
  • curFrame, which is the current frame of the animation
  • curAniFrame, which is the global frame (an ID of the current frame in the texture, in our case - gems.png)

Knowing all that, we are able to tell our gem when and what should it do, while playing the animation. The answers are very simple, when? When the animation has finished playing. And what to do when that happens? Play the base animation! Of course to make this future proof, we also should tell to do that only if the current animation is the shining animation.

And that's it! The final step is to add our function as the animation callback.


Step 60: Test the Animations

It's time to test our animations, let's run the project and see whether it looks all right or not:

As you can see now everything works properly, but with our animation, this constant blinking when the cursor is over one gem, doesn't look very good. Let's skip the constant blinking, by allowing the gem to shine() only once, even if the mouse is still over this one particular gem. For that we need a new variable that will keep an ID that should shine() no more.

Now let's simply forbid the gem with mouseOverID to do any blinking.

Let's test it once again, and see whether this feels all right.

It doesn't feel so unnatural now, but it's always a matter of personal taste. The previous version could look better than this one with other kind of animations too, so it's a good practice to follow your gut in this kind of situations. If it looks good, it is good, they say!


Step 61: Create a Hint Box

In the next few steps we'll be creating a hint box. It will be a simple button; if the player clicks on it, it will point to a gem that can be moved to create a match. With only six types of gems it's really hard to not be able to find one, but if there were more than that, the player could have a problem in finding it. To not spoil his fun with the game, if he can't find it, he can simply use the hint box to find the match for him.

The first thing we should do is to embed the hintBox.png to GameState.as.

Now let's create the sprite:


Step 62: Set the Hint Box

If you look what's inside hintBox.png, you'll notice that there are two boxes, 64px by 64px each:


Those frames are just to let the player know that he clicked on the button. Now that we know the frameWidth and frameHeight, we can loadGraphic() for our sprite.

Now let's create an animation for each frame, so we can swap between them easly.

We should also set its position so it won't hang on the top left corner.

And finally add it to the game loop.

We're done, let's run the game and see how does it look now:


It is there, and that's fine. In the next steps we'll be adding functionality to it.


Step 63: Handle Input

Before we start searching for the gem that can be matched, let's make the hint box visually usable. Go to our update() function in GameState.as. We need to create a similar condition to the one we used while checking whether the cursor is on the board or not. This time we'll use it to know whether the cursor is pointing at the hint box.

Since we took care of that, let's take care of swapping the frames. If the player will press the mouse button, we should switch to "pushed" animation, and when he leaves the button area, we should make it "idle" again.

Nothing complicated about it. Let's test the game and see if the input is handled properly.


Everything works just as we planned. It's time to create a function that will find a gem that can be matched.


Step 64: Find a Gem

Firstly, let's create a new function. Let's call it GetHint(), it shouldn't really take any arguments, it will search through the whole board.

Now we should think about how should we find a gem that can be matched using only one move. We need to see, if the check() function will return true when one of the adjacent gems is of the same type that the gem we are checking is. If it does, we make the gem shine(), to let the player know that this is the gem.


I hope that everything is clear now, and we can go step further, to the coding.


Step 64: Find a Gem PART 2

We should start by creating the loop which will go through every gem on the board.

Now we should perform a check() on all of the adjacent gems. Of course not every gem has four neighbours, and that is why before we perform the checks we need to know where those checks should be applied. We'll simply check whether the gem we are currently iterating through doesn't have a neighbour or any of the sides. For that purpose, we need four new variables, we'll call them skipLeft, skipRight, skipUp, skipDown.

Now we need to check whether we should skip a check() for a gem to the left, right or maybe above or below our gem. It's pretty easy to do, but we need to remember that the gems to the right and to the left need to be the same row that our gem is. Let's start from checking the gem on the left.

We simply check whether the gem on the left is out of our array's bounds and whether it is in a different row than our gem. If either of these is true, we set skipLeft to true. We need to do pretty much the same thing for the gem on the right.

While checking if there isn't any gem above or below we need only to check whether these are out of our gems array's bounds.

That's it, we have set all four variables to their right values.


Step 65: Find a Gem PART 3

The only thing left to do is to check whether this gem can be matched in one move. Thankfully, our check() function is enough for that, there is no need to create some kind of special variation of it, like we did a bit earlier. We need to consider all four directions we can swap to, but before we check whether our gem can make a match after being swapped with the gem to the left (for example), we need to make sure that there is a gem on the left. Of course, we also need to check whether the check() function returns true or not.

For the check() function, we need to supply an array; our st will do, as we won't need to use it anyway. For the ID, we need to check the ID of the gem we swap with, in this case it's a gem on the left. Of course, we are checking for our gem's type. Remember that we need to skip our gem's ID. We need also to consider the gems in other directions we can swap with, the check() for them will look very similarly:

As you can see, we only needed to chaange the ID of the gem we are checking for. We're are nearly done, the only thing left to do is to make the gem blink if it passed any of those trials. After that we should break the loop, because otherwise it would make all the gems that can be swapped shine(). That would create an interesting effect, and is always an alternative to showing only the first gem that passes the conditions we just setup.

And that's it.


Step 66: Test the Hint Box

Let's go back to the place where we were handling the input concerning the hint box. When the player presses the button, let's call our freshly created function.

Now we can test our project! Let's play a little and see if everything works fine.

No bugs, no crashes, and certainly, the gems that shine() can be easly matched!


Step 67: Add a Background

It's a good time to add a background to our game to make it a little bit more lively. In the assets folder you should find fg.png and bg.png, we need to embed them to our GameState.as.

Graphics are embedded, so now we can create our sprites.

And finally, let's loadGraphic() for both new sprites and add() them to the game loop. We want fg to be rendered after the gems, so we need to add() it after we add the gems. On the contrary, we want the bg to be behind the gems, so we need to add it earlier than them.

If you test the game now, you'll see that both sprites are rendered correctly, but unfortunately, we need to make quite a few changes.


Step 68: Make Necessary Changes

The first change to consider is changing the resolution. If your SWF is different resolution than the sprites (600x450), then you either don't really want to use these, or to make adjustments to your game resolution, which shouldn't be much of a hassle. Another option is changing the position of gems and the spacing between them, and the last is adjusting the position of the hint box so that it would fit the hole to the left. After applying those changes, your constants should look like this:

After all these, the game will look much better; the black void is replaced with a background so the game looks more alive now.



Step 69: Balance the Game

Now we can take care of changing some of the game's constants, so the gameplay will feel much smoother. This part of game design is very subjective, but it isn't such a bad idea to let the intuition do her work. Firstly, let's go to the Gem.as; we need to decide what kind of speed and dieTime the gems should have. I played around with them for a bit, and the values I decided on are as follows:

Another change we can make is a little correction to movement handling.

Our change simply lets the gem move a little bit further than its destination. It will create a little shake at the end of the movement, giving the player a sense of the gem's speed. The shake doesn't really have to occur, but it's not very important either way, really.

Let's switch our document to the GameState.as. We should now adjust our swapTime, clearTime and fallTime. Those will affect our gameplay a lot, they can give our game a lot of dynamism, or make it sluggish on the other hand. After playing with these, I reached the decision that these values are satisfactory for me:

Now we can test our game.

It never ceases to amaze me how such little work can show such a big improvement. The game looks and feels much better now!


Step 70: Create a Score

Our game is playable at this point, but we need to add something to reward the player for playing. Giving points for every match is the most intuitive way, so let's implement that. In GameState.as add new variable called score. As you might guess, it will keep our score.

We should also keep the track of the chain matches count if we want to give additional multipliers for these. We'll need an additional variable for that.


Step 71: Calculate the Score

Before we go ahead and start adding anything to the score, let's create a simple function that we'll call after the match has been formed.

There are two parameters here: the first I called gemCount, as the function needs to know how many gems there were in a match. The mult is just a multiplier so we can adjust the score to our needs. Chain matches will be contained in chains variable, so there's no need to submit them as an argument. We need to add some number of points based on those two variables we've got. Since in this kind of game nearly everything is random, it would seem as if the score's formula isn't too important, but we need to reward player more for rare events, such as matching five gems or getting a chain of five matches in a row, and reward him less for a minimalistic match of three. Having this all in mind, we can shape out our formula!

It isn't too complicated. For a match of three gems we give 300 points, for a match of four 1600 and for a match of five (which happens not so often) we give 12500 points. The biggest number of gems that can be matched is seven, and that would give the player a whopping 1680700 points. We also multiply the score by the number of chains squared, so that can give a really big boost to the score too, bearing in mind how often they happen.


Step 72: Add the Score

It's time to add the score when the player creates a match, but before that we need to increment the chains when a chain match appears. The right place to do that is right before we call spawn() function in our update().

We also need to reset the chains, because otherwise the chains from the previous move would accumulate with the new ones. A good place for that is right before we call clear() in our update().

Now let's go to our spawn() function, and let's use addScore there. We will need to give the player additional points for creating two matches in one move, though creating two matches with three gems in each should generate less points than the glorious six gems in one match.

I decided that the multiplier in this case would be 3. Remember that all formulas can be changed later without much work, everything needs to be tested before we decide to keep the current values.

We're done with this, now we need to show the score to the player.


Step 73: Show the Score

Firstly, let's create an FlxText. FlxText is just an object that lets us render the text on the screen using any font we want, as long as we provide it of course. In our case we'll be using the default font that's comes already in flixel.

Now let's add it to the game loop.

And finally, let's give our scoreText the score render. Our score is an integer so we need to convert it to the string first, which is done automatically for us if we use String(score). The best place to do that is after we addScore(), since we do that in the spawn() function, we can simply update our scoreText right after it.

We're done. Since at the beginning the scoreText doesn't have any text to display, it doesn't render anything. The score shows up after we actually create a match. We can test our game now.


Step 74: Get Rid of the Lag

As you may have noticed, sometimes when there are quite a few chain matches it seems as if the chain has ended, but suddenly another one is found. The source of that lies in our constant fallTime. Because chain matches need to wait until all the gems have already fallen to the ground, if the fallTime is too big, the game will appear idle. In fact it will be idle, and we do not want that. Our constant is, well, constant, and because of that the game needs to wait the same amount of time if the gems fall through only one row or if the gems fall through a whole five rows. That's why we need to change this const to a var, and add a new constant that will keep track of how long we should wait for the gems to fall through a single row.

Now we need to calculate how many rows the gems fall. We need to go back to the spawn() function again, because that's the place where we can do this particular task. Again, we have three cases here, we'll start from the first and the hardest one, the case in which both swapped gems have found a match. The first thing we need to do here, is to find the gem with the highest and the lowest position. For that we'll need two new variables; let's create them after the for loop is finished.


Step 75: Get Rid of the Lag PART 2

Now we need to give our topMost and bottomMost appropiate values. We do that pretty much the same way we calculated leftMost and rightMost variables, but this time instead of columnID, columnID2 we'll be using rowID, rowID2, and instead of clearLeft, clearRight we'll be using clearUp, clearDown. We can copy and paste the code that's already written, and use find and replace command to swap the variable names.

Now that we have our topMost and bottomMost row IDs (that's what we just calculated, after all!), we can easly calculate how many rows the gems will fall(). We need to remember that the lower the row, the higher ID it has, so instead of topMost - bottomMost it will be the opposite (bottomMost - topMost). We also need to remember that we count IDs from 0 so we need to add up one row.

Of course to calculate the time we also need to multiply the number of rows by our fallConst. We took care of the first case, the two others will be much easier to deal with since we have all the data we need right away. Let's switch to the second case, only the first of the two swapped gems has found a match. In this case, the number of the gems matched on vertical axis will be equal to clearUp + clearDown + 1. We need to remember about the swapped gem because it's not counted into clearUp or clearDown.

Analogically we calculate the fallTime for the last case, where only the second of the swapped gems has found a match. This time we need to use variables associated with the second gem.

And we're done.


Step 76: Add a Title Screen

To make our game look more refined, we need to add a title screen, so the player won't be thrown in the gameplay right away. For this we'll use a separate state, so we need to create a new class. Let's call it Title. Like our previous state (GameState), it needs to inherit from FlxState.

The code in Title.as should look like that now. It wouldn't hurt to add the update() function right away too.


Step 77: Load Up Resources

Now let's add our sprites which we will use to build our title screen. We'll need to embed title.png, text.png and title_bg.png.

After that we can create FlxSprites with those embedded images.


Step 78: Render the Images

Now let's render our sprites on the screen. Go to our constructor and loadGraphic() for all of our sprites and set their positions. Don't worry, I've already found these for you. We will want board to drop down at the beginning, so we need to place it above the screen. The text is supposed to be rendered on the board, it would probably look better if the scene wasn't too static so we'll make it blink. For now let's simply set its visibility to false.

Now, to test our game we need to tell that we want to start from Title state. We need to go back to Main.as to change that.

Change GameState to Title. If you do that, then when the game will be started the Title state will be run. Now press F5 and test whether it really is so.


As you can see, only the background is visible. That's because the position of the board is above the screen and the text is set to be invisible.


Step 79: Drop the Board

After dropping the board and after we received the input from player, we also want to pull it up. The first thing we need to do is to create a Boolean that will indicate whether the board should be falling down or not.

Let's also add a speed and an acceleration, so we can play with the whole process. Since the acceleration will be changing the speed, we can't really make it a constant.

Back at our update() function, let's start coding what the game should do when fall is set to true.

We should handle the falling the similar way we handled the gems' movement, the only difference is that this time we have only one direction to take care of, the vertical one, and we'll be accelerating our board.

Now we can handle the pulling. It's very similarly done, though pulling the board with acceleration probably isn't the best idea, so we'll skip that point.

That's it for now, let's see the results.



Step 80: Show the Text

Now we should take care of the text. Since we decided that it should blink, we need to setup the timer and create a blinkTime constant.

We can start making the text blink now, but before that, let's set up its position. We need to place it in the center of the board (which is not the center of the sprite).

This will keep our text where it's supposed to be. Now for the blinking, we simply need to change the state of visibility everytime the timer reaches blinkTime.


It works well, we can go ahead and finish up the whole thing.


Step 81: Handle the Input

Firstly, let's think about what to do in the case that the player complied to the board's message. We decided to pull the board up, so we need to set fall to false. Other than that, we should switch the state to GameState, but we should wait a little bit with this, because we need some kind of transition between the Title and GameState states. For transition, the built-in flash() and fade() funtions will be very useful, what they do doesn't really need any explanations, since with their names that is self-explanatory.

Note that the boardSpeed is changed, because all that acceleration made it too high. We used fade() to fade to white color, and the full fade should take 1.5 seconds. After that time we should switch states, so we need to create another timer, that will count those one and half of a second.

We should count while the board is pulled up.

Changing the states in flixel is very simple, we only need to replace FlxG.state property with our GameState object. There's one more thing we need to do here, because we don't want to go from a completely white screen to our game screen, we should now use the flash() function, to make the transition smooth.

flash() is white by default so we don't need to change any parameters.


Step 82: Finish Up the Title Screen

We should get rid of our 1.5 seconds constant and name it properly, so it will be easier to edit it if there's such a need.

Now we should replace 1.5 in our code with this constant:

...becomes:

Also:

...becomes:

Let's test the project and see how the title screen looks.


Step 83: Add the Sounds

It's time to add the sounds effects to the game. I've gathered all the sound files into one SWF, all that's left is to embed them in gamestate.as.

Now that we took care of that, we can start can start playing those sounds when we need them. Firstly let's take care of the SfxHint.

As you can see, to play the sound we simply call FlxG.play() and as an argument we give a reference to our sound. Secondly, let's take care of SfxSwap.

The next one in the queue is SfxCombo.

And finally, the last one, SfxOver.

That's all. We can play the game and see if the sound effects are playing correctly.


Step 84: Final Touches

You could wonder why we used Arrays instead of Vectors. It was done so, because the optimisation at that point wasn't really our primary goal, and it's hard to predict what kind of idea you would get while developing. It's a good idea to leave your options open (well, not always), but now since we really don't use any other type than Gem in our gems array, we can replace it with a Vector, which will require us to change only one line of code in GameState.as.

We've got yet another array, checks. Since we push() only uints into it, let's replace it with a Vector too.

Now for the last two, our st and nd arrays. We also push() only unsigned integers into them. We also need to modify check() function to take a Vector. instead of an Array.

And that's it, we have no more arrays, but there's one last thing to do. When we look for chain matches and we don't find any attached to the currently checked gem, we cease to look for it any longer and delay the search by one frame, resulting in game being idle, because nothing is done until the checks vector is empty. Let's make it so it doesn't stop looking for matches until either it finds one or the vector is empty.

That's it. We used the while loop because we want to continue doing the actions inside of it until we found a gem (stCheck would be equal to true then) or we have no more gems to check (checks vector would need to be empty).

Altough checking too many gems in a single frame would not be a good idea, while testing we can see whether it can make the latency between frames too big. On my two-year-old laptop it didn't, so assuming it is our target machine, then everything is all right! Though the changes now will probably be not so easly noticable, here is our final view on the project:


Conclusion

That's it, the tutorial is finished! You can still play a lot with the source since not all needed features are implemented, nevertheless, we covered a lot here. If you compare the current state of the game to that in which the game was after the first part, the change is overwhelming. The game looks much better and the gameplay is a lot smoother and more enjoyable. I hope you had fun during the creation of this game, and although it's unlikely that anyone can make billions of dollars by building this kind of game any more, the experience achieved by creating it is irreplaceable!

Thanks for your time sacrificed to read this tutorial, I hope you consider it well spent. :)

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.