Advertisement

Make a Tower Defense Game in AS3: Enemies and Basic AI

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

Hey Flash Developers, welcome to the second part of my Tower Defense Game tutorial. In the first part, we developed the basic mechanism of creating turrets and making them shoot towards the point of mouse click. But that's not what turrets are for! In this part we'll extend the game to include enemies, basic artificial intelligence (AI) in turrets, and some more game elements. Are you ready?


Final Result Preview

This is the game we are going to create in this tutorial:

Click the orange circles to place turrets. The red circles are enemies, and the number on each represents its hit points.


Step 1: Recap

In the previous tutorial we developed a game which had placeholders for the turrets. We could deploy turrets by clicking those placeholders, and the turrets aimed at the mouse pointer and shot bullets towards the point where the user clicked.

We finished with a Main class which had the game loop and game logic. Apart from that we had the Turret class which had nothing much except the update function that made the turret rotate.


Step 2: A Separate Bullet Class

We previously created the bullets in Main class and attached an ENTER_FRAME listener to move it. The bullet did not have enough properties earlier to consider it a making a separate class. But in such a game bullets can have many varieties like speed, damage, and so on, so it is a good idea to pull out the bullet code and encapsulate it in a separate Bullet class. Let's do it.

Create a new class called Bullet, extending the Sprite class. The basic code for this class should be:

 package  
 {
	import flash.display.Sprite;
	
	public class Bullet extends Sprite 
	{
		
		public function Bullet() {			
		}
	}
}

Next we put the code to draw the bullet graphic, taken from Main, in Bullet. As we did with the Turret class, we create a function called draw in the Bullet class:

private function draw():void {
	var g:Graphics = this.graphics;
	g.beginFill(0xEEEEEE);
	g.drawCircle(0, 0, 5);
	g.endFill();
}

And we call this function from the Bullet constructor:

public function Bullet() {
	draw();
}

Now we add some properties to the bullet. Add four variables: speed, speed_x, speed_y and damage, before the Bullet constructor:

private var speed:Number;
private var speed_x: Number;
private var speed_y:Number;		
public var damage:int;

What are these variables for?

  • speed: This variable stores the speed of the bullet.
  • speed_x and speed_y: These store the x and y components of the speed, respectively, so that the calculation of breaking the speed into its components does not have to be done again and again.
  • damage: This is the amount of damage the bullet can do to an enemy. We keep this variable public as we will require this in our game loop in the Main class.

We initialize these variables in the constructor. Update your Bullet constructor:

public function Bullet(angle:Number) {
	speed = 5;
	damage = 1;
	speed_x = Math.cos(angle * Math.PI / 180) * speed;
	speed_y = Math.sin(angle * Math.PI / 180) * speed;
	draw();
}

Notice the angle variable we receive in the constructor. This is the direction (in degrees) in which the bullet will move. We just break the speed into its x and y components and cache them for future use.

The last thing that remains in the Bullet class is to have an update function that will be called from the game loop to update (move) the bullet. Add the following function at the end of the Bullet class:

public function update():void {
	x += speed_x;
	y += speed_y;
}

Bingo! We are done with our Bullet class.


Step 3: Updating the Main Class

We moved a lot of bullet code from Main class to its own Bullet class, so a lot of code remains unused in Main and much needs to be updated.

First, delete the createBullet() and moveBullet() functions. Also remove the bullet_speed variable.

Next, go to the shoot function and update it with the following code:

private function shoot(e:MouseEvent):void {
	for each(var turret:Turret in turrets) {
		var new_bullet:Bullet = new Bullet(turret.rotation);
		new_bullet.x = turret.x + Math.cos(turret.rotation * Math.PI / 180) * 25;
		new_bullet.y = turret.y + Math.sin(turret.rotation * Math.PI / 180) * 25;  
		addChild(new_bullet);
	}
}

We no longer use the createBullet function to create bullet rather use the Bullet constructor and pass the turret's rotation to it which is the direction of the bullet's movement and so we don't need to store it in the bullet's rotation property as we did earlier. Also we don't attach any listener to the bullet as the bullet will be updated from within the game loop next.


Step 4: Saving the Bullet References

Now that we need to update the bullets from the game loop, we need a reference of them to be stored somewhere. The solution is the same as for the turrets: create a new Array named bullets and push the bullets onto it as they are created.

First declare an array just below the turrets array declaration:

private var ghost_turret:Turret;
private var turrets:Array = [];
private var bullets:Array = [];

Now to populate this array. We do so whenever we create a new bullet - so, in the shoot function. Add the following just before adding the bullet to the stage:

var new_bullet:Bullet = new Bullet(turret.rotation);
new_bullet.x = turret.x + Math.cos(turret.rotation * Math.PI / 180) * 25;
new_bullet.y = turret.y + Math.sin(turret.rotation * Math.PI / 180) * 25;  
bullets.push(new_bullet);
addChild(new_bullet);

Step 5: Update the Bullets

Just like how we update the turrets the game loop, we will update the bullets, too. But this time, instead of using a for...each loop, we'll use a basic for loop. Before this, we must add two variables to the top of the game loop, so that we know which variables are used within the game loop and can set them free for garbage collection.

var turret:Turret;
var bullet:Bullet;

Go ahead and add the following code at the end of game loop:

for (var i:int = bullets.length - 1; i >= 0; i--) {
	bullet = bullets[i];
	if (!bullet) continue;
	bullet.update();
}

Here we traverse over all the bullets on the stage every frame and call their update function which makes them move. Note here that we iterate the bullets array in reverse. Why? We'll see this ahead.

Now that we have a turret variable declared outside already, we don't need to declare it again inside the for...each loop of turrets. Modify it to:

for each(turret in turrets) {
	turret.update();
}

Finally we add the boundary check condition; this was previously in the bullet's ENTER_FRAME but now we check it in the game loop:

if (bullet.x < 0 || bullet.x > stage.stageWidth || bullet.y < 0 || bullet.y > stage.stageHeight) {
	bullets.splice(i, 1); 
	bullet.parent.removeChild(bullet);
	continue;
}

We check whether the bullet is out of the stage's boundary, and if so we first remove its reference from the bullets array using the splice function, and then remove the bullet from the stage and continue with the next iteration. This is how your game loop should look:

private function gameLoop(e:Event):void {
	var turret:Turret;
	var bullet:Bullet;
	
	for each(turret in turrets) {
		turret.update();
	}
	
	for (var i:int = bullets.length - 1; i >= 0; i--) {
		bullet = bullets[i];
		if (!bullet) continue;
		bullet.update();
	}
}

If you now run the game, you should have the same functionality as in Part 1, with code that is much more clean and organized.


Step 6: Presenting the Enemy

Now we add one of the most important elements of the game: the Enemy. First thing is to create a new class named Enemy extending the Sprite class:

package  
{
	import flash.display.Sprite;

	public class Enemy extends Sprite 
	{
		public function Enemy() {			
		}		
	}
}

Now we add some properties to the class. Add them before your Enemy constructor:

private var speed_x:Number;
private var speed_y:Number;

We initialize these variables in the Enemy constructor:

public function Enemy() 
{
	speed_x = -1.5;
	speed_y = 0;			
}

Next we create the draw and update functions for the Enemy class. These are very similar to the ones from Bullet. Add the following code:

private function draw():void {
	var g:Graphics = this.graphics;
	g.beginFill(0xff3333);
	g.drawCircle(0, 0, 15);
	g.endFill();
}

public function update():void {
	x += speed_x;
	y += speed_y;
}

Step 7: Timing the Game Events

In our game we need to have many events that take place at certain times or repeatedly at certain intervals. Such timing can be achieved using a time counter. The counter is just a variable that gets incremented as the time passes in the game. The important thing here is when and by how much amount to increment the counter. There are two ways in which timing is generally done in games: Time based and Frame based.

The difference is that the unit of step in time based game is based on real time (i.e. number of milliseconds passed), but in a frame based game, the unit of step is based on frame units (i.e. the number of frames passed).

For our game we are going to use a frame based counter. We'll have a counter which we'll increment by one in the game loop, which runs each frame, and so will basically give us the number of frames which have passed since the game started. Go ahead and declare a variable after the other variable declarations in the Main class:

private var ghost_turret:Turret;
private var turrets:Array = [];
private var bullets:Array = [];
private var global_time:Number = 0;

We increment this variable in the game loop at the top:

global_time++;

Now based on this counter we can do stuff like creating enemies, which we'll do next.


Step 8: Let's Create Some Enemies

What we want to do now is create enemies on the field after every two seconds. But we are dealing with frames here, remember? So after how many frames should we create enemies? Well, our game is running at 30 FPS, thus incrementing the global_time counter 30 times each second. A simple calculation tells us that 3 seconds = 90 frames.

At the end of the game loop add the following if block:

if (global_time % 90 == 0) {
}

What is that condition about? We use the modulo (%) operator, which gives the remainder of a division - so global_time % 90 gives us the remainder when global_time is divided by 90. We check whether the remainder is 0, as this will only be the case when global_time is a multiple of 90 - that is, the condition returns true when global_time equals 0, 90, 180 and so on... This way, we achieve a trigger at every 90 frames or 3 seconds.

Before we create the enemy, declare another array called enemies just below the turrets and bullets array. This will be used to store references to enemies on the stage.

private var ghost_turret:Turret;
private var turrets:Array = [];
private var bullets:Array = [];
private var enemies:Array = [];
private var global_time:Number = 0;

Also declare an enemy variable at the top of the game loop:

global_time++;
var turret:Turret;
var bullet:Bullet;
var enemy:Enemy;

Finally add the following code inside the if block we created earlier:

enemy = new Enemy();
enemy.x = 410;
enemy.y = 30 + Math.random() * 370;
enemies.push(enemy);
addChild(enemy);

Here we create a new enemy, position it randomly at the right of the stage, push it in the enemies array and add it to the stage.


Step 9: Updating the Enemies

Just like we update the bullets in the game loop, we update the enemies. Put the following code below the turret for...each loop:

for (var j:int = enemies.length - 1; j >= 0; j--) {
	enemy = enemies[j];
	enemy.update();
	if (enemy.x < 0) {
		enemies.splice(j, 1); 
		enemy.parent.removeChild(enemy);
		continue;
	}
}

Just like we did a boundary check for bullets, we check for enemies too. But for enemies we just check whether they went out of the left side of the stage, as they only move right-to-left. You should see enemies coming from the right if you run the game now.


Step 10: Give the Enemies Some Health

Every enemy has some life/health and so will ours. We will also show the remaining health on the enemies. Lets declare some variables in the Enemy class for the health stuff:

private var health_txt:TextField;
private var health:int;
private var speed_x:Number;
private var speed_y:Number;

We initialise the health variable in the constructor next. Add the following to the Enemy constructor:

health = 2;

Now we initialize the health text variable to show on the center of enemy. We do so in the draw function:

health_txt = new TextField();
health_txt.height = 20; health_txt.width = 15;
health_txt.textColor = 0xffffff;
health_txt.x = -5;
health_txt.y = -8;
health_txt.text = health + "";
addChild(health_txt);

All we do is create a new TextField, set its color, position it and set its text to the current value of health Finally we add a function to update the enemy's health:

public function updateHealth(amount:int):int {
	health += amount;
	health_txt.text = health + "";
	return health;
}

The function accepts an integer to add to the health, updates the health text, and returns the final health. We'll call this function from our game loop to update each enemy's health and detect whether it's still alive.


Step 11: Shooting the Enemies.

First lets modify our shoot function a bit. Replace the existing shoot function with the folowing:

private function shoot(turret:Turret, enemy:Enemy):void {
	var angle:Number = Math.atan2(enemy.y - turret.y, enemy.x - turret.x) / Math.PI * 180; 
	turret.rotation = angle;
	var new_bullet:Bullet = new Bullet(angle);
	new_bullet.x = turret.x + Math.cos(turret.rotation * Math.PI / 180) * 25;
	new_bullet.y = turret.y + Math.sin(turret.rotation * Math.PI / 180) * 25;
	bullets.push(new_bullet);
	addChild(new_bullet);	
}

The shoot function now accept two parameters. The first is a reference to a turret which will do the shooting; the second is a reference to a enemy towards which it will shoot.

The new code here is similar to the one present in the Turret class's update function, but instead of the mouse's position we now use the enemy's cordinates. So now you can remove all the code from the update function of the Turret class.

Now how to make the turrets shoot at enemies? Well the logic is simple for our game. We make all the turrets shoot the first enemy in the enemies array. What? Lets put some code and then try to understand. Add up following lines in the end of the for...each loop used to update the turrets:

for each(turret in turrets) {
	turret.update();
	for each(enemy in enemies) {
		shoot(turret, enemy);
		break;
	}
}

For every turret we now update it, then iterate the enemies array, shoot the first enemy in the array and break from the loop. So essentially each turret shoots at the earliest created enemy as it is always at the beginning of the array. Try running the game and you should see turrets shooting the enemies.

But wait, what's that bullet stream flowing? Looks like they are shooting too fast. Lets see why.


Step 12: Turrets Are Shooting Too Fast

As we know, the game loop runs every frame i.e. 30 times a second in our case, so the shooting statement we added in the previous step gets called at the speed of our game loop and hence we see a stream of bullets flowing. Looks like we need a timing mechanism inside the turrets too. Switch over to the Turret class and add the following code:

private var local_time:Number = 0;
private var reload_time:int;
  1. local_time: Our counter is called local_time in contrast to the global_time in the Main class. This is for two reasons: first, because this variable is local to the Turret class; second, because it doesn't always go forward like our global_time variable - it will reset many times during the course of the game.
  2. reload_time: This is the time required by the turret to reload after shooting a bullets. Basically its the time difference between two bullet shoots by a turret. Remember all time units in our game are in terms of frames.

Increment the local_time variable in the update function and initialize the reload_time in the constructor:

public function update():void {
	local_time++;
}
public function Turret() {
	reload_time = 30;
	draw();
}

Next add the following two functions at the end of the Turret class:

public function isReady():Boolean {
	return local_time > reload_time;
}

public function reset():void {
	local_time = 0;
}

isReady returns true only when the current local_time is greater than the reload_time, i.e. when the turret has reloaded. And the reset function simply resets the local_time variable, to start it reloading again.

Now back in the Main class, modify the shoot code in the game loop we added in the previous step to the following:

for each(turret in turrets) {
	turret.update();
	if (!turret.isReady()) continue;
	for each(enemy in enemies) {
		shoot(turret, enemy);
		turret.reset();
		break;
	}
}

So if now the turret isn't ready (isReady() returns false), we continue with the next iteration of the turret loop. You will see that the turrets fire at an interval of 30 frames or 1 second now. Cool!


Step 13: Limit the Turret Range

Still something not right. The turrets shoot at enemies irrespective of the distance between them. What's missing here is the range of a turret. Each turret should have its own range inside which its can shoot an enemy. Add another variable to the Turret class called range and set it to 120 inside the constructor:

private var reload_time:int;
private var local_time:Number = 0;
private var range:int;
public function Turret() {
	reload_time = 30;
	range = 120;
	draw();
}

Also add a function called canShoot at the end of the class:

public function canShoot(enemy:Enemy):Boolean {
	var dx:Number = enemy.x - x;
	var dy:Number = enemy.y - y;
	if (Math.sqrt(dx * dx + dy * dy) <= range) return true;
	else return false;
}

Every turret can shoot an enemy only when it meets certain criteria - for example, you could let the turret shoot only red enemies with less than half their life and not more than 30px away. All such logic to determine whether the turret is able to shoot an enemy or not will go in the canShoot function, which returns true or false according to the logic.

Our logic is simple. If the enemy is within the range return true; otherwise return false. So when the distance between the turret and enemy (Math.sqrt(dx * dx + dy * dy)) is less than or equal to range, it returns true. A little more modification in the shoot section of the game loop:

for each(turret in turrets) {
	turret.update();
	if (!turret.isReady()) continue;
	for each(enemy in enemies) {
		if (turret.canShoot(enemy)) {
			shoot(turret, enemy);
			turret.reset();
			break;
		}
	}
}

Now only if the enemy is within the range of the turret, will the turret shoot.


Step 14: Collision Detection

A very important part of every game is the collision detection. In our game collision check is done between bullets and enemies. We will be adding the collision detection code inside the for...each loop which updates the bullets in the game loop.

The logic is simple. For every bullet we traverse the enemies array and check if there's a collision between them. If so, we remove the bullet, update the enemy health and break out of the loop to check other enemies. Let's add some code:

for (i = bullets.length - 1; i >= 0; i--) {
	bullet = bullets[i];
	// if the bullet isn't defined, continue with the next iteration
	if (!bullet) continue;
	bullet.update();
	if (bullet.x < 0 || bullet.x > stage.stageWidth || bullet.y < 0 || bullet.y > stage.stageHeight) {
		bullets.splice(i, 1); 
		bullet.parent.removeChild(bullet);
		continue;
	}
	
	for (var k:int = enemies.length - 1; k >= 0; k--) {
		enemy = enemies[k];
		if (bullet.hitTestObject(enemy)) {
			bullets.splice(i, 1);
			bullet.parent.removeChild(bullet);
			if (enemy.updateHealth(-1) == 0) {
				enemies.splice(k, 1);
				enemy.parent.removeChild(enemy);
			}
			break;
		}
	}
}

We use ActionScript's hitTestObject function to check for collision between the bullet and enemy. If the collsion occurs, the bullet is removed in the same way as when it leaves the stage. The enemy's health is then updated using the updateHealth method, to which bullet's damage property is passed. If the updateHealth function returns an integer less than or equal to 0, this means the enemy is dead and so we remove it in the same way as the bullet.

And our collision detection is done!


Step 15: Why Reverse the "For" Loops?

Remember that we traverse the enemies and bullets in reverse in our game loop. Let's understand why. Let suppose we used an ascending for loop. We are on index i=3 and we remove a bullet from the array. On removal of the item at position 3, its space is filled by the item then at position 4. So now the item previously at position 4 is at 3. After the iteration i increments by 1 and becomes 4 and so item at position 4 is checked.

Oops, you see what happened just now? We just missed the item now at position 3 which shifted back as the result of splicing. And so we use a reverse for loop which removes this problem. You can see why.


Step 16: Displaying the Turret's Range

Let's add some extra stuff to make the game look good. We'll add functionality to display a turret's range when the mouse is hovered on it. Switch over to the Turret class and add some variables to it:

private var range:int;
private var reload_time:int;
private var local_time:Number = 0;
private var body:Sprite;
private var range_circle:Sprite;

Next update the draw function to the following:

private function draw():void {
	range_circle = new Sprite();
	g = range_circle.graphics;
	g.beginFill(0x00D700);
	g.drawCircle(0, 0, range);
	g.endFill();
	range_circle.alpha = 0.2;
	range_circle.visible = false;
	addChild(range_circle);
	
	body = new Sprite();
	var g:Graphics = body.graphics;
	g.beginFill(0xD7D700);
	g.drawCircle(0, 0, 20);
	g.beginFill(0x800000);
	g.drawRect(0, -5, 25, 10);
	g.endFill();
	addChild(body);	
}

We break the graphics of the turret into two parts: the body and the range graphic. We do this so as to give an ordering to the different parts of the turret. Here we require the range_circle to be behind the turret's body, and so we add it first to the stage. Finally, we add two mouse listeners to toggle the range graphic:

private function onMouseOver(e:MouseEvent):void {
	range_circle.visible = true;
}

private function onMouseOut(e:MouseEvent):void {
	range_circle.visible = false;
}

Now attach the listeners to the respective events at the end of the constructor:

body.addEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
body.addEventListener(MouseEvent.MOUSE_OUT, onMouseOut);

If you run the game and try to deploy a turret, you will see a flickering when hovering on the placeholders. Why is that?



See the flicker?

Step 17: Removing the Flicker

Remember we set the mouseEnabled property of the ghost turret to false? We did that because, the ghost turret was capturing mouse events by coming in between the mouse and the placeholder. The same situation has arrived again as the turret itself has two children now - its body and the range sprite - which are capturing the mouse events in between.

The solution is the same. We can set their individual mouseEnabled properties to false. But a better solution is to set the ghost turret's mouseChildren property to false. What this does is restrict all the children of ghost turret from receiving mouse events. Neat, huh? Go ahead and set it to false in the Main constructor:

ghost_turret = new Turret();
ghost_turret.alpha = 0.5;
ghost_turret.mouseEnabled = false;
ghost_turret.mouseChildren = false;
ghost_turret.visible = false;
addChild(ghost_turret);

Problem solved.

Step 18: What Next?

We could extend this demo to include much more advanced features and turn it into a playable game. Some of which could be:

  1. Better AI logic for selecting and shooting enemies.
  2. Different type of turrets, bullets and enemies in the game.
  3. Complex enemy paths instead of straight lines.

Let's see what you can come up with from this basic demo. I'll be glad to hear about you tower defense games, and your comments or suggestions for the series.

Advertisement