Quantcast
Channel: Envato Tuts+ Game Development
Viewing all articles
Browse latest Browse all 728

Make a Match-3 Puzzle Game in Construct 2: Points, Improved Matching, and Gravity

$
0
0
This entry is part 4 of 4 in the series Make a Match-3 Game in Construct 2

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:

  1. Insert a new Sprite object.
    1. Open the image Game Field Images/ScoreArea.png from the graphics package.
    2. Close the animation editor.
    3. Set the position to 491, 383.
  2. Insert a new Text object.
    1. Set the Font to Calibri, Bold, 22 using the drop-down.
    2. Set the Name to ScoreText
    3. Set the Color to White or 255, 255, 255
    4. Set the Position to 419, 398.
    5. Set the Size to 200, 50.
    6. Set the Text to 0.

Layout 1 should now look like this:

Match3_Part4_ScoreLayout

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:

Match3_Part4_ScoreVariable

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:

Match3_Part4_GivePoints

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"

Your FindMatches function should now look like this:

Match3_Part4_FindMatchesPoints

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.

Match3_Part4_PreLSwap

Now swap the Blocks I’ve highlighted here, and watch your score to see how many points you gain.

Match3_Part4_LSwap

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, MatchedX and MatchedY.

Instance Variable:
            Name = MatchedX
            Type = Boolean
            Initial Value = false
Instance Variable:
            Name = MatchedY
            Type = Boolean
            Initial Value = false

Your variables should look like this:

Match3_Part4_MatchedXY

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

Your CheckMatches function should now look like this:

Match3_Part4_ModifiedCheckMatches

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 MatchedX and MatchedY.

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 MatchedX and 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

Your GivePoints function should now look like this:

Match3_Part4_ModifiedGivePoints

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:

Match3_Part4_Gravity

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:

Match3_Part4_BlockGravityIssue

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:

Match3_Part4_ModifiedGravity

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:

Match3_Part4_BlockBeingMovedVariable

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:

Match3_Part4_ModifiedDragDrop

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:

Match3_Part4_ModifiedGravity2

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:

Match3_Part4_PreChainingSwap

Now, make the swap that I have highlighted in this next image.

Match3_Part4_ChainingSwap

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:

Match3_Part4_GravityElse

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:

Match3_Part4_InitialBlockIssue

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.

Action: Block>Destroy

Your event should now look like this:

Match3_Part4_DestroyAction

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.


Conclusion

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.


Viewing all articles
Browse latest Browse all 728

Trending Articles