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

Make a Match-3 Puzzle Game in Construct 2: No Pre-Made Matches

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

In the previous tutorial we finally got our game moving and added motion to our blocks. On top of that, we created a rudimentary difficulty system to make the game harder as the player plays longer.

With both of these features in the game, we are ready to implement the system that will eliminate pre-made matches from the board. Although this isn’t the last article in the series, this is the last major system we need to implement – so get comfortable, because we have our work cut out for us.


Final Game Demo

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




Quick Fixes

Before we get started with the main part of this tutorial, I want to take a minute to fix two issues I discovered while I was writing the previous tutorial.

Matching

The first issue I’m referring to comes up in the scenario you can see below:

IssueV1

In this situation, if you drag Block B onto the green dot, it should fall because of the empty spaces below the dot, and eventually land in the spot labeled C. In most scenarios, this will happen and the game will function normally, but in some scenarios the game will instead detect a match in the brief moment where Block B is next to group A and it will end up destroying all three blocks.

This issue isn’t exclusive to that scenario above and can also present itself when you do the same thing in the scenario I’ve highlighted below.

IssueV2

If we don’t fix this, the player will be receiving credit and points for a number of matches they never intended to make and could also get confused about why so many blocks are disappearing unexpectedly.

Thankfully, though, this is a simple issue to fix. To solve this issue we are going to create a new Global Variable called MatchesPossible which will dictate whether matches can be made, and a new Event which will detect when a block is “falling” and will modify MatchesPossible to make it so that no matches can be made while this is happening.

First we will create the Global Variable:

Global Variable: MatchesPossible
     Type = Number
     Initial Value = 1

Your new variable should look like this:

Match3_Part7_MatchesPossible

Now we will make the Event that will listen for when a block is falling:

Event:
     Condition: Invert: Block>Is overlapping at offset
          Object = Block
          Offset X = 0
          Offset Y = 8
     Condition: Block>Compare Y
          Comparison = Less or equal
          Y co-ordinate = SPAWNY
     Action: System>Set value
          Variable = MatchesPossible
          Value = 1

This Event makes it so that when a block is found to have an empty space below it MatchesPossible is set to 1, meaning matches are not possible. You’ll also notice that it checks the Y position of the block. This is to ensure the block is not on the lowest row of blocks which will always have empty space below it.

Next, we need an Event that sets MKatchesPossible back to 0 when no blocks have an empty space below them. This second Event is going to be based on an Else condition:

Event:
     Condition: System>Else
     Action: System>Set value
          Variable = MatchesPossible
          Value = 0

Make sure that this Event immediately follows the first Event so that the Else statement is used correctly.

Your two new Events should look like this:

Match3_Part7_MatchesPossibleEvent

Finally, we are going to add a new condition to CheckMatches so that it looks at MatchesPossible to determine if a match can be made. Add this condition to the initial function call of the CheckMatches Event.

Condition: System>Compare variable
     Variable = MatchesPossible
     Comparison = Equal to
     Vale = 0

With the condition added, your CheckMatches Event should now look like this:

Match3_Part7_ModifiedCheckMatches

Unlike most of the issues we have encountered up to this point, this issue shows up on a fairly inconsistent basis. This means that we can’t really test to see if we fixed the issue, we can only test to see if we caused any other issues. If you play the game now, you should see that no new issues have been caused by this condition.

Points

The second issue I wanted to fix before we adding anything new relates to the Points system.

While working on the project for this tutorial, I noticed that something I did caused the Points system to behave strangely and give the player four or five times as many points as they should be getting for each match. Although I wasn’t able to determine what change I made caused this to start happening, I found that the root of the problem was that the GivePoints function was actually getting called multiple times since the Blocks were not being destroyed immediately. Thankfully, like our last issue, this can be fixed easily.

To fix this we are going to create a new variable which will tell the system whether it can give points. Then, whenever we are about to use the GivePoints function we will also modify the variable to ensure the Event only fires once. Finally, once the points have been given we will change the variable once more so that there won’t be an issue next time we try to give points.

First, create a Global Variable called PointsGiven:

Global Variable: PointsGiven
     Type = Number
     Initial Value = 0

Your variable should like this:

Match3_Part7_PointsGiven

Next we will modify the part of the FindMatches function which actually gives the points by adding a new Condition and two new Actions.

Condition: System>Compare variable
     Variable = PointsGiven
     Comparison = Equal to
     Value = 0

Now add this Action to the beginning of the Action list:

Action: System>Set value
     Variable = PointsGiven
     Value = 1

Finally, add this Action to the end of the Action list:

Action: System>Set value
     Variable = PointsGiven
     Value = 0

The Event should now look like this:

Match3_Part7_ModifiedIsMatched

With these changes we have made it so that the Event which calls GivePoints will only run when the PointsGiven variable is 0. Since we are immediately setting the value to 1 when we start the Event, this prevents the Event from firing more than once and ensures the player will receive the correct number of points.

If you run the game at this point you should receive the correct number of points for every match you make, even if you were not having this issue to begin with.


Eliminating Pre-Made Matches

Now that we have gotten those fixes out of the way, we can move on to creating the system that will eliminate matches that are spawned by the system when it is randomly assigning colors to the blocks it creates.

The problem we have now is that, since the Block colors are entirely random, it’s not uncommon for you to start the game and see a bunch of matches get made immediately. This is an issue because it could make the first few seconds of the game very confusing for someone who has never played before, and because it is giving the player points they didn’t earn.

To solve the issue, we’ll create a function that will look at each block and then see if that block is the same color as any of its neighbors. If it is the same color as one its neighbors, it will continue to change the color of that block until it no longer matches any of the blocks surrounding it.

To make this system work we will also have to create multiple support functions and Events as well, and add a new instance variable to the Block object.

Making It Work

First, create a new instance variable for the Block object:

Instance Variable: BlockID
     Type = Number
     Initial value = 0

This variable is what we will use to easily identify a block so that we can tell some of the functions we are going to make exactly what block we want to look at, regardless of its position.

Before we move on, we also need to start using this variable. Go to the On start of layout Event which creates the blocks and add a new Action before the Action that increases NumBlocks:

Action:Block>Set value
     Instance variable = BlockID
     Value = NumBlocks

Your block-spawning Event should now look like this:

Match3_Part7_ModifiedBlockSpawning

Next we need to make a function which will take in the X and Y position of a block and tell us what color that block is:

Event:
     Condition: Function>On function
          Name = "GetBlockColor"
          Sub-Event:
               Condition: Block>Compare X
                    Comparison = Equal to
                    X co-ordinate = Function.Param(0)
               Condition: Block>Compare Y
                    Comparison = Equal to
                    Y co-ordinate = Function.Param(0)
                    Action: Function>Set return value
                         Value = Block.Color
          Sub-Event: System>Else
               Action: Function>Set return value
                    Value = -1

The function should look like this when it is finished:

Match3_Part7_GetBlockColor

Now that we have a function which can tell us the color of any block, we are going to create the function which will actually look at a block and determine whether it has any neighboring Blocks which are the same color.

The way this function will work is quite simple.

  • First, we will pass a BlockID into the function.
  • If there is currently a block with that BlockID, the function will look at the four neighboring blocks, and will determine whether the block it’s looking at is the same color as any of its neighbors.
  • If it finds that there is a neighbor of the same color, it will begin changing the color of the block it’s looking at, and it will continue to change the color until the Block is a different color from all of its neighbors.

Before we can make this Event, we have to make a new Global Variable. In the function we will use a While loop to determine whether the block’s color needs to be changed. The variable we are about to create is the variable the While Loop will use to determine whether it needs to continue running:

Global Variable: HasMatchingNeighbor
     Type = Number
     Initial Value = 0

Tip:The Event we are going to make contains an Or-based Event. If you’ve never made an Event block that has an Or attribute all you have to do is make the Event Block like you normally would and then right-click the entire Block and choose Make ‘Or’ Block. Unlike a standard Event Block which requires all conditions to be filled before it will fire, an Or block will fire if any condition is fulfilled.

So, let’s make the Event:

Event:
     Condition: Function>On function
          Name = "RemoveSpawnedMatches"
          Sub-Event:
              Condition: Block>Compare instance variable
                   Instance variable = BlockID
                   Comparison = Equal to
                   Value = Function.param(0)
              Action: System> Set value
                   Variable = HasMatchignNeighbor
                   Value = 1
              Sub-Event:
                  Condition: System> While
                  Condition: System>Compare variable
                       Variable = HasMatchingNeighbor
                       Comparison = Equal to
                       Value = 1
                  Sub-Event: Or:
                      Condition: Block>Compare instance variable
                           Instance variable = Color
                           Comparison = Equal to
                           Value = Function.Call("GetBlockColor", Block.X - (Block.Width + 2), Block.Y)
                      Condition: Block>Compare instance variable
                           Instance variable = Color
                           Comparison = Equal to
                           Value = Function.Call("GetBlockColor", Block.X + (Block.Width + 2), Block.Y)
                      Condition: Block>Compare instance variable
                           Instance variable = Color
                           Comparison = Equal to
                           Value = Function.Call("GetBlockColor", Block.X, Block.Y - (Block.Width + 2))
                      Condition: Block>Compare instance variable
                           Instance variable = Color
                           Comparison = Equal to
                           Value = Function.Call("GetBlockColor", Block.X, Block.Y + (Block.Width + 2))
                      Action: Block>Set value
                           Instance variable = Color
                           Value = floor(Random(1,7))
                      Action: System>Set value
                           Variable = HasMatchignNeighbor
                           Value = 1
                  Sub-Event:
                      Condition: System>Else
                      Action: System>Set value
                           Variable = HasMatchignNeighbor
                           Value = 0

Your Event should look like this:

Match3_Part7_RemoveSpawnedMatches

So how does this function work exactly?

The first thing it does is check for a Block with the BlockID that the system passed in. When it locates that Block it sets the value of HasMatchingNeighbors to 1 and then runs the While loop.

Since the While loop will only run when HasMatchingNeighbors is 1, that is the value it sets it to. During the While loop, it tests to seewhether there is a neighboring Block to the left, the right, above, or below that is the same color as the Block we are looking at. If it finds a matching Block in any of these positions, it randomly assigns a new color to the Block and then runs the test again by ensuring HasMatchingNeighbors is set to 1. When it finally finds a color for the Block that doesn’t match any of its neighbors, it changes the value of HasMatchingNeighbors to 0 so that the While loop ends and the program can move on.

Now we need to implement this function into the game; to do this we are going to have to create two new variables and two new functions. Let’s start with the variables.

Global Variable: CheckStartingMatches
     Type = Number
     Value = 0
Global Variable: CheckNewestRow
     Type = Number
     Value = 0

Your variables should look like this:

Match3_Part7_CheckMatchesVariables

The two variables we just made will be used to trigger the two Events we are about to create. The Events themselves will be used to iterate through the blocks immediately after they are created and send each block into the RemoveSpawnedMatches function.

The reason we are not just calling the RemoveSpawnedMatches function immediately after creating the block is because the block grid needs to be complete for the function to work correctly. So, instead of just calling the function directly when blocks are made, we will instead trigger an Event that can go through the blocks and call the Function on its own after the grid is generated.

The first Event will be specifically for iterating through the initial group of blocks:

Event:
      Condition: System>Compare variable
           Instance variable = CheckStartingMatches
           Comparison = Equal to
           Value = 1
      Condition: System>For
           Name = "Blocks"
           Start index = 0
           End index = NumBlocks-1
      Action: Function>Call function
           Name = "RemoveSpawnedMatches"
           Parameter 0 = loopindex("Blocks")
      SubEvent:
           Condition: System>Compare two values
                First value = loopindex("Blocks")
                Comparison = Equal to
                Second value = NumBlocks-1
           Action: System>Set variable
                Instance variable = CheckStartingMatches
                Value = 0

This is what your event should look like:

Match3_Part7_CheckStartingMatches

The second Event will be specifically for checking new rows of blocks when they are made:

Event:
      Condition: System>Compare variable
           Instance variable = CheckNewestRow
           Comparison = Equal to
           Value = 1
      Condition: System>For
           Name = "Blocks"
           Start index = NumBlocks-9
           End index = NumBlocks-1
      Action: Function>Call function
           Name = "RemoveSpawnedMatches"
           Parameter 0 = loopindex("Blocks")
      SubEvent:
           Condition: System>Compare two values
                First value = loopindex("Blocks")
                Comparison = Equal to
                Second value = NumBlocks-1
           Action: System>Set variable
                Instance variable = CheckNewestRow
                Value = 0

This is what the second Event should look like:

Match3_Part7_CheckNewestRow

Implementation

With both of these functions in place we now just need to implement them. Go to the initial Event that makes the blocks, the On Start of Layout Event. We are going to add a Sub-Event to this which will tell the CheckStartingMatches Event to activate.

Sub-Event:
     Condition: System>Compare two values
          First value = loopindex("X")
          Comparison
          Second value: 7
     Condition: System>Compare two values
          First value = loopindex("Y")
          Comparison
          Second value: 3
     Action: System>Set value
          Instance variable = CheckStartingMatches
          Value = 1

Your Event should now look like this:

Match3_Part7_ModifiedStartofLayout

This Sub-Event listens for when the nested For loop has ended and then changes the value of the CheckStartingMatches variable to activate the appropriate Event.

We are now going to make almost the exact same Sub-Event and attach it to the SpawnNewBlocks function.

Sub-Event:
     Condition: System>Compare two values
          First value = loopindex("X")
          Comparison
          Second value: 7
     Action: System>Set value
          Instance variable = CheckNewestRow
          Value = 1

SpawnNewBlocks should now look like this:

Match3_Part7_MdifiedSpawnNewBlocks

This Sub-Event does the same thing as the previous one except that it activates the other Event we created. If you run the game at this point you should see that when you start the game there are no longer any matches automatically occurring.


Conclusion

In this tutorial, we didn’t make too many changes to the game, but the ones we did make were very important.

At this point, I think it’s best for us to stop for now and save the final two game elements for the next tutorial, where we will be covering chains/combos and the Game Over screen. This will be the final part of the series, so I’ll also talk about some game mechanics we won’t be covering in these tutorials, and give you some advice on how you could make those systems on your own.

If you want to get a head start on next week’s content, start looking at how you could detect when the Game Over screen should pop up, based on the position or height of some of the blocks. Alternatively, start thinking about how you could detect when the player makes a chain reaction that causes more than one group to be formed.

Whatever you do, I hope to see you back here next week for the final installment of the series.


Viewing all articles
Browse latest Browse all 728

Trending Articles