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
3, instead of from
7 like it originally did.
The event should now look like this:
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:
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
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:
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:
The first four Actions we just added adjust the positions of the
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:
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:
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
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:
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:
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:
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:
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:
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:
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:
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:
Now that we have the variables in place, I'll explain what each does.
SPEEDMODis 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.
PointsForSpeedUpis the number of points the player needs to hit the next speed up.
PointsBetweenSpeedUpsrepresents how much the
PointsForSpeedUpvariable 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
POINTSFORSPEEDUPMODbefore it is added to
POINTSFORSPEEDUPMODis 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:
- Insert a new Sprite object on Layout 1.
- With the Animation Editor, open the image
- Set the Name to
- Set the Layer to
- Set the Position to
- Set Initial visibility to
- Add a Fade Behavior to the Sprite.
- Set Active at Start to
- Set the Fade out time to
- Set Destroy to
- Set the Name to
Your Layout should now look like this:
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:
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"
GivePoints() function should now look like this:
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.
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.