Flash Tutorial Links:
Play Games: HTML5 Tutorials:

How To Make A Tower Defense Game (Part 2)

3 unique towers have become a staple in Tower Defense games - the straight forward, no nonsense shooter tower, the slow tower, and an AOE tower. Learn how to make them all!

Back to How To Make A Tower Defense Game (Part 1).

Step 4 - The Game Loop, Handling User Controls

Finally, let's move on to the Game Loop. As usual, let's see what are the user controls we need to handle.

272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
public function update(evt:Event)
{
    //******************			
    //Handle User Input
    //******************
    if (hitEscape)
    {
        if (currTower)
        {
            //cancel build
            if (currTower is Tower_Fire)
            {
                //refund gold
                currGold += C.TOWER_FIRE_COST;
            }
            else if (currTower is Tower_Ice)
            {
                //refund gold
                currGold += C.TOWER_ICE_COST;
            }
            else if (currTower is Tower_Lightning)
            {
                //refund gold
                currGold += C.TOWER_LIGHTNING_COST;
            }
            mcGameUI.removeChild(currTower);
            currTower = null;
    
            for (var i in placings)
            {
                placings[i].gotoAndStop("off");
            }
        }
    }
    hitEscape = false;
    
    if (currTower != null)
    {
        currTower.x = mouseX;
        currTower.y = mouseY;
    }

The entire chunk of codes from lines 277 to 306 handles what happen when the player hits the escape key. Basically, there is only one use for it, that is, after he clicks on the build tower buttons, he may change his mind. To cancel this current option of tower he selected (and remember currTower visually displays this tower he has currently chosen), he presses the Escape key. The variable hitEscape is set to true by the keyDownHandlers you can find in the GameController when that happens.

Line 279 checks if the player has already clicked on a tower, because if he hadn't, there's really nothing for you to cancel. Then, they next few lines check what kind of a tower the player had clicked previously, basically by comparing the class that currTower is, and then refunds the player this amount of gold.

Lines 297 and 298 removes the currTower from the mcGameUI, and then sets it to null, thereby cancelling the current tower.

Lines 308 to 312 makes the currTower follow the mouse movement, if it exists.

Step 5 - The Game Loop, Handling Game Logic

Finally, we have reached the crux of the game loop. Let's take a look at the spawning of the mobs first.

317
318
319
320
321
322
323
324
//Update the mobs
if ((currWave < maxWave) && (monsters.length == 0))
{
    currWave++;
    
    //spawn the monsters
    spawnWave(currWave);
}

The condition in line 318 checks that the currWave is less than maxWave, i.e. there are more waves that need to come before we end the game. currWave starts off as 0, and maxWave is a value we set earlier on. In the tutorial, I set it as 20, but you are free to change this. The second part of the condition checks that there is nothing in the monsters array because we only want the next wave to come when all the monsters in this current wave have been killed.

Line 320 increases the current wave number, and line 323 calls upon spawnWave to unleash the new mobs. Let's just move forward to see what spawnWave does.

434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
...
//Spawning of monsters
private function spawnMonster(xPos, yPos)
{
    var monsterToSpawn = new Monster();
    monsterToSpawn.x = xPos;
    monsterToSpawn.y = yPos;
    monsters.push(monsterToSpawn);
    mcGameStage.addChild(monsterToSpawn);
}
 
private function spawnWave(currWave)
{
    if (currWave == 1)
    {
        spawnMonster(C.MONSTER_START_X, C.MONSTER_START_Y);
    }
    else if (currWave == 2)
    {
        for (var i = 0; i < 2; i++)
        {
            spawnMonster(C.MONSTER_START_X - 40*i, C.MONSTER_START_Y);
        }
    }
    else if (currWave == 3)
    {
        for (var i = 0; i < 4; i++)
        {
            spawnMonster(C.MONSTER_START_X - 40*i, C.MONSTER_START_Y);
        }
    }
    ...

I wouldn't go much into the function spawnMonster. Suffice to know that it creates a single monster of class Monster where you can specify the x and y position of it.

Examine the function spawnWave. The if-else conditions go on until the maxWave, which is 20, but I'll just highlight 3 of them, since they're essentially the same. Remember that spawnWave is a function that is called with 1 parameter, which is the currWave. For the first wave, therefore, we can see that in line 448, only 1 monster is spawned.

For the second wave, lines 452 to 455 uses a for loop to create a total of 3 monsters. This is where you can potentially change the design to suit your needs. In my tutorial, I only have 1 Monster type, hence I used a for loop. If you have a few different monster types, you can create spawnMonster1, spawnMonster2, etc to create different monsters accordingly.

What's interesting in the modification of the x parameter in line 454. Basically, it just means creating the first monster at the x and y position denoted by C.MONSTER_START_X, C.MONSTER_START_Y. The second monster created needs to be a little farther back from the first one, and the third monster a little farther than the second. Line 454 takes care of that. Look at this picture for a clearer idea.

spawn

I expanded the swf so that you can see parts of the hidden stage. You can see that each monster is spaced a little distance apart. That was what line 454 was trying to do.

Now, let's take a look at what is causing the monsters to move.

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
for (var i=monsters.length - 1; i >= 0; i--)
{
    if (monsters[i].currLife > 0)
    {
        monsters[i].update();
    }
    
    //Check if monster reaches the end of their path
    if (monsters[i].hasReachedDestination())
    {
        monsters[i].gotoAndStop("remove");
        life -= 1;
    }
    
    if (monsters[i].currentLabel == "remove")
    {
        mcGameStage.removeChild(monsters[i]);
        monsters.splice(i,1);
        
        //Award Gold
        currGold += C.MONSTER_GOLD;
    }
}

Line 328 checks that the monster is alive first before allowing it to take action and update. We'll take a look at the monster's update function later.

Line 334 checks if the monster has reached its destination, which is the end of the tower defense map. If so, the monster's frame is told to jump to the label remove. But, when the monster has reached the end of the tower defense map, it means that there is a "leak", hence the player is penalised with a -1 to his life in line 337.

Then line 340 checks for monsters with their timeline head at the "remove" label, and remove them (in lines 342 and 343). Line 346 awards gold to the player EVEN IF the monster has leaked. I did this to better balance the game, so that early leakages do not snowball to the end. If you have time to balance your game otherwise, this line should be removed.

Now, let's look at the Monster.as inside the Game package to see what goes on in the update function.

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public function Monster()
{
    maxLife = C.MONSTER_LIFE;
    currLife = maxLife;
    speed = C.MONSTER_SPEED;
    
    currIndex = 0;
    
    checkPoints = new Array();
    checkPoints.push(new Point(85,140));
    checkPoints.push(new Point(85,320));
    checkPoints.push(new Point(325,320));
    checkPoints.push(new Point(325,200));
    checkPoints.push(new Point(265,200));
    checkPoints.push(new Point(265,80));
    checkPoints.push(new Point(505,80));
    checkPoints.push(new Point(505,380));
    checkPoints.push(new Point(630,380));
}

Let me explain what checkPoints is used for. It is an array that stores the coordinates of the various points in the map where the monster must reach. There is a little bit of manual work here whereby you really need to check the coordinates of the turning points in your map, but once you get it nailed down, it's pretty straightforward. By turning points, I refer to this:

check points

Lines 22 to 30 is basically checking the coordinates of the red circles, and pushing them into the array. The very last point pushed in at line 30 is the final end point where the monster should travel to. Once it reaches that spot, it is considered a leak (player loses 1 life).

The variable currIndex stores where the monster has moved to so far. The monster always travels to the position pointed to by checkpoints[currIndex], so right at the start, the monster will start to move to checkpoints[0], which denotes the position (85,104). We'll examine that in the update function below.

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public function update()
{
    var finalSpeed:Number;
    
    if (slowTimer > 0)
    {
        finalSpeed = speed / 2;
        slowTimer--;
    }
    else
        finalSpeed = speed;
        
    if (currIndex < checkPoints.length)
    {
        //move in the direction of the checkpoint
        if (this.x < checkPoints[currIndex].x)
            this.x += Math.min(finalSpeed, Math.abs(this.x - 
                                        checkPoints[currIndex].x));
        else if (this.x > checkPoints[currIndex].x)
            this.x -= Math.min(finalSpeed, Math.abs(this.x - 
                                        checkPoints[currIndex].x));
            
        if (this.y < checkPoints[currIndex].y)
            this.y += Math.min(finalSpeed, Math.abs(this.y - 
                                        checkPoints[currIndex].y));
        else if (this.y > checkPoints[currIndex].y)
            this.y -= Math.min(finalSpeed, Math.abs(this.y - 
                                        checkPoints[currIndex].y));
        
        if ((this.x == checkPoints[currIndex].x) &&
            (this.y == checkPoints[currIndex].y))
        {
            currIndex += 1;
        }
    }
    
    //display
    if (currLife > 0)
                mcLifeBar.width = Math.floor((currLife/maxLife)*
                                                    C.LIFEBAR_MAX_WIDTH);
    else
        mcLifeBar.width = 0;
}

In almost all tower defense games, there is a tower that can slow the monster's speed, usually by half or more. Lines 35 to 43 takes care of this logic. The way I implement it here in this tutorial, whenever the monster should be slowed, this variable slowTimer is given a number. If it takes on a number of 30, it means for 1 second (since 30 fps), it will be slowed. According to line 39, I will slow it from its original speed down to half.

Line 45 checks if the currIndex is lower than the total length of the checkPoints array, meaning that the monster has yet to reached its final checkpoint yet. The subsequent few lines are basically the same, but let me take line 48 to explain. If the x position of the current monster is lesser than the x position of its next checkpoint, in line 49, its x value will be increased by the minimum of either its eventual speed, or the distance between his current position and the checkpoint.

The reason why this is done is to prevent the Monster from ding-donging around the checkpoint. Imagine a case where the speed of the monster is 10 pixels, and it is currently 5 pixels on the left of its checkpoint. Without this logic, it will move 10 pixels and land 5 pixels on the right of its checkpoint. Then it makes a check again and realises it is now on the right, hence it moves 10 pixels to the left. And alas, now it is 5 pixels on the left again. Thus, he will never reach the checkpoint if this holds true.

In line 62, once we ascertain that the monster has reached its checkpoint, the currIndex value is increased by 1, thus prompting the monster to seek out its next checkpoint.

Lines 70 to 74 takes care of the health bar display in each monster. currLife / maxLife gives a percentage of its life left. When multiplied by the width of the life bar, it gives a full health bar when it is at max life, and 0 pixels wide when its health is 0.

Back to GameController.as. Moving on, we move onwards to update the towers. Each of the towers is stored inside this array called towers, so we'll be calling their own individual update function.

350
351
352
353
354
//Update all the towers
for (var i in towers)
{
    towers[i].update();
}

I separated the logic for the three towers into Tower_Fire.as, Tower_Ice.as and Tower_Lightning.as. Actually, we should have used inheritance to simplify and optimise things. But, again, for the sake of easy explanation, I'm going to break them up. Let's examine Tower_Fire since it's the most direct attacking tower.

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public function update()
{
    if (isActive)
    {
        var monsters = GameController(root).monsters;
        if (cooldown <= 0)
        {
            for (var j = 0; j < monsters.length; j++)
            {
                var currMonster = monsters[j];
                
                if ((Math.pow((currMonster.x - this.x),2) 
                    + Math.pow((currMonster.y - this.y),2)) < this.range)
                {
                    //spawn new bullet
                    var bulletRotation = (180/Math.PI)*
                                Math.atan2((currMonster.y - this.y), 
                                        (currMonster.x - this.x));
                    var newBullet = new Bullet(currMonster.x,currMonster.y,
                            "fire",currMonster,this.damage,bulletRotation);
                    newBullet.x = this.x;
                    newBullet.y = this.y;
                    
                    GameController(root).bullets.push(newBullet);
                    GameController(root).mcGameStage.addChild(newBullet);
                    
                    this.cooldown = C.TOWER_FIRE_COOLDOWN;
                    
                    break;
                }
            }
        }
        else
        {
            this.cooldown -= 1;
        }
    }
}

There're quite a lot of things here to cover, so let's go over it slowly. Line 28 first checks whether this tower is currently active and acts only if it is. Remember that when you click on the tower building buttons initially, a tower is already created, but its default state is inactive. That's why it doesn't do any shooting as yet.

Once it is placed onto the game stage, its isActive boolean is set to true, and all the logic here will kick in. Each of the tower has a cooldown class variable that stores the amount of time it needs before it shoots again. This is necessary so that we don't see it shooting 30 missiles per second (on a 30fps basis). So line 31 ensures that it is time to act.

It retrieves the monster array from the game controller in line 30 (remember that these arrays have been set to public). The monsters within the array are looped, and lines 37 and 38 is our famous geometrical formula between 2 points. Well ... almost, with an optimisation. You can see that in lines 37 and 38, we take the (square of the x distance) + (square of the y distance) (let me denote this as dist_tower_to_monster) and compare it to the range directly. Our mathematical memory should tell us that distance between two points is the square root of dist_tower_to_monster.

Well, this is an optimisation, like I said. If I want the range of a tower to be 10 pixels, we could take the square root of dist_tower_to_monster and compare it to 10. Or, to optimise it, I can save on the time taken to compute the square root, and compare it directly to the square of 10, which is 100. In my tutorial, I chose the latter, that's why you see the ranges listed in the C.as as being so large.

Moving on, if the monster is within range, the if condition holds true, and we proceed to spawn a new bullet. Line 41 calculates the rotation of the bullet. Yes, I know it's mathsy ... but hey, you can't code games without a firm mathematical background. =) That's one of the reasons why Digipen emphasizes so much on Maths.

Line 44 creates a new bullet, passing in all the necessary parameters, which we'll go into later when we examine the Bullet.as class. Lines 49 and 50 pushes this new bullet into the bullets array which handles them, and adds it onto the game stage so that we can see it.

Line 54 breaks the current loop so that a tower can only fire a bullet at a monster. An easy modification can be done here if you want the tower to target and fire a bullet at all monsters within range. Simply remove the break in line 54.

Line 60 constantly reduces the cooldown of the tower so that it gets ready to fire again.

If you open up Tower_Ice.as and Tower_Lightning.as, you'll realise that the codes are almost identical. Except that you'll find the spawning of bullet in line 45 to reflect "ice" and "lightning" respectively. Again, like I said, this is where you can easily use inheritance to simplify your codes.

Now, we move on to the code for the bullets.

356
357
358
359
360
361
362
363
364
365
366
//Update all the bullets
for (var i=bullets.length - 1; i >= 0; i--)
{				
    bullets[i].update();
    
    if (bullets[i].currentLabel == "remove")
    {
        mcGameStage.removeChild(bullets[i]);
        bullets.splice(i,1);
    }
}

The codes to do the updating of the bullets look very similar, and it's basically looping through the entire bullets array and calling them to update. In line 361, we check if any of the bullets is currently at their "remove" label frame, and if so, they are removed. You would noticed that this is a technique I use frequently to remove things from stage.

Now, we need to examine what goes on in the update function of the bullet. Open up the Bullet.as file.

12
13
14
15
16
17
18
19
20
21
public function Bullet(targetX,targetY,type,targetRef,damage,rotate)
{
    this.targetX = targetX;
    this.targetY = targetY;
    this.gotoAndStop(type);
    this.targetRef = targetRef;
    this.damage = damage;
    this.rotation = rotate;
    this.speed = C.BULLET_SPEED;
}

The constructor for the Bullet looks normal, but examine line 16. Remember that when each of the towers created a bullet, the difference lies in passing in "fire", "ice" or "lightning" mainly. This is where the significance shows. If you open up the Bullet Movieclip in the FLA file, you'll see that it looks like this.

bullet mc

I'm basically trying to reuse the same movieclip for all the bullets. In a more complex environment where you will have lots of animation for each bullet, this style may not work for you. For now, it is ok.

Line 19 is important too in the sense that the bullet is rotated to face the target where it is supposed to reach. This has to be done so that it looks natural, and also to ensure that it moves in the correct direction. As you'll realise later, this rotation is part of the movement logic.

Now, let's take a look at the update function.

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public function update()
{
    this.x += this.speed * Math.cos(Math.PI * this.rotation/180);
    this.y += this.speed * Math.sin(Math.PI * this.rotation/180);
    
    if (this.hitTestPoint(targetX,targetY))
    {
        //if this is a lightning bullet, 
        //it will damage all nearby monsters
        if (this.currentLabel == "lightning")
        {
            var monsters = GameController(root).monsters;
            for (var i = 0; i < monsters.length; i++)
            {
                var currMonster = monsters[i];
                
                if ((Math.pow((currMonster.x - this.x),2) 
                        + Math.pow((currMonster.y - this.y),2)) 
                            < C.LIGHTNING_AOE_RANGE)
                {
                    currMonster.takeDamage(this.damage);
                }
            }
        }
        else if (this.currentLabel == "ice")
        {
            //Add Slow Effect to monster
            this.targetRef.slowDown(C.TOWER_ICE_SLOW_AMOUNT);
            this.targetRef.takeDamage(this.damage);
        }
        else
        {
            this.targetRef.takeDamage(this.damage);
        }
        
        this.gotoAndStop("remove");
    }
}

Line 25 and 26 moves the bullet in the x and y axis based on resolving the x and y component of its speed and rotation. Again, this is pretty mathsy, but recall your trigonometry and it should make sense.

trigo

I hope the picture above jolts back some of your memory from your maths lessons. =)

Line 28 indicates the stop condition for the bullet. It is always intended to move only to a point (where the monster was when it fired). This point is indicated by targetX and targetY. Once the hitTest holds true, it means that it has reached.

The code within the if condition is interesting. We check whether the bullet is a lightning bullet, meaning it fired from the Lightning tower. If so, remember that the lightning tower can actually damage nearby enemies of its target. That's why in lines 34 to 41, you see the code that loops through all the monsters on stage again, and check whether they are in the lightning AOE range. If so, they take damage too.

Line 47 handle the ice bullets, and adds in a freeze effect to the monster that gets hit by it in line 50.

Line 55 is just the straightforward damage for the Fire Tower.

That handles the most difficult-to-understand parts of this code. You'll realise that this way of handling bullet movements is very useful, and can be used for a wide variety of games.

Now, let's carry on with the game logic. Back to your GameController.as.

368
369
370
371
372
373
374
375
376
377
378
379
//Check for end game
if (life <= 0)
{
    gameOver();
    
    //stop all subsequent code in this update to run
    return;
}
else if ((currWave == maxWave) && (monsters.length == 0))
{
    gameWin();
}

Naturally, I placed such frame changing codes towards the end of the game loop. If the player's life is 0 or less, we execute the gameOver() function, which does the removal of event listeners, etc, and most importantly, bring the player to the frames labelled "gameOver".

Otherwise, if the currWave is the same as maxWave and there isn't any more monsters left in the game, then the gameWin() function is called, and the player is brought to the "gameWin" frames, after all the usual removal of event listeners. Removing and managing event listeners as you jump across frames is VERY important, so pay good emphasis here.

Step 6 - The Game Loop, Handling Display

Finally, when handling the display for this game, we just need to take care of displaying the life , gold and the current wave to the player.

381
382
383
384
385
386
387
//******************
//Handle Display
//******************			
//Display new Score
mcGameUI.txtLife.text = String(life);
mcGameUI.txtGold.text = String(currGold);
mcGameUI.txtWave.text = String(currWave) + " / " + String(maxWave);

That about sums up all the key concepts in this tutorial.

Download the Game Files

The files you need to create this game can be downloaded from here.

*UPDATED* If you want a copy of the Tower Defense with a Boss at the end of the waves, download it from here.

The Game

And here you have it, your very own Tower Defense game. Enjoy! Do share with me if you've used this tutorial to create some Tower Defense maps!




How To Play

Objective: Stop the advancing waves of monsters by placing towers at strategic points.

Controls: Click on the three tower icons below and place the tower on the map.


Content on this page requires a newer version of Adobe Flash Player.

Get Adobe Flash player


Flash Resources
Preloader FPS Display Sounds & Music
Keycodes Name Generator
Game Development Resources
Sprite Sheets