Advertisement
From Scratch

Make a Match-3 Game in Construct 2: Block Movement

by

In the previous part of this series, we made some small but important changes to many of the systems we created for our Match-3 game. With these improvements implemented, we are now going to get back on track and implement one of the last two major systems for the game: the Block Movement system.

This tutorial will take you through the entire development of the system which allows the blocks to rise to the top of the screen, and will also cover the creation of all the smaller systems we will need to implement to support the movement system. While the topics I cover in this tutorial are not too complex, there is a lot to go over - so let's get to it.


Final Game Demo

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




1. Moving Blocks Up

Before we start moving the blocks, we have to make a small change to the events which spawn the Blocks. Go to the System > On Start of Layout event and change the Y for loop to go from 0 to 3, instead of from 0 to 7 like it originally did.

The event should now look like this:

ModifiedStartingBlockSpawn

The reason we made this change is because we want the game to start with fewer blocks on screen, so that it doesn't end as quickly when we add a Game Over in the next tutorial.

Next, we will create a variable that will represent the speed of the blocks:

Global Variable:
      Name = CurrentSpeed
      Type = Number
      Value = 0.2

Now we'll create the event that actually moves the blocks:

Event:
      Condition: System>Every X Seconds
            Interval (seconds) = CurrentSpeed
      Action: Block>Move at Angle
            Angle = -90
            Distance = 1

The event should look like this:

Match3_Part6_BlockMovementEvent1

If you run the game after adding this event, the first thing you should see is that the blocks drop, due to the gravity we implemented in a previous tutorial. After that, the Blocks should slowly rise until they are in their original position, and then drop again. This will repeat infinitely as long as you don't do anything to the Blocks.

This happens because the Blocks are moving past the point where the gravity is supposed to kick in, and they are discovering that there are no blocks underneath them, causing them all to fall. While this is an issue, it's not the first one I want to look at.


2. Fixing Swapping

Run the game, and try making a swap of any kind. When you do this, you should see that the blocks start getting stuck behind each other, getting stuck in positions that are not aligned with the grid, and just generally misbehaving. There are two reasons for this issue.

The first issue is that, even though we are moving the Blocks themselves, we are not moving the LeftBlock, RightBlock, TopBlock, and BottomBlock objects with them, meaning that the blocks you use to detect swaps are not moving with the block grid - they are just sitting in the position they are set to when you first pick up a block.

So, when you try to make a swap, the blocks are getting put out of place because the swap detection blocks have not adjusted at all to the grid. (This is also the reason for the second issue we are having, which is that we are not modifying the positions we stored in the BlockPositions array either.)

The GIF below demonstrates this problem:

UnmovingSwapBlocks

As you can see in the GIF, the swap detection blocks are not moving, even though the blocks themselves are.

To solve both of these problems, we'll add a few more Actions to the Event we just created:

Action: BottomBlock>Move at Angle
     Angle = -90
     Distance = 1
Action: LeftBlock>Move at Angle
     Angle = -90
     Distance = 1
Action: RightBlock>Move at Angle
     Angle = -90
     Distance = 1
Action: TopBlock>Move at Angle
     Angle = -90
     Distance = 1
Action: BlockPositions>Set at XY
     X = 0
     Y = 1
     Value = BlockPositions.At(0,1) - 1
Action: BlockPositions>Set at XY
     X = 1
     Y = 1
     Value = BlockPositions.At(1,1) - 1

The Event should now look like this:

Match3_Part6_BlockMovementEvent2

The first four Actions we just added adjust the positions of the LeftBlock, TopBlock, RightBlock, and BottomBlock objects so that they stay inline with the block grid. The second two events adjust the Y values we have stored in the BlockPositions array so that they also stay inline with the block grid.

If you test the game again at this point, swapping should be mostly fixed.

At this point, there is still one other issue we need to deal with to make swapping work correctly. Run the game and attempt to make a downward swap with any of the blocks in the bottom row while that row is partially below the bottom area of the game field:

Match3_Part6_BlocksBehindField

Make the swap while the Blocks are behind the border, like the highlighted ones in the image above.

If you did this correctly, you should see that nothing happened and the blocks did not swap. If you waited too long, the blocks may have swapped because they had moved above the game field's border again, so if this happened try again once they fall and you should see this issue occur.

This issue is rather simple to solve and to understand. If you look at the code for downward swaps, you should find the Event that we added in the previous tutorial which prevents the player from making downward swaps that cause the Block to fall off the bottom of the game field. Since this statement prevents the player from making downward swaps when the BottomBlock object is lower than the block's initial Y position, it prevents the blocks from being swapped once they have fallen and only allows you to make swaps again once they have moved past their original position again.

To fix this statement we are going to make one small change to the condition:

Condition: BottomBlock>Compare Y
     Comparison = Less or equal
     Y co-ordinate = SPAWNY + ((Block.Width + 2)/2)

The condition should now look like this:

Match3_Part6_ModifiedBottomSwap

This modification means that a downward swap can only occur while the BottomBlock object is at most a half-block below the Y position the blocks start in. This also means that, once we start spawning new rows of blocks and pushing them onto the screen from the bottom, those blocks will only be able to be swapped in this way once at least half of the block is visible.

We are also going to put a similar restriction into all of our swapping Events to ensure that all of them become usable at the same time, and that a block cannot be swapped at all until at least half of it is visible. Again, this will also help when we integrate the system which spawns new rows of blocks. To do this, we'll add a new condition to each of the remaining three swap Events.

The conditions we add will be exactly the same as the one we just modified in the BottomBlock event, except that they will reference the TopBlock, RightBlock, and LeftBlock objects instead of the BottomBlock object, depending on which event it is in.

The new Condition for the TopBlock Event should be:

Condition: TopBlock>Compare Y
     Comparison = Less or equal
     Y co-ordinate = SPAWNY + ((Block.Width + 2)/2)

The new Condition for the LeftBlock Event should be:

Condition: LeftBlock>Compare Y
     Comparison = Less or equal
     Y co-ordinate = SPAWNY + ((Block.Width + 2)/2)

The new Condition for the RightBlock Event should be:

Condition: RightBlock>Compare Y
     Comparison = Less or equal
     Y co-ordinate = SPAWNY + ((Block.Width + 2)/2)

Your whole On DragDrop drop Event should now look like this:

Match3_Part6_ModifiedDragDropEvent

With these new conditions in place, we have fixed our swapping mechanics and we have started preparing the existing systems for the next system we are adding: the one which will spawn new rows of blocks.


3. Spawning More Blocks

Now that we have the blocks moving up at a constant rate, we need to make the new rows of blocks spawn at the correct time, and allow the player to continue playing for as long as they want. We are going to use a function to spawn the new rows of blocks, and we are going to use an event which detects when the blocks are inline with SPAWNY to trigger that function.

So first, let's make the function itself.

Event:
     Condition: Function>On function
          Name = "SpawnNewBlocks"
     Condition: System>For
          Name = "X"
          Start Index = 0
          End Index = 7
     Action: System>Create object
          Object = Block
          Layer = 1
          X = SPAWNX + (loopIndex("X"))*(Block.Width+2)
          Y = SPAWNY + (Block.Width+2)
     Action: Block>Set value
          Instance Variable = Color
          Value = floor(Random(1,7))
     Action: System>Add to
          Variable = NumBlocks
          Value = 1

Your new Event should look like this:

Match3_Part6_SpawnNewBlocks

When used, this function will create a row of blocks below the bottom row of blocks in the game field. As it stands now, though, we don't actually use this function at any point, so let's make the Event that does that:

Event:
     Condition: System>Every X seconds
          Interval(seconds) = CurrentSpeed
     Condition: Block>Compare Y
          Comparison = Equal to
          Y = SPAWNY
     Condition: Invert: Block>Is Dragging
     Action: Function>Call function
          Name = "SpawnNewBlocks"

Your new Event should look like this:

Match3_Part6_CallSpawnNewBlocks

The Event we just created checks the Y position of the blocks every time they are moved. If it finds any blocks that are inline with SPAWNY, it triggers the SpawnNewBlocks() function like we discussed earlier. It also checks to ensure that the block it finds is not the one being dragged by the player.

If you test your game at this point, it will work, but you should notice one strange issue. The moment you start the game, your blocks will fall as if there are no blocks below them, but after that point everything works perfectly, and new blocks are spawned whenever they are needed.

This happens because, when the game first starts, it processes the gravity code before the code which spawns new rows of blocks. To fix this we are going to make a small adjustment to the code that spawns the initial group of blocks so that they are spawned below the point where a new row would be needed. This allows it to avoid running the gravity code immediately, and allows it to create the new row of blocks once the existing blocks are in the right location.

Go to the Event that spawns the initial group of blocks and modify the Action which actually creates the block. Change the Action to this:

Action: System>Create object
     Object = Block
     Layer = 1
     X = SPAWNX + (loopIndex("X"))*(Block.Width+2)
     Y = SPAWNY - (loopIndex("Y"))*(Block.Width+2) + 5

The Event should now look like this:

Match3_Part6_ModifiedInitialBlockSpawn

This modification means that the blocks will spawn five pixels below SPAWNY. This means that the blocks will actually have to move up five times before a new row will spawn, and solves our problem.


4. A Bit of Animation

At this point our Blocks are moving, and we have new rows being created. On top of that, remember that earlier we prevented the player using any block until at least half of the block is visible. While this is a good feature, the player may not understand why a block cannot be used immediately when it becomes visible, even if not much of it is visible at the time.

Because of this potential UI issue, we are going to make each block use the gray block sprite (at the beginning of the block's animation frames) when it is in this unusable state. This will make it clear to the player when a block becomes usable, and it will give us a chance to finally use our last block image.

You can see an example of what it will look like when the Blocks go from being inactive to active in the GIF below:

BlockColorChange

The Event we create will also include a second Condition that checks to make sure the block it's looking at is not being dragged. This Condition allows us to ensure that when the player drags a block below the point where blocks become usable, it will not change its image so that it is gray, and will stay the color it is supposed to be.

To make this animation work we first need to add a new Event:

Event:
     Condition: Block>Compare Y
          Comparison = Greater than
          Y = SPAWNY + ((Block.Width + 2)/2)
     Condition: Invert: Block>Is Dragging
     Action: Block>Set frame
          Frame number = 0

The new Event should look like this:

InactiveBlockAnimation

You should now be able to test your game and you should see that the blocks are using the gray image when they are below the point they become usable.


5. Enabling and Disabling Drag/Drop

If you run the game now, you'll notice that even though the blocks cannot be swapped with each other when they are gray, the gray blocks can still be dragged around and manipulated. This is because we never disabled the Drag/Drop capabilities of the block when we prevented the player from swapping with them.

To prevent the gray blocks from being able to be moved, we'll modify the Event we created in the previous section. First we will add a new Action which turns off the dragging when the Block is below the point it becomes usable.

Add this Action to the Event we created earlier:

Action: Block (DragDrop)>Set enabled
     State = Disabled

We are also going to add an Else statement for this Event which lets the block be dragged again once it is above the point that the block becomes usable:

Event:
     Condition: Else
     Action: Block (DragDrop)>Set enabled
          State = Enabled

With both of these changes, the Event should look like this:

Match3_Part6_ModifiedInactive

If you test the game at this point, the blocks should no longer be usable when they are gray, and should work the same way they always have when they are not.


6. Speed Changes

The final thing I want to cover in this article is the system that will allow us to change the game's speed over time. Specifically, this is the system that will make the blocks move faster as the player eliminates more of them.

The system we are going to create is relatively simple: every time the player gets some number of points, the speed of the game will increase based on a modifier that we're going to create, and the number of points the player needs to get the next speed increase will change based on a second modifier.

Before we can actually start making the Events for this system, we'll create a couple of Global Variables to handle the new features for us:

Global Variable: SPEEDMOD
     Type = Number
     Initial value = 0.8
     Constant = Yes
Global Variable: PointsForSpeedUp
     Type = Number
     Initial value = 400
     Constant = No
Global Variable: PointsBetweenSpeedUps
     Type = Number
     Initial value = 400
     Constant = No
Global Variable: POINTSFORSPEEDUPMOD
     Type = Number
     Initial value = 1.4
     Constant = Yes

Your new variables should look like this:

Match3_Part6_GlobalVariables

Now that we have the variables in place, I'll explain what each does.

  • SPEEDMOD is the variable we will multiply the speed by to modify it whenever the player reaches the number of points they need to cause a speed increase.
  • PointsForSpeedUp is the number of points the player needs to hit the next speed up.
  • PointsBetweenSpeedUps represents how much the PointsForSpeedUp variable will increase when the Player gets a speed up, to adjust it so that the next speed up takes even more points. Right now it is 400, like PointsForSpeedUp, but when the player actually gets a speed up it will be multiplied by POINTSFORSPEEDUPMOD before it is added to PointsForSpeedUp.
  • Finally, POINTSFORSPEEDUPMOD is the variable we will use to modify the number of points the player needs to get to increase their speed another time past the one they most recently got.

Along with setting up the variables, we also need to create a new sprite object which will act as the alert for the player when the speed increases.

Go to Layout 1 and follow these steps to create the new sprite:

  1. Insert a new Sprite object on Layout 1.
  2. With the Animation Editor, open the image SpeedIncImage.png.
    1. Set the Name to SpeedIncreaseIndicator.
    2. Set the Layer to Game Field.
    3. Set the Position to 188, 329.
    4. Set Initial visibility to Invisible.
      1. Add a Fade Behavior to the Sprite.
      2. Set Active at Start to No.
      3. Set the Fade out time to 2.5.
      4. Set Destroy to No.

Your Layout should now look like this:

Match3_Part6_SpeedLayout

Now we will actually create the Event that changes the Speed:

Event:
     Condition: Function>On function
          Name = "CheckForSpeedUp"
     Condition: System>Compare variable
          Variable = Score
          Comparison = Greater or equal
          Value = PointsForSpeedUp
     Action: SpeedIncreaseIndicator>Set visible
          Visibility = Visible
     Action: SpeedIncreaseIndicator>Start fade
     Action System>Set value
          Variable = CurrentSpeed
          Value = CurrentSpeed * SPEEDMOD
     Action System>Set value
          Variable = PointsBetweenSpeedUp
          Value = PointsBetweenSpeedUp * POINTSFORSPEEDUPMOD
     Action: System>Add to
          Variable = PointsForSpeedUp
          Value = PointsBetweenSpeedUp

Your Event should look like this:

Match3_Part6_CheckForSpeedUp

When this function is called, it checks to see whether the player has scored enough points to warrant a speed increase. If they have, then:

  • it activates the sprite which tells the player the speed has increased by making it visible and starting the Fade
  • it increases the speed by multiplying it by the modifier
  • it determines the number of points needed before the next speed up, and
  • it adds that value to the total number of points the player will need to have before the speed increases again.

With this function complete, we just have to make sure that it gets called. Go to the GivePoints() function and add this Action to the end of the primary event and the sub-event:

Action: Function>Call function
     Name = "CheckForSpeedUp"

The GivePoints() function should now look like this:

Match3_Part6_ModifiedGivePoints

With that Event complete you should be able to test your game and see the speed up system in action.

Tip: As I played with it more, I found that these values felt a little bit off, so I suggest you take some time to experiment with the system, and find the values you feel most comfortable with.


Conclusion

We've covered a lot of different topics in this article, but everything we dealt with was directly or indirectly related to getting the movement system to work the way we wanted. While it took some time, and required us to make more systems than we might have anticipated at the beginning, the payoff was worth it and we ended up with a very strong system in the end.

Because of how much we've already covered, I think this is a good place to end this article. The next article should be the final tutorial in this series and we are going to cover a lot of smaller topics within it, but the biggest thing we are covering is definitely the elimination of pre-made matches.

If you want to start trying to figure out how we will eliminate them, take a look at how we detect matches to begin with. The system we create will be very similar to that system, except it will use the matches it finds in a different way. Start thinking about it and see what you can come up with, and I will see you back here next time for the last major tutorial in the series.

Related Posts