Warning: Declaration of thesis_comment::start_lvl(&$output, $depth, $args) should be compatible with Walker::start_lvl(&$output, $depth = 0, $args = Array) in /nfs/c03/h08/mnt/50298/domains/gamedev.michaeljameswilliams.com/html/wp-content/themes/thesis_18/lib/classes/comments.php on line 0

Warning: Declaration of thesis_comment::end_lvl(&$output, $depth, $args) should be compatible with Walker::end_lvl(&$output, $depth = 0, $args = Array) in /nfs/c03/h08/mnt/50298/domains/gamedev.michaeljameswilliams.com/html/wp-content/themes/thesis_18/lib/classes/comments.php on line 0

Warning: Declaration of thesis_comment::start_el(&$output, $comment, $depth, $args) should be compatible with Walker::start_el(&$output, $object, $depth = 0, $args = Array, $current_object_id = 0) in /nfs/c03/h08/mnt/50298/domains/gamedev.michaeljameswilliams.com/html/wp-content/themes/thesis_18/lib/classes/comments.php on line 0

Warning: Declaration of thesis_comment::end_el(&$output, $comment, $depth, $args) should be compatible with Walker::end_el(&$output, $object, $depth = 0, $args = Array) in /nfs/c03/h08/mnt/50298/domains/gamedev.michaeljameswilliams.com/html/wp-content/themes/thesis_18/lib/classes/comments.php on line 0
AS3 Avoider Game Tutorial, Part 2: Multiple Enemies — Michael James Williams

AS3 Avoider Game Tutorial, Part 2: Multiple Enemies

by Michael James Williams on September 22, 2008 · 185 comments

in Avoider Game Base,Tutorial

*(This tutorial is also available in Spanish, Polish, and Italian.)*

###Introduction

In this part of my AS3 conversion of Frozen Haddock‘s tutorial, I’ll show you how to add multiple enemies to your game. Check out what you’ll be working towards by clicking the image below.

screenshot

###Setting Up

If you didn’t follow along with the first part of the tutorial, grab the zip file here and extract it to your hard drive, making sure it keeps the folder paths intact.

If you did follow the first part, I recommend making a backup of your existing game files by copying the main folder (I called mine *AvoiderGame-MJW*, remember) and naming the copy something like *AvoiderGame-MJW-part1*. This way, you can look back at the early game in the future and see how much you’ve progressed!

Either way, open the FLA file. Open the document class file (mine’s called *AvoiderGame.as*) and the *Enemy.as* file too, since we’re going to be editing them a lot.

###Taking Control of the Enemy

Take another look at the Enemy’s constructor (it’s located in the *Enemy.as* file):

		public function Enemy() 
		{
			x = 100;
			y = -15;
		}

This little bit of code means that every single instance of the Enemy class will start off at (100,-15). This will not make for a very interesting Avoider game. It’s also not going to be any use when we start making enemies that move upwards — if they start above the screen, they’ll never make it onto the playing field!

Change the constructor function like so:

public function Enemy( startX:Number, startY:Number ) 
{
	x = startX;
	y = startY;
}

We’ve added *parameters* to the constructor function. Now when we create an instance of Enemy in the document class, we can pass x and y values to it to specify where it should appear.

Switch to your document class (that’s *AvoiderGame.as*) and change the code that creates a new enemy from:

enemy = new Enemy();

to:

enemy = new Enemy( 100, -15 );

If you run the game (*Control > Test Movie*, remember), you’ll see that the enemy starts at (100,-15) — which is exactly as we’d expect. We’ve passed the values 100 and -30 to the Enemy constructor, which has first set *startX = 100* and *startY = -15*, and then set *x = startX* and *y = startY*. Of course you can change the numbers in Enemy(100,-15) to make it start out in different places. Try it out.

###Irresponsible Cloning

How can we make more than one enemy appear? Let me show you an example of what **not** to do. It’s the simplest method: we literally copy and paste the enemy until we have as many as we need.

First, define a few more enemies as public vars in your document class (lines 10-12):

public class AvoiderGame extends MovieClip 
{
	public var enemy:Enemy;
	public var eric:Enemy;
	public var ernie:Enemy;
	public var emily:Enemy;
	public var avatar:Avatar;
	public var gameTimer:Timer;

Next, set these up with different starting positions in the document class’s constructor, and add (using .addChild) them to the game (lines 20-25):

public function AvoiderGame() 
{
	enemy = new Enemy( 100, -15 );
	addChild( enemy );
	eric = new Enemy( 160, -120 );
	addChild( eric );
	ernie = new Enemy( 205, -60 );
	addChild( ernie );
	emily = new Enemy( 317, -85 );
	addChild( emily );		

Next, in your *onTick* function, get them all moving:

public function onTick( timerEvent:TimerEvent ):void 
{
	enemy.moveDownABit();
	eric.moveDownABit();
	ernie.moveDownABit();
	emily.moveDownABit();
		

Finally, check each of them for a collision with the player:

if ( avatar.hitTestObject( enemy ) ) 
{
	gameTimer.stop();
}			
if ( avatar.hitTestObject( eric ) ) 
{
	gameTimer.stop();
}			
if ( avatar.hitTestObject( ernie ) ) 
{
	gameTimer.stop();
}			
if ( avatar.hitTestObject( emily ) ) 
{
	gameTimer.stop();
}

Run the game. Sure, it works, but it’s a pretty **very** messy solution. Go ahead and undo all of that copying and pasting, it’s unnecessary.

###Forming an Army

Copying and pasting enemies like that lets us refer to each one by a specific name. But… do we ever actually need to do that? Is there ever a situation where we need to tell just one enemy to act differently to the rest? Only when something happens to that enemy — when it hits the player, perhaps, or when it leaves the screen — but in those cases we single the enemy out by its **situation**, not by its name. I’ll show you what I mean in a bit.

What we’re going to do is create a group of enemies, and use the document class as a “general”, telling the group how to behave. I’m going to call this group *army*, for obvious reasons. We’ll use an *Array* as the structure for the group; this is basically an ordered list of objects.

First we need to define the new army, just as we did for the enemy. It’s going to have to be accessible for the duration of the game, so we’ll define it within the document class but outside the constructor (line 7):

public class AvoiderGame extends MovieClip 
{
	public var army:Array;
	public var enemy:Enemy;
	public var avatar:Avatar;
	public var gameTimer:Timer;

Also, remove that line, *public var enemy:Enemy;* (line 10 above). Since the enemies are all going to be part of the army, and the army is a public var, we don’t need the enemies to be public vars any more.

Now, alter the game’s constructor like so:

public function AvoiderGame() 
{
	army = new Array();
	var newEnemy = new Enemy( 100, -15 );
	army.push( newEnemy );
	addChild( newEnemy );

What have we done?

– **Line 16** — Set the army as a *new* instance of Array, just as we’ve done for the enemy.
– **Line 17** — So as not to get confused, I’ve renamed *enemy* to *newEnemy*, as it will now only be used for creating new enemies. Since there’s no *var newEnemy* statement (*public* or otherwise) anywhere, we need to stick *var* on the front here.
– **Line 18** — Added the new enemy to the army. (“Pushed” it onto the end of the list of enemies.)
– **Line 19** — added the new enemy to the game (all that’s changed here is that *enemy* has been renamed *newEnemy*).

Now we need to make the enemy move. Remember this is done in the onTick function. We’re going to need a for each…in loop, which is new to ActionScript 3.

Edit the onTick function so that:

public function onTick( timerEvent:TimerEvent ):void 
{
	enemy.moveDownABit();
	avatar.x = mouseX;
	avatar.y = mouseY;

becomes:

public function onTick( timerEvent:TimerEvent ):void 
{
	for each ( var enemy:Enemy in army ) 
	{
		enemy.moveDownABit();
	}
	avatar.x = mouseX;
	avatar.y = mouseY;

I think it’s actually quite clear what’s happening here. The *for each…in* loop goes through everything in the army’s list of items and tells it to move down a bit. The confusing part is that it looks like we are telling the loop only to look at items of type Enemy — but that’s not how it works. We are merely telling the loop that it should **expect** everything inside the army to be of type Enemy. We could have added the avatar to the army back in the constructor function — and then the game would have crashed because the Avatar class doesn’t contain a function called *moveDownABit()*. Anyway, don’t worry about this too much, it’ll become more intuitive as you use it more.

There’s one more change we need to make before we can run this: checking for collisions. We can just move the collision detection code to be inside the for each…in loop (lines 36-39):

public function onTick( timerEvent:TimerEvent ):void 
{
	for each ( var enemy:Enemy in army ) 
	{
		enemy.moveDownABit();
		if ( avatar.hitTestObject( enemy ) ) 
		{
			gameTimer.stop();
		}
	}
	avatar.x = mouseX;
	avatar.y = mouseY;
}

Although, this means that we are checking for collisions before we’ve moved the avatar, i.e. we’re checking if the enemy is colliding with where the player **used** to be, which hardly seems fair. So, rearrange it a bit:

public function onTick( timerEvent:TimerEvent ):void 
{
	avatar.x = mouseX;
	avatar.y = mouseY;
	
	for each ( var enemy:Enemy in army ) 
	{
		enemy.moveDownABit();
		if ( avatar.hitTestObject( enemy ) ) 
		{
			gameTimer.stop();
		}
	}
}

Save it and run it, and you’ll see everything’s working out just as before. Now to add more enemies.

###Adding to the Army

Try this:

public function onTick( timerEvent:TimerEvent ):void 
{
	var newEnemy:Enemy = new Enemy( 100, -15 );
	army.push( newEnemy );
	addChild( newEnemy );

You should recognise lines 33-35. We’ve just put the code for creating a new enemy in the onTick, so that every tick it will create a new enemy. What do you expect will happen when we run it?

screenshot

Hm. It’s creating new enemies in the same place at such a fast rate that they’re overlapping. Let’s try to fix this by starting the new enemies off in all different places.

We’ll need the *Math.random()* function for this. *Math.random()* returns a random number between 0 and 1. My game is 400 pixels wide, so I’d like the enemies to start off with an x-coordinate of between 0 and 400. Therefore (lines 33-34):

public function onTick( timerEvent:TimerEvent ):void 
{
	var randomX:Number = Math.random() * 400;
	var newEnemy:Enemy = new Enemy( randomX, -15 );
	army.push( newEnemy );
	addChild( newEnemy );

So now the enemies will be created at some random point above the screen. Save it and run it, and what do we get?

screenshot

Argh!

All right, so, maybe they’re still coming in at too fast a rate…

###Less Enemies per Second, Please

Right now, the enemies are appearing at a rate of one new enemy per tick. Since a tick is 25ms, there are 40 ticks per second, and therefore 40 new enemies per second. I think if we reduced this to four new enemies per second, we’d be good. Since 4 is 1/10 of 40, we need to reduce the new-enemies-per-tick rate to 1/10.

We could do this by making a new enemy appear every ten ticks. I think it would be more fun if we said there was a 1/10 chance of a new enemy appearing on any given tick; this way, sometimes we’d have more than four enemies appearing per second, and sometimes we’d have less, but over time we’d average out at four/sec. The uncertainty involved makes the game a little more exciting, however (though perhaps “exciting” is a little bit of a misnomer at this early stage of the game’s development…).

How can we do this, then? Well, we know that Math.random() generates a random number between 0 and 1. Since these random numbers are evenly distributed, that means that there’s a 1/10 chance of the number generated being between 0 and… well, 1/10. I.e., the chance of *Math.random() < 0.1* is 1/10. So, let's change our code to make use of this fact (lines 29 & 34):

public function onTick( timerEvent:TimerEvent ):void 
{
	if ( Math.random() < 0.1 )
	{
		var randomX:Number = Math.random() * 400;
		var newEnemy:Enemy = new Enemy( randomX, -15 );
		army.push( newEnemy );
		addChild( newEnemy );
	}

(Note: each time you call Math.random() the number generated is different, so this new code isn’t interfering with our random positioning code.)

Save it and run it.

screenshot

Much better! Actually, I think this may now be too easy. Why not fiddle around with the *if (Math.random() < 0.1)* statement to see what works best for you? ###Wrapping Up That's it for part two. You can download a zip file with everything I've been working on from here.

In the next part, we’ll add a Game Over screen so that finally the game will have an aim! It’s available here.

If you’d like to try expanding upon this part yourself, how about trying to make the starting enemy appear at a random location? I’m sure you can do that now. Also, the enemies sometimes appear partly off one side of the screen. Can you fix that?

{ 50 comments… read them below or add one }

Leave a Comment

Writing code? Write <pre> at the start and </pre> at the end to keep it looking neat.

Previous post:

Next post: