You have learned a lot up until now. The last thing we covered was how to store multiple values in an array or dictionary and how to access them. You also learned about WebStorage that will store data in the player's computer; this data can be accessed when the game is played at a later time.
However, now we will develop on what we know so far. I will show you more ways in which we can use arrays other than to store the player's score. We'll also create a new kind of enemy. Earlier, our enemies were really simple and easy to make; they just went down and shot straight down. Now, we'll give our enemies a little AI to make them cleverer, by making a battle tank game.
In this chapter, you will learn how to:
As always, we'll begin by creating the layout for our game. Unfortunately, for us, there is no sprite in freebundle.zip
that fits a battle tank game. However, we can use two sprites to make a tank: one sprite for the base of the tank and another one for the turret.
First, add two sprite objects to the layout and name them tankTurret
and tankBase
. The tankBase
sprite is from UI elementsAdventure pack
. Pick any square icon; in this example, I will use the buttonSquare_blue_pressed
image. For the turret, pick an arrow image that points to the right, such as arrowSilver_right
. If you place these images on top of each other, you will see an icon similar to the one shown in the following screenshot:
This looks good enough for a tank. However, for it to be called a tank, the base and the turret must act like one, that is, if one is created, so is the other, and if one is destroyed, so is the other. How do we do this? Well, Construct 2 has a feature called container to group objects together.
So, what is a container? A container is a feature in Construct 2 that eases the creation of an object composed of more than one object. Sometimes, when making a game, you'll have an object that is too complex to be represented in one object. Using a container, we can treat multiple objects as if they are one. In our example here, we have a tank that consists of two sprites: the base and the turret. Adding these two to a container enables them to rotate and move independently; they also have their own instance variables. This is useful, for example, when you want to create a side-scrolling game in which you can make the hand and sword check against collision with a target enemy to hurt them and the player's body to be checked against collision with the enemy's attack.
All objects in a container have the following traits:
These traits are made because Construct 2 treats a container as one object.
Now, let's create a container. To do so, select one of the objects you want in the container, such as the tankTurret
object. After that, you can see the Container section of its property in the Properties bar. If it doesn't have any container yet, it will say No container along with a blue Create link beside it; click on the link to create the container.
Construct 2 will open a new window to select objects to add to this container. For now, we only have the tankBase
object; so let's add this, click on it, and press the OK button. The tankBase
object is now added to the container; if you look at the Properties bar, you'll see it. Click on the two objects alternately and look at the Properties bar. You can see that they are added to each other's container.
You can add another object to that container or remove an existing member from it. Now, every time a tankBase
object is created, Construct 2 will also create a tankTurret
object. However, it is not enough to have them created at the same time; we want the turret to stick to the base so that when the base tank moves, the turrets follow.
We can pin an object to another object using a behavior called Pin; this behavior will pin an object to another, making it maintain its distance and angle with the object it's pinned to. As we will mainly move tankBase
and make tankTurret
follow it, we will add this behavior to the tankTurret
object.
After adding the Pin behavior, we will use it to pin to another object, in this case, the tankBase
object. This pinning is done by code, and we will pin the tankTurret
object when the turret is created. The turret is created when the base is created, so we'll pin the turret when the base is created. While pinning, we have to decide whether to pin by position only, or angle only, or both.
Pinning by its position will make the pinned object retain its position relative to the other object, but when the other object rotates, the pinned object doesn't change its angle.
Pinning by its angle will make the pinned object retain its angle relative to the other object, but it doesn't change its position when the other object moves.
Pinning by both angle and position will make the pinned object retain its position and angle relative to the other object.
For now, we want to pin by both position and angle, so the code is as follows:
We have grouped two objects in a container; now, let's make them move. To move the units, we will first select them and then click on a place on the game to move the selected units there. We need an instance variable to make the units know whether or not they've been selected. This instance variable is a Boolean variable; let's call it selected,
and we'll create it in the tankBase
object.
To be able to select a unit, the game must respond to a mouse input; so we'll add a Mouse object in our project. Then, we have to move the unit, but how do we do this? Construct 2 has a simple way to move an object with a Pathfinding behavior. This behavior takes a position to move to and then moves the object to this position. Sounds simple enough, so let's add this behavior to the tankBase
object.
Now that we have this behavior, we can start moving our units. There is a way to move them: players must first click on the unit they want to move to select them; we marked the selected units by changing their selected instance variable to true
:
After that, the players must click on a place in the game they want the selected units to move to, and we'll tell the units to find a way there using a Pathfinding behavior:
If the units can find a way there, they will go there:
We have made our selected units go where we want them to! Test the game now and you will see the units moving to where you click with the mouse. They even rotate nicely when changing directions.
I think a blank white background is pretty boring for a level, don't you? So, let's add a few obstacles in the way of our unit. We will place these obstacles in their own layer, so create a new layer called Obstacles and put it between the Main and Background layers.
We will use a tiled background to make the obstacles so that we can create several kinds of obstacles. Unfortunately, there isn't a sprite in the Sprites
folder with a size of 16 x 16 or 32 x 32 that we can use for this, so we'll have to make our own. First, we'll create a tiled background object and name it tiledObstacle
, and then, in the edit image window, we'll resize it by clicking on the Resize icon.
Now, change the value to 32 x 32.
After we resize it, we'll color the image in the orange color. Close the edit image window and place the tiled obstacles in three ways: vertically, horizontally, and diagonally, as shown in the following screenshot:
We've got three kinds of obstacles; this is a good way to show you how the Pathfinding behavior works. If you test the game now, our unit can still walk through the obstacles. Why? This is because the Pathfinding behavior doesn't see them as obstacles.
There are two ways for the Pathfinding behavior to recognize obstacles: solid and custom. By default, the Pathfinding behavior sees solid objects as obstacles and will try to move around them. The other setting, Custom, means that we have to tell the Pathfinding behavior which objects are the obstacles. Remember that solid objects are objects with a Solid behavior, which makes them impassable by other objects. You can change the setting in the Properties bar.
For now, let me show you how to use solid objects as obstacles. We only need to add a solid behavior to the tiledObstacle
objects, and the Pathfinding behavior already knows that they are obstacles. Test the game after you've added a Solid behavior to the obstacles, and you can see that our unit now tries to evade them.
Alright, we know how simple it is to make obstacles with solid objects, but how do we make custom obstacles that aren't solid objects? To answer this, we will first create another object to be this custom obstacle. Add another sprite object to the layout, and as we don't have the appropriate sprite for this, let's resize it to 32 x 32 and give it a blue color. Name this sprite civilians
; it'd make sense for tanks to avoid them. We will create custom obstacles when we want one object with the Pathfinding behavior to evade an obstacle, and when we also want the other object to not evade the same obstacle.
We'll then change the Obstacles property to Custom so that the Pathfinding behavior will look for custom objects as obstacles.
So, how do we tell the Pathfinding behavior which objects are the obstacles? We will do this using code. When tankBase
is created, we'll tell it which objects are the obstacles using the add obstacle
action. We'll add the civilians
and tiledObstacle
objects as obstacles; we want to add the solid objects in this way because the Pathfinding behavior's property has been changed to Custom. So, on the event sheet, edit the tankBase
property's On created
action to be as follows:
After this, place the civilians
objects somewhere so that it would hinder the tank's movement. This is represented in the following screenshot:
If you test the game now, you'll see that the tank is trying to evade the civilians as they go to their destination.
We've finished making our unit, so now let's create the enemy objects. For the enemy objects, we will use the same technique we use when we make our units, that is, adding two objects to a container. For the enemyBase
object, we will use the buttonSquare_beige_pressed
sprite from the Adventure pack
folder, and for the enemyTurret
object, we'll use arrowBlue_right
from the same folder.
After we add them to the layout, we will place them together in a container. Follow the same process as with the player's tank, but with the enemy tank objects. We'll also add the Pin behavior to enemyTurret
, and then like tankTurret
, we'll pin it to enemyBase
when it is created:
Put these two objects a bit away from the player's tank unit, somewhere out of the game window but still inside the layout, because they will to move and search for the player later. Don't forget to put them on top of each other, as shown in the following screenshot:
We will do one more thing before we have finished making the enemy: enable it to search for the player. How are we going to do this? We'll do this by adding the Pathfinding behavior to the enemyBase
object. Add this behavior to enemyBase
, and we're done. We'll keep the Obstacles property as Solid for the enemy.
We have a turret for both the player's tank unit and the enemy's unit, but for now, they don't do anything, so let's make it shoot. To "shoot" means that it will have to recognize its target, rotate it, and then shoot at it. Lucky for us, Construct 2 already has a behavior to do all of this: the Turret behavior. So, let's add this to both tankTurret
and enemyTurret
.
After we add this behavior to both turrets, we need to determine which objects are the targets for the respective turrets. We will do this using the add object to target
action that will make the turret automatically shoot the assigned object when it's in range. We want tankTurret
to target the enemy unit, so we will add this object to its target:
We want enemyTurret
to target the player's unit, so we will add this to its list of targets:
Now, if they come into each other's range, the following events will occur:
tankTurret
object will recognize enemyTurret
as its target (and the other way around)On target acquired
event will fire, and the turret will rotates to its targetOn shoot
event will trigger at the rate determined by the rate of fire
propertyRemember that the turret object doesn't automatically shoot, but we must create an object with a Bullet behavior to act as the bullet, so let's do this now. Add two more sprite objects with a size of 14 x 14, make them a small circle sprite, and name them playerBullet
and enemyBullet
. Place them outside of the layout and give them both the Bullet behavior. We also want to destroy the bullets if they go outside of the layout, so add the destroy outside
layout to them.
We'll give playerBullet
a bluish color and enemyBullet
a reddish color. After that, we need to add the code. Like I explained in the previous bullet points, when the turret thinks it can shoot, it will fire the On shoot
event. In this event, we will create the player's bullet and shoot it at the same angle as the turret:
Let's do the same thing for the enemy turrets:
With this, the turrets will shoot at their targets. Finally, let's declare when the bullets are destroyed rather than letting them fly endlessly on the game screen. There are two scenarios when the bullets are destroyed: when they hit tankBase
(for enemyBullet
) and when they hit enemyBase
(for playerBullet
). To add a gameplay element, let's create a health instance variable to tankBase
and enemyBase
.
In this example, we'll give an initial value of 10
to the health instance variable of both objects, and we'll decrease it little by little:
Here, we are writing the code for the bullet collision with tankBase
:
Don't forget to destroy the objects when the health of tankBase
and enemyBase
reaches 0
; otherwise, nothing will happen. We want these objects to be destroyed when their health is 0
or less:
Here, we are writing the code for the tankBase Destroy
event:
Now that we have created our own unit and an enemy, let's create a level. We will add a few more friendly and enemy units on the layout, as well as more obstacles. We want to make a level you'd find in a tank-strategy game. You can follow this design or create your own:
We now have a game in which the layout is bigger than the game window, and we want to be able to navigate around it. Previously, we were able to navigate using the Scroll To behavior that's added to an object, but now, we have more than one unit that the player can control. So we want to navigate around the layout without using the Scroll To behavior. How do we do this?
We'll use the Mouse object to scroll the layout. If the player puts the mouse on the right-hand side of the screen, then the game will scroll to the right; if the player puts the mouse on the left-hand side of the screen, then the game will scroll to the left, and the same applies for up and down. We'll use the Mouse object's expressions, absoluteX
and absoluteY
, which will give us the x and y coordinates of the mouse on the screen.
This is different from the usual mouse.X
or mouse.Y
expression that gives us the mouse's x and y coordinates on the layout and is, therefore, affected by scrolling. Both absoluteX
and absoluteY
expressions aren't affected by scrolling, so they're perfect if you want to know the mouse's position on screen.
So first, we'll scroll to the left when the mouse's position is near the left-hand edge of the screen, the way we know this is if the mouse's x position is a small number, for example if it is less than 20 pixels. We'll scroll it in amounts of 500 pixels per second; in this action, we will also use the dt
expression; here, dt
is short for delta time, which is the time difference between each tick. We will multiply the scroll speed with dt
to ensure that the scrolling moves correctly even on slow computers:
Next, we will scroll the screen to the right. To do this, we need to know if the mouse position is 20 pixels from the right-hand side of the screen. Luckily, there's an expression to check the window's width: WindowWidth
(pretty clear, right?). 20 pixels from the right-hand side means that 20 pixels are subtracted from the WindowWidth
expression:
Then, we will scroll the screen up; doing this is the same like scrolling to the left, but we'll check the absoluteY
value instead of absoluteX
. We'll also scroll it in the y direction to make it move up:
Finally, we'll scroll the screen down. To do this, we also need to know the height of the screen, similar to when you want to scroll to the right. We also have an expression for this, which is WindowHeight
:
Test the game now, and you can navigate the layout using your mouse.
We have a pretty good base for the game, but one thing is still missing: the sound. Construct 2 has an object to handle any kind of sound in the game: the Audio object. This object can play sound effects and background music needed in the game. So, we'll add this object to the game.
Before we use the Audio object to play sounds, we need to import the sound files. If you look at the Projects bar, there are two places where sounds can be imported: the Sounds
folder and the Music
folder.
The files in the Sounds
folder will be downloaded before they are played, so it is perfect for short sound files such as sound effects. On the other hand, files in the Music
folder will be streamed from the server instead of being downloaded to the player's computer. This is suitable for long background music, so the files can be played without the player having to wait for a few minutes for the files to be downloaded. However, the music might buffer while streaming, which can cause a delay.
The sound effects we're using in this example are from the Sound FX
folder in freebundle.zip
; the background music is from the Music
folder of the same bundle. To import the sound effects, we'll right-click on the Sounds
folder in Construct 2 and select Import sounds. After that, a window will open where you can search for the intended files for sound effects. For now, we'll select the one from the freebundle
folder, which is SFX1.wav
present at freebundleSound FX
.
After that, you'll see another window that will convert the files to be compatible with browsers. If the icon beside the filename is a green check mark, then it's good.
After you click on the Import button, Construct 2 will convert the files to be compatible with common web browsers. The process for this is the same as the one to import music: right-click on the Music
folder and select the file. In this example, we'll use MattOglseby-3
and then import it. We now have the required files to add sound effects and background music.
There's one thing to keep in mind when importing audio files to Construct 2: file types. Even now, the browser makers still can't agree on one sound format to be supported on their browsers. Internet Explorer and Safari still use the MPEG-4 AAC format (the .m4a
file), while Firefox, Chrome, and Opera support the free Ogg Vorbis format.
Therefore, to support most browsers available, you have to provide both file types for one audio. However, if you can't, you can simply provide PCM .wav
files for the audio, as this file type is widely supported, and Construct 2 can convert it to both the MPEG-4 AAC and Ogg Vorbis formats.
Okay, now that you know about the Audio object and how it works, it's time to actually add the sound to our game. There are two places where we want to play the sound effects: when the tanks are shot and when the tanks are destroyed. We'll play the SFX1
audio file for both events; edit them as follows:
We'll then edit the events where the player's units and enemy tanks are destroyed, to play the explosion sound effect:
Here, we are writing the code for the tankBase
destroy event:
Now, if you test the game, you can hear the sound effects when the turrets are shot and when the tanks are destroyed, but this game is still a bit quiet. Maybe, we can add music to lighten it up. When playing music, always make sure that you play it on a trigger event, or else you might accidentally start the same music multiple times.
So, we'll play the background music at the start of the level. We'll use MattOglseby-3
from freebundleMusic
and import it to our project, like the SFX1
file (make sure that you imported the .ogg
file and not the .m4a
file), and play it at the start of the layout:
Test the game now; can you feel how adding background music makes the game more entertaining and enjoyable?