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 4: Menus and Buttons — Michael James Williams

AS3 Avoider Game Tutorial, Part 4: Menus and Buttons

by Michael James Williams on October 12, 2008 · 289 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:

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):

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:

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:

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*:

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:

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:

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:

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):

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):

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:

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:

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:

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:

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:

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:

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?

{ 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: