AS3 Avoider Game Tutorial, Part 4: Menus and Buttons

by Michael Williams on October 12, 2008 · 221 comments

in Avoider Game Base,Tutorial

(This tutorial is also available in Spanish)

Introduction

In this part of my AS3 conversion of Frozen Haddock’s avoiding game tutorial, we’ll be inserting a button to let the player restart the game if they die, and adding a menu screen to the start of the game.

Click the image below to see how this will look.

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.

Let Me Try That Again

At the minute, if the player touches an enemy, he’s sent to the game over screen and has to refresh the page in order to play again. We’re going to add a simple button to this screen that lets the player try again.

Create a new symbol of type Button (Insert > New Symbol, and select the Button option). Call it RestartButton, and click OK.

screenshot

You’ll enter editing mode for this new button. Draw whatever you like, as long as it’s obvious that clicking this will start the game again. Here’s mine:

screenshot

Now check out the timeline at the top of the screen:

screenshot

A button actually contains four separate images:

  • Up — What the button looks like normally.
  • Over — What the button looks like when the mouse is pointing at it.
  • Down — What the button looks like when it’s being clicked.
  • Hit — The area of the button that is “sensitive” to the mouse (more on this in a bit).

The shaded grey box with a dot in it in the Up column indicates that we have drawn an image for that state. Let’s draw images for the others now. Right-click each of the empty boxes in turn and select Insert Keyframe:

screenshot

screenshot

screenshot

OK, great. Click the shaded grey box with a dot in it (also known as a keyframe) in the Over column. It should have copied the image from the Up column into it; if not, you can copy and paste it from that column. Change the image in some suitable way — I’m just going to invert the colours of mine:

screenshot

Now change the Down keyframe in some other way:

screenshot

The Hit keyframe is a little different. The shape you draw in here defines the “clickable” area of the button. The player will never actually see this image. If you drew a button with a shadow, for instance, and you wanted the player to be able to click the button but not the shadow, then you would just draw the button’s shape in here and leave the shadowed area blank. (Does that make sense?)

Anyway, for my button I don’t want to leave any of the button unclickable, so I’m going to make the shape the same shape as the button:

screenshot

I’ve coloured it bright red just to remind myself that this is not going to be seen by the player, but you can make it any colour you like. You can even leave the original image there, if you want.

Now we need to add this button to the game over screen. Save what you’ve done, then double-click the GameOverScreen in the Library.

screenshot

Find your new RestartButton in the Library and drag it onto the game over screen.

screenshot

(Oops. I forgot that the black border of the button wouldn’t show up against the black background of the game over screen. Oh well.)

Remember back in the first part of this tutorial, when I talked about instances and classes? Well, the button on the game over screen is an instance of the class RestartButton. Since we haven’t defined it in ActionScript, it doesn’t have an instance name. If we want to be able to access it with code (and we do), we need to give it one.

Click the button you just added, and check out the Properties panel:

screenshot

The box marked is what we need. Enter restartButton (as usual, be careful with the capital letters!)

screenshot

All right, now we can write some code to get it to actually do something when it’s clicked.

Hey! …Listen!

So far we’ve used event listeners to trigger a function on the death of the avatar and the “tick” of the game timer. Now we’re going to be listening for the “click” of the restart button.

Since the button exists in the GameOverScreen, code concerning it must go in the GameOverScreen class. But… we never actually made such a class. So let’s do it now! Create a new AS file and enter the following:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
package 
{
	import flash.display.MovieClip;
	public class GameOverScreen extends MovieClip 
	{
		public function GameOverScreen() 
		{
 
		}
	}
}

Save this as GameOverScreen.as in your Classes directory.

Hopefully this code is looking quite familiar by now. Save everything and test the game. It’ll give you an error:

GameOverScreen.as, Line 2 1046:  Type was not found or was not a compile-time constant: SimpleButton.

What? Flash has detected that there is a button on the GameOverScreen but is saying “what is this? I have never heard of this ‘button’”. Flash can be pretty dumb sometimes.

We need to tell Flash exactly what a button is. Go back to GameOverScreen.as and modify it to import the SimpleButton class definition (line 4):

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
package 
{
	import flash.display.MovieClip;
	import flash.display.SimpleButton;
 
	public class GameOverScreen extends MovieClip 
	{
		public function GameOverScreen() 
		{
 
		}
	}
}

Now you can save it and test it. You’ll notice that the button changes when you hover over it and when you click it. Obviously it doesn’t actually do anything because we haven’t added the event listener yet. So let’s do that next.

Since this code is so similar to what we’ve added in earlier parts, I’ll just insert all of it at once and explain it afterwards:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package 
{
	import flash.display.MovieClip;
	import flash.display.SimpleButton;
	import flash.events.MouseEvent;
 
	public class GameOverScreen extends MovieClip 
	{
		public function GameOverScreen() 
		{
			restartButton.addEventListener( MouseEvent.CLICK, onClickRestart );
		}
 
		public function onClickRestart( mouseEvent:MouseEvent ):void 
		{
 
		}
	}
}
  • public function onClickRestart( mouseEvent:MouseEvent ) — This is the function that we want to be run upon clicking restart (hence, “on click restart”). Just as before, we are allowing information about the event (of the clicking) to be passed to this function.
  • restartButton.addEventListener( MouseEvent.CLICK, onClickRestart ); — This adds the event listener to the button, and tells it that if the button is CLICKed (yes, Flash really requires you to write CLICK in capitals like that) then it should run the onClickRestart function.
  • import flash.events.MouseEvent; — Without this line, Flash wouldn’t know what a mouse event was, which means it wouldn’t know the meaning of the word CLICK.

To start the game again, we have to remove the game over screen and reload the play screen. In Part 3 we rearranged everything so that the document class handled all this screen manipulation, so it’s that class that we need to modify. Open DocumentClass.as and add this new function:

?View Code ACTIONSCRIPT3
24
25
26
27
28
29
30
31
public function restartGame():void
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	addChild( playScreen );
 
	gameOverScreen = null;
}

Lines 26 & 27 set up the playScreen in exactly the same way as the constructor. This is necessary, because when the player gets Game Over, that line playScreen = null will erase the event listener and reset the x and y coordinates of the playScreen, as I explained in Part 3.

Line 30 would erase the gameOverScreen, but there’s a problem. gameOverScreen is only defined in the onAvatarDeath() function, so it isn’t available to the restartGame() function. Let’s make gameOverScreen available to the whole document class, just like playScreen:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
package 
{
	import flash.display.MovieClip;
	public class DocumentClass extends MovieClip 
	{
		public var playScreen:AvoiderGame;
		public var gameOverScreen:GameOverScreen;

Now we need to remove the var from the var gameOverScreen statement from onAvatarDeath, since we’ve already defined it:

?View Code ACTIONSCRIPT3
16
17
18
19
20
21
22
23
24
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{
	gameOverScreen = new GameOverScreen();
	gameOverScreen.x = 0;
	gameOverScreen.y = 0;
	addChild( gameOverScreen );
 
	playScreen = null;
}

Here’s what my class looks like:

?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
package 
{
	import flash.display.MovieClip;
	public class DocumentClass extends MovieClip 
	{
		public var playScreen:AvoiderGame;
		public var gameOverScreen:GameOverScreen;
 
		public function DocumentClass() 
		{
			playScreen = new AvoiderGame();
			playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
			playScreen.x = 0;
			playScreen.y = 0;
			addChild( playScreen );
		}
 
		public function onAvatarDeath( avatarEvent:AvatarEvent ):void
		{
			gameOverScreen = new GameOverScreen();
			gameOverScreen.x = 0;
			gameOverScreen.y = 0;
			addChild( gameOverScreen );
 
			playScreen = null;
		}
 
		public function restartGame():void
		{
			playScreen = new AvoiderGame();
			playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
			playScreen.x = 0;
			playScreen.y = 0;
			addChild( playScreen );
 
			gameOverScreen = null;
		}
	}
}

I’ve added lines 13 & 14 to position the playScreen at (0,0), just like with the gameOverScreen, and duplicated them in restartGame(). In a later part, I’ll simplify the way things work so that we don’t have to write the same code twice all the time.

REBOOT!

So now we have a function in gameOverScreen that is run when the restart button is clicked, and a function in the document class that restarts the game. It’s certainly tempting to hook the two together directly, but as I explained in the previous part, that leads to problems down the line. We’ll take the same approach here as we did with the avatar’s death: creating a new custom event.

What’ll happen is this:

  • Player clicks restart button
  • Restart button sends off a MouseEvent of type CLICK
  • MouseEvent.CLICK is picked up by an event listener within gameOverScreen, and triggers onClickRestart()
  • onClickRestart(), in turn, sends off our new custom event — let’s call it NavigationEvent — of type RESTART
  • NavigationEvent.RESTART is picked up by an event listener within the document class, and triggers a new function: onRequestRestart()
  • onRequestRestart() runs the restartGame() function

Phew! It’s long, but it’s actually quite straightforward. It also gives us scope for letting the player restart the game in other ways: when they press “R”, perhaps, or after a few seconds have passed. In either case, we just need to fire off a NavigationEvent.RESTART event, and we’ll already have done the rest.

Our new custom event looks much like the AvatarEvent:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
package  
{
	import flash.events.Event;
	public class NavigationEvent extends Event 
	{
		public static const RESTART:String = "restart";
 
		public function NavigationEvent( type:String )
		{
			super( type );
		}
	}
}

(Save this as NavigationEvent.as in the Classes folder.) Firing off the event is simple, too (this next bit goes in GameOverScreen):

?View Code ACTIONSCRIPT3
14
15
16
17
public function onClickRestart( mouseEvent:MouseEvent ):void
{
	dispatchEvent( new NavigationEvent( NavigationEvent.RESTART ) );
}

And with this being your third event listener, you should have no problems adding it to the document class (line 21):

?View Code ACTIONSCRIPT3
18
19
20
21
22
23
24
25
26
27
public function onAvatarDeath( avatarEvent:AvatarEvent ):void
{
	gameOverScreen = new GameOverScreen();
	gameOverScreen.addEventListener( NavigationEvent.RESTART, onRequestRestart );
	gameOverScreen.x = 0;
	gameOverScreen.y = 0;
	addChild( gameOverScreen );
 
	playScreen = null;
}

Of course, you need to create the onRequestRestart() function within the document class, and tell it what to do:

?View Code ACTIONSCRIPT3
29
30
31
32
public function onRequestRestart( navigationEvent:NavigationEvent ):void
{
	restartGame();
}

So now when the restart button is pressed (and after a long chain of events), the document class’s restartGame function will be run, which in turn will remove the game over screen and reload the play screen. Save it and test it, and at last, the player can restart the game without refreshing the page!

Hobson’s Choice

Adding a menu to the start of the game really isn’t difficult now. The code is going to be practically the same as for the game over screen. Let’s go through the steps real fast.

First, create a menu screen. You can create one from scratch if you like, but since the game over screen is already the right size I’m going to make a copy of that to start from.

Go back to your FLA file and find GameOverScreen in the Library. Right-click it and select Duplicate. It’ll ask for a name, so type in MenuScreen; leave the Type as Movie Clip. Double click the new MenuScreen in the Library to enter editing mode, and delete the restart button. Make whatever changes you want to the interface. Here’s mine:

screenshot

A true work of art.

Now for the button. Again, you can make one from scratch like we did earlier (remember it needs to be of type Button!), or you can duplicate the existing RestartButton. Totally your choice. Either way, call the new button StartButton and edit it accordingly. Here’s mine:

screenshot

I’m not being lazy, I’m maintaining a consistent style throughout my application. Add this new button to your MenuScreen and set the instance name to startButton.

screenshot

Now let’s write the code. Right-click the MenuScreen and select Properties. Check the Export for ActionScript box, enter MenuScreen in the Class box if it doesn’t come up automatically, and click OK. As usual, it’ll tell you:

screenshot

…so let’s create that class file.

Hit File > New and select ActionScript file. This is going to be the class file for our MenuScreen. Here’s the code:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package 
{
	import flash.display.MovieClip;
	import flash.display.SimpleButton;
	import flash.events.MouseEvent;
 
	public class MenuScreen extends MovieClip 
	{
		public function MenuScreen() 
		{
			startButton.addEventListener( MouseEvent.CLICK, onClickStart );
		}
 
		public function onClickStart( event:MouseEvent ):void
		{
			dispatchEvent( new NavigationEvent( NavigationEvent.START ) );
		}
	}
}

Save the file in your Classes directory as MenuScreen.as.

I’m not even going to explain most of this code, because it is almost exactly the same as GameOverScreen.as. Note, however, that in line 16 I’ve dispatched a NavigationEvent of type START — but at the minute the NavigationEvent class only has a RESTART type. Let’s add this new type to NavigationEvent.as right now:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
package  
{
	import flash.events.Event;
	public class NavigationEvent extends Event 
	{
		public static const RESTART:String = "restart";
		public static const START:String = "start";

Simple enough.

At the minute, the document class is displaying the play screen when it starts up. We’re going to change it to display our new menu screen instead. Change these lines:

?View Code ACTIONSCRIPT3
6
7
8
9
10
11
12
13
14
15
16
public var playScreen:AvoiderGame;
public var gameOverScreen:GameOverScreen;
 
public function DocumentClass() 
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	playScreen.x = 0;
	playScreen.y = 0;
	addChild( playScreen );
}

to this:

?View Code ACTIONSCRIPT3
6
7
8
9
10
11
12
13
14
15
16
17
public var menuScreen:MenuScreen;
public var playScreen:AvoiderGame;
public var gameOverScreen:GameOverScreen;
 
public function DocumentClass() 
{
	menuScreen = new MenuScreen();
	menuScreen.addEventListener( NavigationEvent.START, onRequestStart );
	menuScreen.x = 0;
	menuScreen.y = 0;
	addChild( menuScreen );
}

Note that I’ve added a new public var called menuScreen, and am no longer setting up the playScreen here.

Now we just need to make that new onRequestStart() function (line 13, above) and we’ll be sorted:

?View Code ACTIONSCRIPT3
35
36
37
38
39
40
41
42
43
44
public function onRequestStart( navigationEvent:NavigationEvent ):void
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	playScreen.x = 0;
	playScreen.y = 0;
	addChild( playScreen );
 
	menuScreen = null;
}

Save everything and test the game and… success!

Wrapping Up

I realise I went through things a bit faster in this part than I have previously. I’m doing this so as not to repeat myself, but if it’s just making things more confusing, please let me know.

As always, you can grab the zip file with everything I’ve done so far here, and you can subscribe to my RSS feed (to be alerted as soon as the next part is available) here.

In Part 5 I’ll show you how to add a clock and a score. Until then, why not have a go at adding a button to the Game Over screen that takes you back to the menu?

{ 219 comments… read them below or add one }

Michael Williams May 29, 2010 at 12:12 am

Nice work, Crump! That’s awesome :D How did you solve it?

Morgan May 29, 2010 at 6:21 am

Ive gone through your brilliant tutorial, and really enjoyed it! I managed to add a few extras which i was very proud of given that its was my first time using external .as files.

Now im trying to make a my own program and at the moment im trying to get my DocumentClass to add my first screen to the stage. My first screen is a class called SpaceCrystalScreen. Here is the code for my DocumentClass:

package
{
    import flash.display.MovieClip;

public class DocumentClass extends MovieClip
{
    var spaceCrystalScreen:SpaceCrystalScreen;

public function DocumentClass();
{
    spaceCrystalScreen = new SpaceCrystalScreen();

    spaceCrystalScreen.x = 0;
    spaceCrystalScreen.y = 0;
    addChild (spaceCrystalScreen);
}

}

}

however whenever i try to reference spaceCrystalScreen i get the 1120 error
1120: Access of undefined property spaceCrystalScreen.

it should be very simple and i cant understand why because i have declared the variable at the class level. can you see whats wrong?

cheers
mate!

Morgan May 29, 2010 at 6:24 am

sorry a mistake above
it should be public var spaceCrystalScreen:SpaceCrystalScreen;

but even when i add that it still throws the error… :(

Morgan May 29, 2010 at 6:46 am

Sorry to spam! but i rewrote my class and now it works. i hate not knowing what i did wrong though!

ps. your tutorial is great! ive read a few books on AS3 and now i get them!

ayumilove May 31, 2010 at 11:30 pm

@Morgan

ActionScript error #1120 means that an object “property” within Flash or Flex is not defined. But this doesn’t really help you fix it. One main reason for this AS3 Error is that you haven’t given the MovieClip or Button an instance name when you placed it on the stage in Flash. Another reason is that the reference is not correct.

Michael Williams June 12, 2010 at 1:34 pm

@Morgna: Great to hear — and thanks :D

@ayumilove: Good points. Man, I wish Flash would give you a little more detail on the error (like which property it is).

Colin June 13, 2010 at 9:26 am

hello,

i’m loving the tutorial so far but i’m getting an error that reads:

C:\Users\Ghost Train\Documents\Flash\Avoider\Classes\MenuScreen.as, Line 11 1120: Access of undefined property startButton..

i’ve triede “trace()”ing it and i have the instance of startButton name correctly on the stage, what other ways do you think i should try and solve this?

thank you

Josh June 14, 2010 at 6:00 pm

My restart button doesn’t do anything.
I’ve checked the code 5 times over and can’t find a problem…

I need some help =/

Michael Williams June 22, 2010 at 3:32 pm

@Colin: Have you turned “auto-declare stage instances” on, as in the start of Part 1? Also, is your startButton inside the MenuScreen clip, and is the MenuScreen exported for ActionScript?

@Josh: Put trace() statements in the most important points: start with one in the CLICK event handler function. (The trace() statement outputs a line of text to the Flash Output panel; use it like this: trace("this is a test");)

Colin July 11, 2010 at 2:52 pm

i am having a huge problem with my document class. I get this error: Line 1 5006: An ActionScript file can not have more than one externally visible definition: DocumentClass, onAvatarDeath. WHAAA? help please
Code:
package
{
    import flash.display.MovieClip;

public class DocumentClass extends MovieClip
{
    public var playScreen:AvoiderGame;

public function DocumentClass()
{
    playScreen = new AvoiderGame();
    playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
    addChild( playScreen );
}

}
public function onAvatarDeath ( avatarEvent:AvatarEvent ):void
{
var gameOverScreen:GameOverScreen = new GameOverScreen();
gameOverScreen.x = 0;
gameOverScreen.y = 0;
addChild( gameOverScreen );

playScreen = null;

}

}

Ivan Ruiz July 12, 2010 at 6:56 pm

hi, first of all thank you i have learned a lot but i have a problem and have been trying to solve it for years

is the problem

1061: Llamada a un método addEventListener posiblemente no definido mediante una referencia con tipo estático Class.

can anyone tell me what can cause that problem

thanx

Michael Williams July 23, 2010 at 11:50 am

@Colin: This error is because your onAvatarDeath() function is outside the DocumentClass‘s curly braces. Move it back inside the braces and the problem should be solved :)

@Ivan Ruiz: Looks like you’re trying to call .addEventListener() on a class, rather than on an instance of a class. For example, perhaps you’re doing MenuScreen.addEventListener() instead of menuScreen.addEventListener().

WorkingMan July 27, 2010 at 6:53 pm

When we make the buttons for the menu screen and the game over screen, we have to drag the button onto the MovieClip in Flash and then create an instance for each button.

Is there a way to place the button on the MovieClip and create instances by using Actionscript in the code?

Thanks

Mudkip August 3, 2010 at 4:58 pm

I’m having a problem with removing playScreen and gameOverScreen.

I set them to null, like it says in the tutorial, but they still remain there, EventListeners and all.

No idea what I’m doing wrong.

Mudkip August 3, 2010 at 5:02 pm

Also, when I trace gameOverScreen or playScreen after setting them to null, they do in fact show as null, but they remain.

James August 4, 2010 at 3:11 am

Hi, me again,
This time it’s for a location problem.
Where can I find the properties thing(for the replay button), in CS4 10.0?????
I looked through every possible menu in the instance right click drop down, but couldn’t find it.

TJ August 10, 2010 at 3:52 pm

Thanks for this awesome tutorial. =D

I have a problem though… I’m pretty sure I’ve followed everything here, but when I click on Start and Restart, it doesn’t kill the menu or game over screens. The smileys start falling down, with the game over or menu screen as a background.

package{
    import flash.display.MovieClip;

public class Main extends MovieClip{
    public var playScreen:AvoiderGame;
    public var GameOver:GameOverText;
    public var mainMenu:MainMenu;

public function Main(){
    mainMenu = new MainMenu();
    mainMenu.addEventListener( NavigationEvent.START, onRequestStart);
    addChild (mainMenu);
}

public function onPlayerDeath(playerEvent:PlayerEvent):void{
    GameOver = new GameOverText();
    GameOver.addEventListener( NavigationEvent.RESTART, onRequestRestart);
    addChild (GameOver);

    playScreen = null;
}

public function restartGame():void {
    playScreen = new AvoiderGame();
    playScreen.addEventListener(PlayerEvent.DEAD, onPlayerDeath);
    addChild(playScreen);

    GameOver = null;
}

public function onRequestRestart( navigationEvent:NavigationEvent):void {
    restartGame();
}

public function startGame():void {
    playScreen = new AvoiderGame();
    playScreen.addEventListener( PlayerEvent.DEAD, onPlayerDeath);
    addChild(playScreen);

    mainMenu = null;
}
public function onRequestStart( navigationEvent:NavigationEvent):void {
    startGame();
}

}

}

There’s my code. XD

TJ August 10, 2010 at 3:58 pm

Ah dang. I just solved it.

Apparently I didn’t link the BG to the Game AS.

Sorry! >.<

Anyways, awesome tutorial. ;D

Michael Williams August 21, 2010 at 12:15 am

OK! So, it seems I let these comments pile up again. Sorry about that. Here goes:

@WorkingMan: Yes! Entirely possible. Just export the button for ActionScript in the library and then use the same methods you use to put an instance of the Avatar on the screen.

@Mudkip: You need to removeChild() them. I explain more about what null actually does in Part 12 of the tutorial.

@James: Hi. Click the button, then click Window > Panels > Properties panel. (Or it might just be Window > Properties. I don’t have CS4.)

@TJ: Thanks! Well done for fixing it :)

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

{ 2 trackbacks }

Previous post:

Next post: