In the previous tutorial, we integrated a basic match-detection system into our Match-3 game. While we are well on our way to having a playable game, there are still a few important game elements we need before we can really call what we have a "game". This article is going to focus on filling in some of those missing details, and getting us much closer to our final product.
Final Game Demo
Here is a demo of the game we're working towards throughout this series:
1. Awarding Points
We are going to cover points before we start improving the matching system, because it will be much easier to see the first issue in our current matching system if we have a points system implemented.
The points system in our game is going to be very simple, for every Block used to form a given group, the player will receive 10 points. In a later tutorial we will also add in a system that allows the player to gain more points by chaining together multiple groups, but for now we will focus on just introducing a simple points system for the player.
Before we start editing the events, we need to add in a Points display, so first go to Layout 1 and do the following:
- Insert a new Sprite object.
- Open the image Game Field Images/ScoreArea.png from the graphics package.
- Close the animation editor.
- Set the position to
- Insert a new Text object.
- Set the Font to
Calibri, Bold, 22using the drop-down.
- Set the Name to
- Set the Color to
255, 255, 255
- Set the Position to
- Set the Size to
- Set the Text to
- Set the Font to
Layout 1 should now look like this:
Now that we have something to tell the player what the points text means, and a text object to display the player's score with, we can move on to actually giving the player points. Go to Event Sheet 1 and create a new Global Variable.
Global Variable Name: "Score" Type = Number Value = 0
The variable should look like this:
This is the variable we will modify whenever we give the player points. Next, we will create a new function which, when called, will detect how many Blocks the player has matched into groups, and give them the appropriate number of points.
Event: Condition: Function> On Function Name = "GivePoints" Condition: System>For Each Object = Block Condition: Block>Is boolean instance variable set Instance Variable = IsMatched Action: System> Add to Variable = Score Value = 10 Action: ScoreText>Set text Text = Score
Your code should look like this:
So, to reiterate, this event looks at every single Block. Every time it finds a Block which has
IsMatched set to
true - meaning it's been confirmed to be part of a group - it gives the player 10 points for that Block, and updates the score text.
If you test your game at this point, it will seem like the function isn't working. The reason for this is because we haven't actually called the function anywhere in the code, so the points are never being incremented, and the text is never being updated. Go to your
FindMatches function and add a new Action to the beginning of the final sub-event for this function.
Action: Function>Call function Name = "GivePoints"
FindMatches function should now look like this:
Note: Make sure that you have added this new Action at the beginning of the Sub-Event. If you add this Action to the end, it will not work since all of the matched Blocks will have been destroyed before the
GivePoints function is called. This means that, when it searches for the matched Blocks, it will not find any and so the player won't receive any points.
At this point you can test your game again and you should see the points text updating, and that the player is receiving the correct number of points for each match they make.
2. Improving the Match Detection
Now that we have the points system in, I want you to run the game, and create the scenario shown below.
Now swap the Blocks I've highlighted here, and watch your score to see how many points you gain.
When you formed this match, you should have seen that you gained 50 points. This is because, currently, the points system gives the player 10 points for each Block that is marked as
IsMatched, as opposed to giving the player 10 points for each Block used in each match, like the system I described above.
If the point system worked correctly, it would give the player 60 points: 30 for the vertical group of three Blocks, and 30 for the horizontal group of three Blocks. This problem stems from the fact that the match system doesn't have any way of marking when a Block is matched both horizontally and vertically; it only knows if the Block is matched at all.
To solve this problem we are first going to add two new Instance Variables to our Block object,
Instance Variable: Name = MatchedX Type = Boolean Initial Value = false Instance Variable: Name = MatchedY Type = Boolean Initial Value = false
Your variables should look like this:
These variables are going to be used in conjunction with
IsMatched to tell the system when the Block is part of horizontal, or X, groups, and when the Block is part of vertical, or Y, groups. Now that we have the variables, we are going to modify the
CheckMatches function so that when it labels a Block
IsMatched because it found a large enough group, it will also label that Block as being part of an X or Y group depending on whether Parameter 3 or Parameter 4 is 1.
Go to the
CheckMatches function and replace the original
NumMatchesFound check with these two new sub-events:
Sub-Event: Condition: System>Compare two values First value = Function.Param(3) Comparison = Equal to Second value = 1 Condition: System>Compare variable Variable = NumMatchesFound Comparison = Greater or equal Value = 3 Action: Block>Set Boolean Instance variable = IsMatched Value = True Action: Block>Set Boolean Instance variable = MatchedX Value = True Sub-Event: Condition: System>Compare two values First value = Function.Param(4) Comparison = Equal to Second value = 1 Condition: System>Compare variable Variable = NumMatchesFound Comparison = Greater or equal Value = 3 Action: Block>Set Boolean Instance variable = IsMatched Value = True Action: Block>Set Boolean Instance variable = MatchedY Value = True
CheckMatches function should now look like this:
So the new version of
CheckMatches functions in the same way as the previous one, except it now also checks to see whether the Block was found to be a match in a vertical group or a horizontal group, and labels the Block accordingly with the new variables
Awarding Extra Points to Blocks That Match Twice
Now that we have a way to determine when a Block is matched vertically, matched horizontally, and matched in both directions, as opposed to just knowing it has been matched in a group, we need to add a sub-event to the
GivePoints function which will distribute an extra 10 points for a Block that has both
MatchedY set to true.
Go to the
GivePoints function and add this sub-event:
Sub-Event: Condition: Block>Is Boolean instance variable set Instance variable = MatchedX Condition: Block>Is Boolean instance variable set Instance variable = MatchedY Action: System>Add to Variable = Score Value = 10 Action: Text>Set text Value = Score
GivePoints function should now look like this:
If you run your game and again create the scenario I illustrated above, your score should now correctly increase by 60 points.
3. Adding Gravity
Now that we have a Points system implemented, and we have updated the matching system, we are going to start improving another important aspect of the gameplay. If you've spent any time playing with the game up to this point, you'll know that one of the biggest issues is that when Blocks are destroyed, nothing happens to the Blocks above them. Specifically, the Blocks above empty spaces don't fall to fill in those spaces.
This is fine in the tests, but in the final version it would be detrimental to the gameplay to leave things as they are, so the next thing we're going to add is "gravity" which will cause the Blocks to fall and fill in empty spaces when other Blocks are destroyed.
The way we will implement this system is actually quite simple. We will perform a check using the
Block > Is overlapping at offset event to see if there is a Block below the Block we are looking at. If we find there is no Block, we will move the Block we are looking at down to fill in the empty space; otherwise, we will do nothing.
To make this work we will create a new Event:
Event: Condition: INVERT>Block>Is overlapping at offset Object = Block Offset X = 0 Offset Y = 8 Action: Block>Move at angle Angle = 90 Distance = (Block.Width + 2)/2
Your code should look like this:
If you run the game at this time, you will see that the moment the game begins, all of the Blocks fall off the screen! The reason for this is because we didn't put anything into the code to tell it where the "floor" of the game field would be.
So essentially, the Blocks on the bottom row realize there are no Blocks below them and fall accordingly. Then, once the lowest row of Blocks has fallen, the next lowest row sees there are now no Blocks below them, and they too fall. This process continues, until all of the Blocks have fallen, and leaves the screen completely empty.
You can see a slightly slowed down version of this in action in the GIF below:
To fix this, we will add a second condition to the Event.
Event: Condition: Block>Compare Y Comparison = Less or equal Y = SPAWNY - 1
Your code should now look like this:
By adding this new condition we ensure that only Blocks that are above the Y position of the lowest row are affected by our "gravity". Despite this fix, we still have a few problems.
Dealing With Dragging
The primary problem is that the event which looks to see whether there is an empty space below a Block does not have anything to tell it to be inactive when the player is dragging a Block. This means that, if you drag a Block too far without letting go of it, the Blocks in the position above where you dragged it from will fall into the space left by the Block you dragged. On top of that, the Block you are dragging will also have an issue if you bring it out of the game field, and it will start to fall away from the mouse cursor since there are no Blocks below it.
To fix this problem we need to add a new global variable to tell the system when we are moving a Block, a new action to the Block dragging and dropping events to set this global variable, and a third condition to the gravity event so it takes this variable into account before activating.
First, let's make the global variable:
Global Variable: Name = BlockBeingMoved Type = Number Initial Value = 0
Your variable should look like this:
Now, go to the
On DragDrop drag start event and add a new Action:
Action: System>Set Value Variable = BlockBeingMoved Value = 1
Also, go to the
On DragDrop drop event and add a new Action to the primary event:
Action: System>Set Value Variable = BlockBeingMoved Value = 0
With the lines added, your DragDrop events should now look like this:
Finally, go to the gravity Event and add a new condition:
Condition: System>Compare Variable Variable = BlockBeingMoved Comparison = Equal to Value = 0
Your gravity code should now look like this:
The new variable that we created,
BlockBeingMoved, is used to tell the system when a Block is being moved by the Player. If the variable equals
0 it means that no Block is being moved and it can run the gravity scripts as normal. If the variable equals
1, it means a Block is being moved, and the gravity scripts should not be run.
If you run the game at this point, you will see that no matter where you move the Block while you are dragging it, no issues occur.
Checking For New Matches
Now we just have one last issue to deal with regarding the gravity system. Run the game and create a scenario similar to this:
Now, make the swap that I have highlighted in this next image.
You should notice that when the group of Green/Star Blocks is destroyed, an Orange/Hexagon Block falls and forms a group of three Blocks, but doesn't get destroyed.
The reason these Blocks don't get destroyed is because we never called the
FindMatches function a second time to see if any new matches were formed when the Blocks fell to fill in the empty spaces. To fix this, go to the Event which checks for empty spaces below Blocks and add this Else Event:
Event: Condition: System>Else Action: Function>Call function Name= "FindMatches"
Your code should look like this:
This else statement means that, whenever it finds there are no empty spaces, it will perform a check to see if there are any groups to destroy. This event will automatically run whenever Blocks fall into new positions since it is activated by an Else statement which is linked to that check, and will only fire once it is sure all the Blocks have fallen into place.
If you run the game at this point you will find that you can now create chains of Blocks by destroying Blocks in a way that groups will be formed when the remaining Blocks fall. On top of that, you will also find that when you first start the game, any groups that are spawned initially will be destroyed as well. As I said in the previous tutorial, we will eventually eliminate pre-made matches, so this issue will not matter in the end.
Removing Blocks From the Initial Layout
Finally, we have to do one other thing before we can consider our gravity system complete. Depending on where you placed the initial Block sprite when you completed the first tutorial, you may notice that when you start the game it falls and becomes visible.
If you don't know what I mean, go to Layout 1, set the position of your Block sprite to
521, -32, and run the game. When you play the game, you should see the original Block land in the position I've highlighted in the image below:
As you can see in the image above, the initial Block falls from its position off-screen and becomes visible. We don't want this because it is only going to cause us issues later on. To solve this small problem we are going to add an Action to the
On start of layout event that will destroy any Blocks that are in the Layout when it initially loads.
Your event should now look like this:
Now when you run the game, you should no longer see the Block. You may be asking yourself why we didn't just delete the block from the Layout so we don't have to worry about this problem at all. The reason we didn't do this is because Construct 2 cannot create copies of any object type with Events, unless there is already an instance of that Object type in the game when it first loads. By deleting it within an event, we remove it so it doesn't become an issue later, and we make it possible to spawn as many Blocks as we need through code.
We covered a lot of topics in this tutorial, and while there is more we could do, I think it is important to take a break and let this info sink in. In the next installment, we will fix a couple of small issues, make the fancy floating points text that you might have noticed is in the final demo, and set up the chaining system.
I hope you got a lot out of this part of the series, and I will see you back here next week.