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 10: Multiple Levels — Michael James Williams

AS3 Avoider Game Tutorial, Part 10: Multiple Levels

by Michael James Williams on March 10, 2009 · 114 comments

in Avoider Game Base,Tutorial

We’ve got a lot of the features necessary for a game to be considered a game, but we’re missing a key ingredient: progression. At the minute, the game doesn’t get any harder, so there’s no real sense of achievement for surviving the game for a long time, since it all comes down to luck and stamina. It’s also the case that after a few seconds of play, you’ve seen everything the game has to show you — not just from a graphics point of view, but from a gameplay/experience point of view, too.

Therefore, in this part of my AS3 and Flash CS3 conversion of Frozen Haddock’s avoider game tutorial, we’ll add new levels the player can reach.

Click the preview image below to check out how this will play:

screenshot

If you’ve not been following the tutorial, 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.

###What Are Levels?

Before we decide to add levels to our game, let’s look at the different ways other game developers have incorporated levels.

In Dodge, you start off pitted against a few yellow enemies.

screenshot

When you destroy all enemies, you’re given a quick breather and a chance to trade points for health before moving on to the next level. Later levels increase the number of enemies on-screen, and also introduce more *kinds* of enemies, like this blue one:

screenshot

Amorphous+ does not have the same sort of level structure. You effectively choose what level to play at the start, picking the number of enemies to fight or the rate at which the difficulty increases, or specifying a specific type of enemy to practise against. Within the game, you play the level through to the end (or until you die), and the longer you survive, the more vicious the enemies that spawn:

screenshot

The level’s background is picked at random when you start playing:

screenshot

Finally, Four Second Frenzy is different from all the above. Like the WarioWare series of games, it uses “microgames” with completely different objectives, rather than simply adding new elements to a single core game.

screenshot

The microgames do all tie in to a single control structure and set of lives, though.

So, clearly, there are a lot of different meanings to this idea of a “level”. A change in level might simply mean a visual change, but more often it’ll mean a change in the difficulty, the types of enemies, the scoring conditions, or even the entire ruleset of the game! You need to decide what levelling up means in your game.

###What I’ll Do

Based on what I’ve said in previous parts of this tutorial, it may seem natural to make one class per level, with each class extending AvoiderGame, and use events to switch between them. So, we might have classes named AvoiderGameLevelOne, AvoiderGameLevelTwo, etc., and let each one fire off a “NavigationEvent.NEXT_LEVEL” when appropriate. Presumably then the document class would listen for this event, and when it heard it, it would run “playScreen = new AvoiderGameLevelTwo()” (or whichever level was appropriate), and pass through all the information such as score and time to this new playScreen instance.

That structure would work, and it does seem to follow what we’ve already done with the different screens in the game. It would also be particularly useful for a game like the aforementioned Four Second Frenzy, whose levels were made by different people. The trouble is, it’s very restrictive for any game that wants to keep a single set of rules.

For one thing, we’d be restricted to a finite set of levels. If the player finishes AvoiderGameLevelTen and there’s no AvoiderGameLevelEleven, then that’s it, game over. This is fine for some games, especially if a story is involved, but for an arcade-style game that we want to keep getting more challenging rather than just ending, it’s hardly ideal.

Consider Tetris. At the end of each level, all that happens is the blocks fall faster, and the score earned for making a line increases. Some versions change the background colour too. If we were going to use one class per level, then we’d lose all the blocks that had already fallen each time we created a new level. If we wanted to keep them, we’d have to pass all the information about where they were up to the document class, and then let the document class pass that information back down into the new level. And we’d still have to stop increasing the falling speed and points earned per line once we ran out of level classes.

I’m going to base my level structure on Tetris, with a new level meaning a change in background and a greater number of enemies appearing per tick. Therefore, I don’t want to use the class-per-level approach. But what can we do instead?

When you think about it, all the AvoiderGame class contains is a set of rules, the *mechanics* of the game. When you press “up”, AvoiderGame checks its list of rules and sees that this means the avatar should be moved up a few pixels. Every time a new enemy is created, AvoiderGame knows to add some points to the score. But we could easily alter the exact number of pixels the avatar should move, or the exact number of points to add to the score, *without* changing the underlying structure of the rules.

So, we can create new levels simply by changing these values depending on what level the player is on. We could use if statements, like so:

if ( currentLevel == 1 )
{
	gameScore.addToValue( 10 );
}
else if ( currentLevel == 2 )
{
	gameScore.addToValue( 15 );
}
else if ( currentLevel == 3 )
{
	gameScore.addToValue( 22 );
}

…but then we end up with bits of level-specific code dotted all around the class, with no easy way to find or edit it all at once. Close, very close, but not quite good enough.

The best solution I know of is to store all the level-specific information in a separate place from the rules, and then have the AvoiderGame class request the values it needs for the current level. Read on to find just how to do that.

###The LevelData Class

Let’s start by making a striking and obvious visual change between levels: the background.

Start by opening up the *PlayScreen* symbol in the library (remember this is linked to the *AvoiderGame* class file). At the minute, the background is just sort of drawn on to the PlayScreen itself. If we want to be able to change it using code, we’ll have to make it a symbol of some kind. Select all of it, being careful not to select the clock and score (you can select multiple objects by holding *shift* while clicking them):

screenshot

To make this into a symbol (in this case, we’ll make it a movie clip), click *Modify > Convert To Symbol*. Call it *BackgroundContainer* — I’ll explain why in a minute — and export it for actionscript with the same name.

Because we have a preloader, we need to uncheck the *Export in first frame* box, and add the movie clip to the *AssetHolder* movie clip. For more information on doing that, check out Part 8. From now on, I’m not going to mention it, so be sure you remember to do it for each symbol. You can of course leave it, then do it for a bunch of symbols at once, perhaps when you finish the game.

Anyway, edit the *BackgroundContainer* if you’re not doing so already, and *again* select all of the background elements and convert them to a new symbol of type movie clip. I’m calling mine *BlueBackground* but of course this might not be appropriate for whatever you’ve drawn, so name it accordingly. Export it for ActionScript with the same name, too.

Now, go back to editing *PlayScreen*, and give the *BackgroundContainer* movie clip an instance name of *backgroundContainer*. It’ll probably be in front of the score and clock, so right-click it, select *Arrange*, and click *Send to Back*:

screenshot

So now the *PlayScreen* contains a movie clip of class *BackgroundContainer*, which in turn contains another movie clip that is our *BlueBackground*.

Great. Now right-click the *BlueBackground* in the library, and select *Duplicate*. Call it *RedBackground*, export it for ActionScript, and start editing it. Here’s what mine looks like:

screenshot

No surprises there. Save it and run it to make sure everything’s still working with the blue background.

Now it’s time to make the class that’ll contain the level-specific information. Create a new AS file, and save it as *LevelData.as* in the *Classes* directory. Here’s the base code:

package
{
	public class LevelData
	{
		public function LevelData()
		{
			
		}
	}
}

Note that it’s not *extending* anything — it doesn’t need to. We want this to contain all the data relevant to the level, and at the minute that’s just the background colour, so add a class-level variable called *backgroundImage*:

package
{
	public class LevelData
	{
		public var backgroundImage:String;
		
		public function LevelData()
		{
			
		}
	}
}

How do we link the level number to the background, though? Like this:

package
{
	public class LevelData
	{
		public var backgroundImage:String;
		
		public function LevelData( levelNumber:Number )
		{
			if ( levelNumber == 1 )
			{
				backgroundImage = "blue";
			}
			else if ( levelNumber == 2 )
			{
				backgroundImage = "red";
			}
		}
	}
}

So when we create a new instance of this LevelData class, we’ll pass through the number of the level that we’re currently on, and it’ll set its *backgroundImage* variable to the colour corresponding to that level’s background. We can then obtain this variable from within *AvoiderGame*. Let’s do that now.

Save this class file, and go back to *AvoiderGame.as*. Create a new class-level variable to hold the current level’s data:

public var currentLevelData:LevelData;

In the constructor function, instantiate this object like so:

