AS3 Avoider Game Tutorial, Part 6: Several Small Improvements

by Michael Williams on February 10, 2009 · 59 comments

in Avoider Game Base, Tutorial

Introduction

This part of my AS3 and Flash CS3 conversion of Frozen Haddock’s avoiding game tutorial is split into several separate parts, rather than being one long tutorial. I’ve received a number of suggestions for improvements to make to the game, and here I’m going to show you how to implement them. Some are obvious to the player — like hiding the mouse cursor and improving the collision detection — while some will mainly benefit you, the programmer. Because all the changes are separate, there’s no need to work through the entire post at once.

Click the image below to see the result of this part in action:

screenshot

If you’ve not been following the tutorial so far, grab the zip file of the game so far here and extract it somewhere. Otherwise, copy the files you’ve been working on so far to a new folder, as usual.

Open the FLA file, and let’s get started.

Now You See It, Now You Don’t

We’ll start with the most requested feature ever: hiding that damn mouse cursor.

In case you hadn’t noticed it before, check this image:

screenshot

The mouse cursor appears to be holding the nose of the avatar. This is actually very easy to change. Just open the AvoiderGame.as file from your Classes directory, and add the following line to the constructor function (line 16 below):

?View Code ACTIONSCRIPT3
14
15
16
public function AvoiderGame() 
{
	Mouse.hide();

Actually Mouse is one of those keywords you have to import, so:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
package 
{
	import flash.display.MovieClip;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.ui.Mouse;

(Line 6 above.) Save it and run it, and you’ll get the following:

screenshot

Perfect! Well, almost. Once you get Game Over you’ll find that the mouse is still missing in that screen, making it difficult (and irritating) to click the Restart button. So, go into your GameOverScreen.as file and add this line to the constructor:

?View Code ACTIONSCRIPT3
11
12
13
public function GameOverScreen() 
{
	Mouse.show();

Of course, you’ll have to import flash.ui.Mouse again for this file.

For good measure, do the same for the MenuScreen.as class. I know that at the minute there’s no need, because it’s impossible to get to the menu screen after entering the play screen, but you might decide to let the player go back to it later.

screenshot

Making Comments

Comments are incredibly useful for programmers. The basic idea is that Flash will completely ignore anything you write on a line after “//”.

For example:

?View Code ACTIONSCRIPT3
//Flash ignores this
var demo:Number = 7; //Flash sets demo to 7, but it ignores this text right here

The code box above sets the colour of these comments to green; Flash’s internal AS file editor turns them grey. In each case, the unusual colour indicates that the text is for the programmer’s benefit only.

What’s the point? Two reasons for using comments:

  1. Write notes all over your code to remind yourself (or another person) what it does
  2. Temporarily stop a piece of code from working without deleting it

Take a few minutes now to go through everything we’ve done and use comments to write notes about any part you find tricky. If you read a line and have to stop and think for a second to remember what it does, write a comment to make it clear. Try to make this a habit for any new code from now on as well.

For example, I’m going to write myself a comment for this line of AvoiderGame.as:

?View Code ACTIONSCRIPT3
gameTimer = new Timer( 25 );

I always forget whether it’s seconds or milliseconds or centiseconds or what. So:

?View Code ACTIONSCRIPT3
gameTimer = new Timer( 25 );    //ticks every 25ms = 0.025s or 40 times per second

Now I don’t have to think about it every time I look at it.

P.S. If you want to do multi-line comments (or “comment out” — that is, temporarily remove — several lines of code at once), you can use “//” repeatedly:

?View Code ACTIONSCRIPT3
//This is a long,
//multi-line comment.
//It might contain an
//explanation as to how
//an entire function works,
//or what a class is for.

But you can also use “/*” to mark the start, and “*/” to mark the end, like so:

?View Code ACTIONSCRIPT3
/* This is a long,
multi-line comment.
It might contain an
explanation as to how
an entire function works,
or what a class is for. */

Another use for comments: sign your code with your name and website address :)

Don’t Be So Hard On The Code

There are a few areas in the code where I’ve “hard-coded” certain values. In other words, I’ve written a value that we might want to change later inside some code that will make it hard to change later. Here’s an example, from Enemy.as:

?View Code ACTIONSCRIPT3
12
13
14
15
public function moveDownABit():void 
{
	y = y + 3;
}

First, I’ve called the function moveDownABit(). That’s a terrible name! This is the function that’s run every tick. By giving it a name that includes the direction of motion, I’ve pretty much ruled out the possibility of making the enemies move up, or left and right, or towards the avatar.

Second, I’ve written the number 3 directly into the code. 3 is the number that determines how far the enemy moves per tick; it’s effectively the enemy’s speed. By putting it directly into the code like that, I’ve make it harder to change later. All right, so it’s not exactly buried in such a way that we couldn’t find it later, but that’s only because we have a very simple movement function at the minute.

I’m going to change it to this:

?View Code ACTIONSCRIPT3
12
13
14
15
16
17
18
19
public function moveABit():void 
{
	var xSpeed:Number = 0;	//pixels moved to the right per tick
	var ySpeed:Number = 3;	//pixels moved downwards per tick
 
	x = x + xSpeed;
	y = y + ySpeed;
}

Now it’s easy to alter the behaviour of the enemies; just change xSpeed and ySpeed and they’ll move in a different direction. In fact, let’s improve this further by moving them from the function-level to the class-level (i.e. outside of any specific function but inside the class):

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package 
{
	import flash.display.MovieClip;
	public class Enemy extends MovieClip 
	{
		public var xSpeed:Number;	//pixels moved to the right per tick
		public var ySpeed:Number;	//pixels moved downwards per tick
 
		public function Enemy( startX:Number, startY:Number ) 
		{
			x = startX;
			y = startY;
 
			xSpeed = 0;
			ySpeed = 3;
		}
 
		public function moveABit():void 
		{
			x = x + xSpeed;
			y = y + ySpeed;
		}
	}
}

Now the actual direction and speed of movement is defined in the constructor, but are available to be read or changed from anywhere else in the class. If you wanted to, you could make a new function inside this class which the AvoiderGame class could use to pass the enemy the position of the avatar, so that it could move towards it. But I’ll leave that as a challenge for you.

A slightly simpler challenge to demonstrate how this works would be to change the lines:

?View Code ACTIONSCRIPT3
xSpeed = 0;
ySpeed = 3;

to

?View Code ACTIONSCRIPT3
xSpeed = Math.random();
ySpeed = Math.random();

Check it out! The enemies will tend to move towards the bottom-right corner of the screen — can you work out why, and fix it?

Oh! Also, because I changed the name of the function from moveDownABit() to moveABit(), you’ll have to alter AvoiderGame.as appropriately, from:

?View Code ACTIONSCRIPT3
47
48
49
for each ( var enemy:Enemy in army ) 
{
	enemy.moveDownABit();

to

?View Code ACTIONSCRIPT3
47
48
49
for each ( var enemy:Enemy in army ) 
{
	enemy.moveABit();

There are other places in the code where I’ve hard-coded values that should really be soft-coded. Can you find them?

You Only Die Once

Here’s a good bug someone spotted. Can you see it? (AvoiderGame.as)

?View Code ACTIONSCRIPT3
47
48
49
50
51
52
53
54
55
for each ( var enemy:Enemy in army ) 
{
	enemy.moveABit();
	if ( avatar.hitTestObject( enemy ) ) 
	{
		gameTimer.stop();
		dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
	}
}

I didn’t spot it myself at first, which is why I let it slip through. Suppose your avatar is touching two enemies at once (i.e. within one tick). It’s rare, but it does happen sometimes. Well, then, the AvatarEvent.DEAD is going to be dispatched twice within one tick. Whoops! My mistake.

Why is that a problem? Well, at the minute, it’s not, because nothing much happens when the avatar dies. But imagine if we had a lives system, and removed one life per death. Or if we added a feature to the scoring system whereby death added a bonus to your score. In either case, being able to die “twice at once” like this is just going to lead to confusing problems.

How to fix it: we’re going to use a type of data that I don’t think we’ve used before. It’s called a Boolean, which is a silly word meaning a variable that can only be set to either true or false.

?View Code ACTIONSCRIPT3
47
48
49
50
51
52
53
54
55
56
57
58
59
60
var avatarHasBeenHit:Boolean = false;
for each ( var enemy:Enemy in army ) 
{
	enemy.moveABit();
	if ( avatar.hitTestObject( enemy ) ) 
	{
		gameTimer.stop();
		avatarHasBeenHit = true;
	}
}
if ( avatarHasBeenHit )
{
	dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
}

In line 47 we define our new Boolean variable, avatarHasBeenHit. As you can guess from the name, it’s going to let us know whether or not the avatar has been hit. We set it to false to begin with, because the avatar hasn’t been hit yet.

Line 54 replaces the AvatarEvent dispatch call with a new line that simply sets avatarHasBeenHit to true. Note that nowhere else within the for loop can set avatarHasBeenHit back to false, so if the avatar is hit once, by any enemy, it’ll be recorded.

Lines 57-60 simply check this record to see whether the avatar was indeed hit, and, if so, runs the AvatarEvent dispatch call from before. Because this code isn’t in the for loop, it won’t be run more than once per tick (and since the gameTimer is stopped when the hit is detected, no more ticks will be run anyway).

Pixel-Perfect Hit Detection

Since we started with a popular request, let’s end with one, too.

This image shows a “collision” — at least by the game’s standards:

screenshot

If you comment out the line in AvoiderGame.as that dispatches the AvatarEvent, so that the GameOverScreen isn’t shown and the game just pauses (like in Part 2), you’ll see this is true.

Why is this considered a collision? Well, the avatar.hitTestObject( enemy ) call only checks to see whether the two objects’ bounding boxes are colliding. If I tell you that an object’s bounding box is the smallest upright rectangle that can contain that object, you’ll understand why the above situation counts as a hit:

screenshot

That’s what Flash “sees”, and so it’s not surprising the collision detection is so bad. (It’s one of Stephen Calender’s Obstacles for Flash Game Development).

Now, I would very much like to walk you, step-by-step, through building your own pixel-perfect collision detection function. However (and I hate to say this) there simply is not enough room here. Hit testing in Flash is a huge area of discussion, and any time anyone states an opinion on it, a thousand other developers jump in for a good old argument. Maybe I’ll write that post one day, but not here.

Instead, I’m going to show you how to use someone else’s brilliant AS3 collision detector. It’s written by a guy named Troy Gilbert (who invented Mockingbird — The Game Making Game) and it does its job very well.

His original code, along with its accompanying notes, is here. I have copied it into the below (collapsed) code box (click the triangle):

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package
{
	import flash.display.BitmapData;
	import flash.display.BitmapDataChannel;
	import flash.display.BlendMode;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	public class PixelPerfectCollisionDetection
	{
		/** Get the collision rectangle between two display objects. **/
		public static function getCollisionRect(target1:DisplayObject, target2:DisplayObject, commonParent:DisplayObjectContainer, pixelPrecise:Boolean = false, tolerance:Number = 0):Rectangle
		{
			// get bounding boxes in common parent's coordinate space
			var rect1:Rectangle = target1.getBounds(commonParent);
			var rect2:Rectangle = target2.getBounds(commonParent);
			// find the intersection of the two bounding boxes
			var intersectionRect:Rectangle = rect1.intersection(rect2);
			if (intersectionRect.size.length> 0)
			{
				if (pixelPrecise)
				{
					// size of rect needs to integer size for bitmap data
					intersectionRect.width = Math.ceil(intersectionRect.width);
					intersectionRect.height = Math.ceil(intersectionRect.height);
					// get the alpha maps for the display objects
					var alpha1:BitmapData = getAlphaMap(target1, intersectionRect, BitmapDataChannel.RED, commonParent);
					var alpha2:BitmapData = getAlphaMap(target2, intersectionRect, BitmapDataChannel.GREEN, commonParent);
					// combine the alpha maps
					alpha1.draw(alpha2, null, null, BlendMode.LIGHTEN);
					// calculate the search color
					var searchColor:uint;
					if (tolerance <= 0)
					{
						searchColor = 0x010100;
					}
					else
					{
						if (tolerance> 1) tolerance = 1;
						var byte:int = Math.round(tolerance * 255);
						searchColor = (byte <<16) | (byte <<8) | 0;
					}
					// find color
					var collisionRect:Rectangle = alpha1.getColorBoundsRect(searchColor, searchColor);
					collisionRect.x += intersectionRect.x;
					collisionRect.y += intersectionRect.y;
					return collisionRect;
				}
				else
				{
					return intersectionRect;
				}
			}
			else
			{
				// no intersection
				return null;
			}
		}
		/** Gets the alpha map of the display object and places it in the specified channel. **/
		private static function getAlphaMap(target:DisplayObject, rect:Rectangle, channel:uint, commonParent:DisplayObjectContainer):BitmapData
		{
			// calculate the transform for the display object relative to the common parent
			var parentXformInvert:Matrix = commonParent.transform.concatenatedMatrix.clone();
			parentXformInvert.invert();
			var targetXform:Matrix = target.transform.concatenatedMatrix.clone();
			targetXform.concat(parentXformInvert);
			// translate the target into the rect's space
			targetXform.translate(-rect.x, -rect.y);
			// draw the target and extract its alpha channel into a color channel
			var bitmapData:BitmapData = new BitmapData(rect.width, rect.height, true, 0);
			bitmapData.draw(target, targetXform);
			var alphaChannel:BitmapData = new BitmapData(rect.width, rect.height, false, 0);
			alphaChannel.copyChannel(bitmapData, bitmapData.rect, new Point(0, 0), BitmapDataChannel.ALPHA, channel);
			return alphaChannel;
		}
		/** Get the center of the collision's bounding box. **/
		public static function getCollisionPoint(target1:DisplayObject, target2:DisplayObject, commonParent:DisplayObjectContainer, pixelPrecise:Boolean = false, tolerance:Number = 0):Point
		{
			var collisionRect:Rectangle = getCollisionRect(target1, target2, commonParent, pixelPrecise, tolerance);
			if (collisionRect != null && collisionRect.size.length> 0)
			{
				var x:Number = (collisionRect.left + collisionRect.right) / 2;
				var y:Number = (collisionRect.top + collisionRect.bottom) / 2;
				return new Point(x, y);
			}
			return null;
		}
		/** Are the two display objects colliding (overlapping)? **/
		public static function isColliding(target1:DisplayObject, target2:DisplayObject, commonParent:DisplayObjectContainer, pixelPrecise:Boolean = false, tolerance:Number = 0):Boolean
		{
			var collisionRect:Rectangle = getCollisionRect(target1, target2, commonParent, pixelPrecise, tolerance);
			if (collisionRect != null && collisionRect.size.length> 0) return true;
			else return false;
		}
	}
}

Using this is very simple. Make a new ActionScript file, and call it PixelPerfectCollisionDetection.as. Copy the above code into it, and save it in your Classes directory.

Now, head back to AvoiderGame.as, and replace the line:

?View Code ACTIONSCRIPT3
51
if ( avatar.hitTestObject( enemy ) )

with this line:

?View Code ACTIONSCRIPT3
51
if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) )

Let’s look at that call in detail:

  • PixelPerfectCollisionDetection — Name of the class.
  • isColliding — This is a function within that class. Specifically, it’s a static function, which means it belongs to the class rather than to a specific instance of the class. (We used the static keyword when making our own Events.)
  • avatar, enemy — These are the objects we want to check to see if they’re colliding.
  • this — The function requires the third parameter to be the “common parent” of the two objects we’re checking for collisions. Since we’ve run addChild() on each of them from within this class, this is the common parent of them both.
  • true — The function requires the fourth parameter to be a Boolean specifying whether or not to be pixel-precise. I figured it might as well be.

Wrapping Up

That’s it for this week. As usual you can grab the zip file here. Next week we’ll let the user choose between mouse and keyboard controls, and make the avatar follow the mouse rather than be a replacement cursor.

Update: I just found out about Corey O’Neil’s AS3 Collision Detection Kit (thanks to Ryan of Untold Entertainment). How I missed this before I do not know. It’s a set of classes that give all sorts of different ways of doing collision detection and dealing with it. If you want to do more with your collision detection, check it out.

{ 1 trackback }

AS3: Creating a game like circle chain « LÞ the keg'o'grog blog
August 18, 2009 at 8:43 pm

{ 58 comments… read them below or add one }

Michael Williams November 21, 2009 at 5:41 pm

Hey Farout,

Not sure why you’d still be getting the two deads. To test it myself I put the traces in the onAvatarDeath() function in the document class.

Thanks for sharing your xSpeed and ySpeed generation code :)

And wow, seems the Flash game dev community is even more varied than I thought :P What’s EE and stats?

Glad you’ve found this useful!

Josh December 24, 2009 at 10:52 pm

So I just put in the whole collision detection thing and – after fixing some problems with capitalization – I test it and it goes directly to the game over screen after I push the start button. What is wrong this time?

TypeError: Error #1009: Cannot access a property or method of a null object reference. at DocumentClass/onAvatarDeath() at flash.events::EventDispatcher/dispatchEventFunction() at flash.events::EventDispatcher/dispatchEvent() at AvoiderGame/onTick() at flash.utils::Timer/_timerDispatch() at flash.utils::Timer/tick()

and this pops up around a hundred times on the output bar thing.

Josh December 24, 2009 at 11:31 pm

AAAAAAAAAHHHHHHH

after fixing that problem now for some reason the gameover screen wont pop up when the avatar hits an enemy. I have no idea why since the only thing I changed is that one line.

Michael Williams December 29, 2009 at 12:34 am

Not sure — sorry! I need more details.

Acke February 10, 2010 at 3:41 am

Hi, Thanks for the excellent tutorials! Even though i’m an experienced programmer they are helping a lot!

Just a useless fact Boolian is not just a silly name! Its named after George Boole who defined a system of logic in the 19th century.

Nik February 10, 2010 at 7:55 am

i’m using xSpeed = (Math.random() – Math.random()) // so you’ll get -1, 0, 0, 1 for Xspeed don’t know if it’s a good solution though??

Michael Williams February 13, 2010 at 2:07 am

Hey Nik,

Yep, that’ll work! You’ll find that you get more results closer to 0 than you do close to -1 or 1, though — can you figure out why?

Meiji March 4, 2010 at 6:52 pm

Awesome tut! Even when I have some AS3 OOP knowledge, I’m really enjoying it! =D Just a question, I placed the Mouse.show() inside the ‘onTick’ function in AvoiderGame.AS and seems to work pretty fine. That way I don’t have to write it in any other class nor import it again. I think there’s no difference, right?

for each(var enemy:Enemy in _army) {
                enemy.moveABit();
                if (PixelPerfectCollisionDetection.isColliding(_avatar,enemy,this,true)) {
                    Mouse.show(); //Here I'm placing the Mouse.show()
                    _gameTimer.stop();
                    avatarHasBeenHit = true;
                }
            }

Thank you very much for this great tutorial!

Leave a Comment

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

CommentLuv Enabled

Anti-Spam Protection by WP-SpamFree

Previous post:

Next post: