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

Make a Match-3 Puzzle Game in Construct 2: Match Detection

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

So far, this series has covered the basics of setting up a Match-3 game, and implementing the initial gameplay elements such as block-swapping. In this tutorial, we are going to build on all of that, and start detecting when the player has made a match.


Final Game Demo

Here is a demo of the game we’re working towards throughout this series:




1. Detecting a Match

For now, we are only going to implement a basic version of the matching system, focusing on finding when matches exist and destroying matched blocks. In later articles we will continue developing and advancing the system.

Tip: You should read into how a recursive function works, if you don’t already know; essentially, it is a function which calls itself. Recursive functions can work similarly to loops, but since they can also take in and return variables, they have many more uses than loops do.

As with the previous tutorial, I first want to discuss how the system will work, and then attempt to build it.

  • The match system will iterate through each instance of the Block object.
  • For each Block, it will pass the color and position of the block it’s looking at into a recursive function, which will look at the horizontal or vertical neighbor, and determine whether they are the same color.
  • If a match is found, it will call the function again with the position and color of the new block, rather than the original one.
  • This will continue until it finds no match. At that point, it will check how many matches it found.
  • If it found three or more matches, it marks all the blocks it just looked at as Matched with the IsMatched instance variable we made in one of the previous tutorials; otherwise, it does nothing.
  • Finally, once all of the blocks have been checked, the function will destroy every block that’s marked as a match.

First, we need an event which can iterate through each Block. The way I built the system, it actually iterates through the blocks twice: once to check for vertical matches, and once to check for horizontal matches. Depending on which check it is doing, it will use a different function to actually look for the match.

The very first thing we need to do is make a Global Variable to keep track of how many matching blocks we’ve found in any given iteration:

Global Variable
		Name: "NumMatchesFound"
		Type = Number
		Value = 0

Now, let’s make the Event that will iterate through the blocks:

Event: Function > On function
		Name: "FindMatches"
		Sub-Event: System > For Each
			Object: Block
			Action: System > Set value
				NumMatchesFound = 1
			Action: Function > Call function
				Name: "CheckMatchesX"
				Parameter 0: Block.X
				Parameter 1: Block.Y
				Parameter 2: Block.Color
			Sub-Event: System > Compare Variable
				NumMatchesFound  >= 3
				Action: Block > Set Boolean
					IsMatched = True
		Sub-Event: System > For Each
			Object: Block
			Action: System > Set value
				NumMatchesFound = 1
			Action: Function > Call function
				Name: "CheckMatchesY"
				Parameter 0: Block.X
				Parameter 1: Block.Y
				Parameter 2: Block.Color
			Sub-Event: System > Compare Variable
				NumMatchesFound  >= 3
				Action: Block > Set Boolean
					IsMatched = True
		Sub-Event: Block > Is Boolean instance variable set
			System > Wait
				Second = 0.1
			Block > Destroy

Your code should look like this:

Match3_Part3_FindMatches

In this event, we iterate through every block and sending them into CheckMatchesX or CheckMatchesY, the functions which will check to see if the neighboring Block is a match.

To send the block into the function, we pass the functions three different parameters:

  • Parameter 0 is the Block’s X position
  • Parameter 1 is the Block’s Y position
  • Parameter 2 is the color.

After each Block is sent into one of the functions and the function finishes running, it checks NumMatchesFound to see if it found three or more matching Blocks, and then labels the Blocks as Matched if it did.

Finally, every Block that is marked as being Matched gets destroyed after .1 seconds passes. This wait statement is there to allow the game to switch the images for the Blocks to the image that indicates they are matched, and to give the player a moment to notice this change.

(While you could remove the wait statement without negatively impacting the gameplay, it makes the matching easier for the player to understand, and slows down the game just enough so that the player can easily keep track of what is going on.)


2. The Two Check Functions

Next we need to make the CheckMatchesX and CheckMatchesY functions. These functions will work similarly to the iterators above, in that there will be one version for checking horizontal matches, CheckMatchesX, and one for vertical matches, CheckMatchesY.

Horizontal Checks

First, let’s build the horizontal check function:

Event: Function > On function
		Name: "CheckMatchesX"
		Sub-Event: 
			Condition: Block > Compare X
				X = Function.Param(0) + (Block.Width+2)
			Condition: Block > Compare Y
				Y = Function.Param(1)
			Condition: Block > Compare instance variable
				Color = Function.Param(2)
			Action: System > Add to
				Variable = NumBlocks
				Value = 1
			Action: Function > Call function
				Name: "CheckMatchesX"
				Parameter 0: Function.Param(0) + (Block.Width+2)
				Parameter 1: Function.Param(1)
				Parameter 2: Function.Param(2)
			Sub-Event: System > Compare Variable
				NumMatchesFound  >= 3
				Action: Block > Set Boolean
					IsMatched = True

Your code should look like this:

Match3_Part3_CheckMatchesX

So, what is this function doing?

  • First, it tests to see whether a neighboring block even exists to the left of the block we passed in.
  • Once the function confirms there is a Block in the neighboring location, it checks whether it is the same color as the Block we passed in.
  • If it is, it increases NumMatchesFound by one, and passes the newly found Block into the function just like it did for the original.
  • This continues until it finds a Block which isn’t the same color as the original. At that point it checks to see if it found enough matching blocks to create a group, and labels the blocks as matches if it did.

Vertical Checks

Now, let’s make another version of this function which will do the same thing for vertical matches. This is going to be our CheckMatchesY function. You can either copy the original function and make all the appropriate changes, or just build it again from scratch; in either case, here is how your function should look when it is finished:

Event: Function > On function
		Name: "CheckMatchesY"
		Sub-Event: 
			Condition: Block > Compare X
				X = Function.Param(0)
			Condition: Block > Compare Y
				Y = Function.Param(1) + (Block.Width+2)
			Condition: Block > Compare instance variable
				Color = Function.Param(2)
			Action: System > Add to
				Variable = NumBlocks
				Value = 1
			Action: Function > Call function
				Name: "CheckMatchesY"
				Parameter 0: Function.Param(0)
				Parameter 1: Function.Param(1) + (Block.Width+2)
				Parameter 2: Function.Param(2)
			Sub-Event: System > Compare Variable
				NumMatchesFound  >= 3
				Action: Block > Set Boolean
					IsMatched = True

Your code should look like this:

Match3_Part3_CheckMatchesY

3. Actually Looking for Checks

Finally, we need to actually call the FindMatches function. Go to the SwapBlocks function and add a new sub-event to the end of the function:

Event: Function > Sub-Event:
		Action: Function > Call function
			Name: "FindMatches"

You’ll notice that this sub-event doesn’t actually have any conditions. If you’ve never made a sub-event like this before, just make a sub-event with any condition at all, since it requires you to give a condition when making a sub-event, and then delete the condition, but leave the sub-event. This way, you make sure the sub-event always run.

Your SwapBlocks event should now look like this:

Match3_Part3_SwapBlocks

If you run the game at this point, you will see that the blocks get destroyed when matches occur. You’ll also notice though that any matches that are there when the game begins don’t disappear until you make a swap of some kind. This is because we never call the FindMatches function after we create the grid of blocks.

The reason we haven’t added this code is because in the final version there will be another function which prevents matches from being auto-generated like this, so there is really no reason to worry about this problem at all. (But feel free to call the FindMatches function earlier, if you like.)


4. Consolidating the Checks

At this point, we have a pretty strong matching system, but the problem is that our code is redundant. Currently, we have two different functions that check to see if there is a matching neighbor, and the only difference between them is that one checks vertically, and the other checks horizontally.

Since the free version of Construct 2 limits how many Events we can have, this is definitely a waste. To solve this, we are going to make a new version of the function that can do both checks.

If you look at the function, you will see the only difference between the two versions is that one adds Block.Width + 2 to the x-position of the Block, and the other adds it to the y-position of the Bock. So, the obstacle we have to get past to make this a single function, is giving the function a way to add Block.Width + 2 to only X, or only Y, without using an If statement or multiple functions, since those require more Events to be executed.

My solution to this is not very complex, but it will be easier to understand if we can see it come together, so we will implement it, and I will explain how it works once we can see it all in action.

  1. Delete the CheckMatchesY event.
  2. Rename the CheckMatchesX event to, simply, CheckMatches.
  3. In the function call for CheckMatchesX under the FindMatches event:
    1. Modify the function call to be for CheckMatches instead of CheckMatchesX.
    2. Add Parameter 3.
      1. Value = 1.
    3. Add Parameter 4.
      1. Value = 0.
  4. In the function call for CheckMatchesY under the FindMatches event:
    1. Modify the function call to be for CheckMatches instead of CheckMatchesY.
    2. Add Parameter 3.
      1. Value = 0.
    3. Add Parameter 4.
      1. Value = 1.

As I will explain soon, these added parameters will tell CheckMatches whether it is doing a horizontal check or a vertical check. When we send in 1 for Parameter 3, and 0 for Parameter 4, it is a horizontal check, and when we send in 0 for Parameter 3, and 1 for Parameter 4, it is a vertical check.

Now, go back to the CheckMatches function, and modify the conditions and actions to look like this:

Event: Function > On function
		Name: "CheckMatches"
		Sub-Event: 
			Condition: Block > Compare X
				X = Function.Param(0) + ((Block.Width+2)*Function.Param(3))
			Condition: Block > Compare Y
				Y = Function.Param(1) + ((Block.Width+2)*Function.Param(4))
			Condition: Block > Compare instance variable
				Color = Function.Param(2)
			Action: Block > Set Boolean
				IsMatched = True
			Action: Function > Call function
				Name: "CheckMatches"
				Parameter 0: Function.Param(0) + ((Block.Width+2)*Function.Param(3))
				Parameter 1: Function.Param(1) + ((Block.Width+2)*Function.Param(4))
				Parameter 2: Function.Param(2)
				Parameter 3: Function.Param(3)
				Parameter 4: Function.Param(4)
			Sub-Event: System > Compare Variable
				NumMatchesFound  >= 3
				Action: Block > Set Boolean
					IsMatched = True

This is what your FindMatches and CheckMatches code should now look like:

Match3_Part3_ModifiedCheckMatches

How does this work?

So, what is this new version of the function actually doing?

Well, whenever you call CheckMatches you are now sending two more parameters, and rather than adding Block.Width + 2 to either the x- or the y-position, it is adding (Block.Width + 2) * Function.Param(3) to the x-position, and (Block.Width + 2) * Function.Param(4) to the y-position.

Since one of those two parameters will always be 1, and the other will always be 0, this means that either the x- or the y-position will be modified – never both!

For instance, if we pass in 1 for Parameter 3, and 0 for Parameter 4, then it adds (Block.Width + 2) * 1, which is simply Block.Width + 2, to the x-position, and (Block.Width + 2) * 0, which is 0, to the y-position.

Here is a quick example to show what I mean and how it calculates the position of the block where it will check for the match. Let’s say that in this example the original Block is at (200, 200), and the Blocks have a width of 40. So, if we want to get the position of the neighboring vertical Block, the formulas would work out like this:

  • X = 200 + ((Block.Width + 2)*0) = 200 + (40 + 2)*0 = 200 + 0 = 200
  • Y = 200 + ((Block.Width + 2)*1) = 200 + (40 + 2)*1 = 200 + 42 = 242

If we wanted to get the position of the neighboring horizontal Block, the formulas would work out like this:

  • X = 200 + ((Block.Width + 2)*1) = 200 + (40 + 2)*1 = 200 + 42 = 242
  • Y = 200 + ((Block.Width + 2)*0) = 200 + (40 + 2)*0 = 200 + 0 = 200

If you run the game now, you should see the match system still works the way it originally did, but from our perspective, it is actually a better system.


Conclusion

At this point our match detection function is still incomplete, but we’ve done a lot in this tutorial already and I think it’s important to let all of this sink in before we add anything else. With that in mind, I am going to end this article here. Check out the demo in its current form.

In the next article we will be adding a points system,we will improve the matching system, and we will add “gravity” so that the Blocks will fall when Blocks below them are eliminated.

If you want to get a head start on the next article, take some time to consider how you would detect when there is an empty space below a Block. Try looking at the Block > Is Overlapping at Offset function for inspiration!


Viewing all articles
Browse latest Browse all 728

Trending Articles