public function AvoiderGame() 
{
	currentLevelData = new LevelData( 1 );

Now, *currentLevelData.backgroundImage* will contain the text “blue”. Feel free to *trace()* it to check it’s working. What we’re going to do now is put the correct background into the, well, background:

public function AvoiderGame() 
{
	currentLevelData = new LevelData( 1 );
	if ( currentLevelData.backgroundImage == "blue" )
	{
		backgroundContainer.addChild( new BlueBackground() );
	}
	else if ( currentLevelData.backgroundImage == "red" )
	{
		backgroundContainer.addChild( new RedBackground() );
	}

What this is doing is *addChild*-ing the correct background image to the *backgroundContainer* clip, **not** to the play screen. Since the background container is already right at the back, this means it’ll stay behind the score, the clock, the avatar and the enemies. If we just wrote *addChild( new BlueBackground() )* we’d have to manually move it to the back. This solution is kind of like painting over a wall with the background image we want to display; we’re not actually removing the image that’s already there, just sticking something new on top of it. Obviously this is inefficient.

You might be wondering why we don’t just place an instance of *BlueBackground* on the play screen, with an instance name of *backgroundImage*, and then write, *backgroundImage = new RedBackground()* as we need to. The issue is that since *BlueBackground* and *RedBackground* are different classes, we can’t just swap them like that, even though they are both just sub-classes of MovieClips. We can get around this by unchecking the *Automatically declare stage instances* option in the *Publish Settings*, and this is not very complicated, but it’d require adding quite a lot of code so I don’t want to go into it now.

Anyway, if you save and run it, you’ll see that the game has a blue background. Great, but that doesn’t exactly prove anything. So change the 1 in this line:

currentLevelData = new LevelData( 1 );

to a 2:

currentLevelData = new LevelData( 2 );

Save it and run it again:

screenshot

Awesome.

###Level Up!

That basically shows that we can skip to a specific level, but how about actually progressing to a new level from within the game?

First we need to decide what a player needs to do to get to the next level. One idea I’ve seen a couple of people suggest in the comments and by email is placing a target on-screen that the avatar has to reach to get to the next level. That’s a cool idea, but I’m going to do something simpler for now; I’m just going to put the player in a new level when they reach a certain score.

Change that 2 back to a 1 so that the player starts on the first level. Now, the best place to check the player’s score is in the *onTick()* function, so move to that part of the code. I’m going to add the check right at the end of the function, so that everything else can happen first:

	if ( avatarHasBeenHit )
	{
		bgmSoundChannel.stop();
		dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
	}
	
	if ( gameScore.currentValue >= 150 )
	{
		currentLevelData = new LevelData( 2 );
		if ( currentLevelData.backgroundImage == "blue" )
		{
			backgroundContainer.addChild( new BlueBackground() );
		}
		else if ( currentLevelData.backgroundImage == "red" )
		{
			backgroundContainer.addChild( new RedBackground() );
		}
	}
}

Lines 183-194 are the new ones. Well, I say that, but lines 186-193 are copied from above, so let’s move them to a new function, *setBackgroundImage()*:

	if ( avatarHasBeenHit )
	{
		bgmSoundChannel.stop();
		dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
	}
	
	if ( gameScore.currentValue >= 150 )
	{
		currentLevelData = new LevelData( 2 );
		setBackgroundImage();
	}
}

public function setBackgroundImage():void
{
	if ( currentLevelData.backgroundImage == "blue" )
	{
		backgroundContainer.addChild( new BlueBackground() );
	}
	else if ( currentLevelData.backgroundImage == "red" )
	{
		backgroundContainer.addChild( new RedBackground() );
	}
}

That’s neater. Go back and replace those lines with a call to *setBackgroundImage()* in the constructor function, too.

If you save and run this, you’ll find that although it does change the background after you get enough points, it runs awfully slowly. The reason for this is the if statement that we’re using:

if ( gameScore.currentValue >= 150 )

Obviously that’ll still be true if the score is 160, 170, or over 9000. So every tick after you get 150 points for the first time, it’s going to run; 40 times a second, a new background will be created and painted over the old one. No wonder it gets a bit laggy.

How can we fix this? Well, here’s my solution. First, add a new variable, *pointsToReachNextLevel*, to the *LevelData* class, and set it up like so:

package
{
	public class LevelData
	{
		public var backgroundImage:String;
		public var pointsToReachNextLevel:Number;
		
		public function LevelData( levelNumber:Number )
		{
			if ( levelNumber == 1 )
			{
				backgroundImage = "blue";
				pointsToReachNextLevel = 150;
			}
			else if ( levelNumber == 2 )
			{
				backgroundImage = "red";
				pointsToReachNextLevel = 9999999;
			}
		}
	}
}

(I’ve temporarily set it to some ridiculously huge number to reach level three since we haven’t got there yet.) Now, back in AvoiderGame, just set that troublesome if statement to use this new variable:

if ( gameScore.currentValue >= currentLevelData.pointsToReachNextLevel )
{
	currentLevelData = new LevelData( 2 );
	setBackgroundImage();
}

Test it out, and it should work fine. Excellent! Now how about increasing the rate at which enemies appear on level two? It’s really simple; you might want to try it out yourself before reading on.

All you need to do is add a new variable to the *LevelData* class (and set it up):

package
{
	public class LevelData
	{
		public var backgroundImage:String;
		public var pointsToReachNextLevel:Number;
		public var enemySpawnRate:Number;
		
		public function LevelData( levelNumber:Number )
		{
			if ( levelNumber == 1 )
			{
				backgroundImage = "blue";
				pointsToReachNextLevel = 150;
				enemySpawnRate = 0.05;
			}
			else if ( levelNumber == 2 )
			{
				backgroundImage = "red";
				pointsToReachNextLevel = 9999999;
				enemySpawnRate = 0.1;
			}
		}
	}
}

…and then use this in *AvoiderGame* (see line 126 below):

if ( Math.random() < currentLevelData.enemySpawnRate )
{
	var randomX:Number = Math.random() * 400;
	var newEnemy:Enemy = new Enemy( randomX, -15 );
	army.push( newEnemy );
	addChild( newEnemy );
	gameScore.addToValue( 10 );
	sfxSoundChannel = enemyAppearSound.play();
}

###Infinite Levels

I said above that one of the disadvantages of using one class per level was that we would be restricted to a finite set of levels. So far, we've only got two, which is hardly worth boasting about. Let's add a couple more. Start by adding the level-specific data to the *LevelData* class:

public function LevelData( levelNumber:Number )
{
	if ( levelNumber == 1 )
	{
		backgroundImage = "blue";
		pointsToReachNextLevel = 150;
		enemySpawnRate = 0.05;
	}
	else if ( levelNumber == 2 )
	{
		backgroundImage = "red";
		pointsToReachNextLevel = 350;
		enemySpawnRate = 0.1;
	}
	else if ( levelNumber == 3 )
	{
		backgroundImage = "blue";
		pointsToReachNextLevel = 600;
		enemySpawnRate = 0.13;
	}
	else if ( levelNumber == 4 )
	{
		backgroundImage = "red";
		pointsToReachNextLevel = 9999999;
		enemySpawnRate = 0.15;
	}
}

Since I've only got two backgrounds, I'm just alternating between them. Naturally you can make more and use those, as long as you alter the *setBackgroundImage()* function accordingly.

We've got a problem, though. Check out that if statement again:

if ( gameScore.currentValue >= currentLevelData.pointsToReachNextLevel )
{
	currentLevelData = new LevelData( 2 );
	setBackgroundImage();
}

Line 178 above shows us that we're just going to keep loading level 2 over and over again. We need to move it to the next level, i.e. we need to run something like, *currentLevelData = new LevelData( currentLevelNumber + 1 );*. We could create a new variable within *AvoiderGame* called *currentLevelNumber*, but I think we might as well store this in *LevelData* as well. Just alter it like so:

public var backgroundImage:String;
public var pointsToReachNextLevel:Number;
public var enemySpawnRate:Number;
public var levelNum:Number;

public function LevelData( levelNumber:Number )
{
	levelNum = levelNumber;

I'd call the variable *levelNumber*, but that's what I've called the variable I'm passing into the constructor function, so *levelNum* will have to do. Now in *AvoiderGame* we just have to refer to this value:

if ( gameScore.currentValue >= currentLevelData.pointsToReachNextLevel )
{
	currentLevelData = new LevelData( currentLevelData.levelNum + 1 );
	setBackgroundImage();
}

Save it and run it, and you'll see we now have four levels (but if you manage to get ten million points you'll get a lot of errors). That's fine and all, but why not shoot for infinitely many levels?

All we have to do for that is decide on a general rule for setting these values. If you've done "nth term" sequences in algebra class you'll understand this. Take a look how I've done it for the numeric values:

else if ( levelNumber == 4 )
{
	backgroundImage = "red";
	pointsToReachNextLevel = 770;
	enemySpawnRate = 0.15;
}
else
{
	backgroundImage = "blue";
	pointsToReachNextLevel = levelNumber * 200;
	enemySpawnRate = 0.6 - ( 2 / levelNumber );
}

(Don't forget to change the points to reach level 5 to be something a bit more manageable!)

If you work the maths out, you can see that when the player gets to level five, they need 1000 points to reach the next level, and the enemies spawn at a rate of 0.2 per tick. Level six requires 1200 points to pass, and the enemies spawn at a rate of roughly 0.27 per tick. You can continue this onwards forever, though the game does get pretty slow once a lot of enemies have been spawned (we'll talk about fixing this problem in Part 12).

We're nearly done, but there's one thing that still bugs me -- the background stays blue from level five onwards. Of course, we could use *Math.random()* to pick the colour of the background after level four, but I'd much rather have it alternate between blue and red.

To achieve this, we can use the *modulo* function, which I've not introduced yet. *Modulo* gets you the remainder after a division. One divided by two is zero remainder one, so one modulo two is one. Two divided by two is one remainder zero, so two modulo two is zero. Three divided by two is one remainder one, so three modulo two is one. You get the idea; basically *levelNumber modulo two* will alternate between one and two; *levelNumber modulo five* would count from one to five and then start again. If you'd like to read lots and lots about the *modulo* operation, check out Wikipedia's article.

Anyway, it's simple to add. *Modulo* is represented in code by the percentage sign, %, so we can use it like so:

else
{
	if ( levelNumber % 2 == 1 )
	{
		backgroundImage = "blue";
	}
	else
	{
		backgroundImage = "red";
	}
	pointsToReachNextLevel = levelNumber * 200;
	enemySpawnRate = 0.5 - ( 2 / levelNumber );
}

Easy.

###Challenges

This opens up a huge range of challenges you can attempt. For starters, how about displaying the current level number and the number of points required to reach the next level? You could also alter the speed of the enemies or the avatar as the player gets further through the game. And if I tell you that all movie clips (and thus all classes extending movie clips) have *.scaleX* and *.scaleY* properties that alters the width and height of the object (so that setting it to 1 is normal size, 2 is twice as big as normal, and 0.5 is half as big as normal), could you alter the size of the enemies and avatar according to the level?

If you've added different kinds of enemies to your game, you might want to have multiple spawn rates, one for each kind, so that you can let the nastier enemies appear more and more often as the player gets further through the game (like in Amorphous+). If you haven't done this, you could combine this idea with the above tip regarding *scaleX* and *scaleY*, and use different sizes of the standard enemy instead of different types.

How about splash screens between levels? You can use *gameTimer.stop()* to effectively pause the game, and then pop up a simple button for the player to click to move on to the next level using the "GameOverText" method from Part 3. You could even create cutscene movie clips to go in-between certain levels.

Bonus levels could be cool, too. For example, every fifth level make coins appear instead of enemies, and hitting them adds to your score instead of killing the player.

You could also alter the conditions for moving to the next level. Perhaps base them on time rather than score, or on reaching a different point in the level. Maybe you could make a level select screen, or have a cheat code that players can enter to skip a level.

If you have any other ideas, please stick them in the comments so that other people can try them!

###Wrapping up

Hey, look at that, we're all done and we didn't *import* or *extend* anything, or add any event listeners! That's pretty unusual.

As always, you can download a zip with all the files relating to this part of the tutorial here.

In the next part, we'll look at creating and reloading savegames.

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