Part 7: Putting It All Together

Now that we have discussed all the supporting classes and MovieClips that we need to finish this lesson, we need to discuss how all of this fits together in HomeWars.as.

To start, we must set-up all the static vars and functions to support the depths that we will use for the new objects we will be creating.

Depths

The static vars for depths are as follows:

        static var SCOREBOARD_DEPTH		= 5000;
	static var PLAYER_DEPTH 		= 1000;
	static var MISSILE_MIN_DEPTH 	        = 2000;
	static var MISSILE_MAX_DEPTH 	        = 3000;
	static var ENEMY_MIN_DEPTH 		= 4000;
	static var ENEMY_MAX_DEPTH 		= 5000;
	static var EXPLODE_MIN_DEPTH 	        = 6000;
	static var EXPLODE_MAX_DEPTH 	        = 7000;
	static var TEXT_INTRO_DEPTH 	        = 10000;
 

We also need to creeate variables to hold references to the objects and arryas to hold things like EnemyMissiles and Enemies.

        var enemies:Array;
	var enemymissiles:Array;
	var explosions:Array;
        var endScreen:MovieClip;
	var scoreboard:MovieClip;
	var textintro:MovieClip;

Also, to handle the increasing depths of our objects, we need some variables to hold a continuous count of those objects:

        var enemyCount:Number = 0;
	var explodeCount:Number = 0;	

Finally, we need to create some functions that will allow us to retrieve depths for the Enemis and Explosions (we will use the existing Missile depths for the Enemy missiles).

       function getNextEnemyDepth():Number {
		enemyCount++;
		if (enemyCount+ENEMY_MIN_DEPTH > ENEMY_MAX_DEPTH) {
			enemyCount = 0;
		} 

		return enemyCount+ENEMY_MIN_DEPTH;
	}

	function getNextExplodeDepth():Number {
		explodeCount++;
		if (explodeCount+EXPLODE_MIN_DEPTH > EXPLODE_MAX_DEPTH) {
			explodeCount = 0;
		} 

		return explodeCount+EXPLODE_MIN_DEPTH;
	}

Game States

If you recall from Lesson 1, we created a very simple “state machine” to handle the flow of our game. We need to alter these states a bit for the advancements we will make in Lesson 2. The states we will now use are as follows:


        static var STATE_TITLE_SCREEN 	=	10;
	static var STATE_WAIT_FOR_CLOSE	=	20;
	static var STATE_INIT			=	30;
	static var STATE_SHOW_TEXT_INTRO=	40;
	static var STATE_INIT_PLAYER	=	50;
	static var STATE_PLAY			=	60;
	static var STATE_REMOVE_PLAYER	=	70;
	static var STATE_END_SCREEN		=	80;
	static var STATE_PAUSE			=	100;

We have added a few states to this list that did not exist prior.

STATE_SHOW_TEXT_INTRO will be used to to show the TextIntro on the screen, and wait for it to finish before proceeding with the game.

STATE_INIT_PLAYER will be used to place the Player on the screen.

STATE_REMOVE_PLAYER will be used to remove the Player from the screen when it has been destroyed.

STATE_PAUSE will be used to pause the game.

STATE_PAUSE is interesting because this not how we accomplished this task in Lesson 1. There are two reasons for this change.

1. It is cleaner to make pause a gamestate instead of a function of the STATE_PLAY state.

2. Because we are now have STATE_INIT_PLAYER and STATE_REMOVE_PLAYER that do things while action might be continuing on the screen, we need to make the pause function independent of STATE_PLAY.

STATE_PAUSE is still initialed when the [p] key is pressed:


            function onKeyDown() {

		if (Key.getCode() == PAUSE_KEY_CODE) {

			if (gameState == STATE_PAUSE) {
				gameState = pauseState;
			} else {
				pauseState = gameState;
				gameState = STATE_PAUSE;
			}

		}
	}

But instead of setting a paused variable that is checked by the STATE_PLAY, we save the old gameState in the variable pauseState and set the gameState to STATE_PAUSE. When [p] is pressed again, we set gameState =pauseState and the game will continue as nothing happened. The fSTATE_PAUSE() function called from Run() is an empty shell function.

        function fSTATE_PAUSE() {
		trace("fstate_pause");
	}

Initializing

Just like in Lesson 1, when a game is started fSTATE_INIT() is called. However, now it both initializes the necessary variables for new game:


                missiles = new Array();
		missileCount=0;
		enemies = new Array();
		enemyCount = 0;
		explosions = new Array();
		explodeCount = 0;
		enemymissiles = new Array();

And then creates a ScoreBoard object, intializes it, and puts it on the screen:


                scoreboard = parentTL.attachMovie("FScoreBoard","sb",SCOREBOARD_DEPTH);
                scoreboard.setLocation(0,0);
                scoreboard.reset(); 

Then it sets the gameState to one of our new states: STATE_SHOW_TEXT_INTRO.


                gameState = STATE_SHOW_TEXT_INTRO;

fSTATE_TEXT_INTRO puts an instance of TextIntro on the screen, then waits for it to finish by listening for the EventTextIntroComplete event, and then setting the gameState to STATE_WAIT_FOR_CLOSE. fSTATE_WAIT_FOR_CLOSE() now included added functionality to boradcast a Display event when it is being called. We do this so we can have the screens and TextIntros animate, while retaining our event model. textintro subscribes to this event with this line of code: this.addEventListener(”Display”,textintro);


        function fSTATE_SHOW_TEXT_INTRO() {
		textintro = parentTL.attachMovie("FTextIntro","ti",TEXT_INTRO_DEPTH);
		textintro.setText("Get Ready!!!");
		textintro.setWait(40);
		textintro.setLocation(205,100);
		textintro.addEventListener("EventTextIntroComplete",this);
		this.addEventListener("Display",textintro);
		gameState = STATE_WAIT_FOR_CLOSE;

	}

       function fSTATE_WAIT_FOR_CLOSE() {
           dispatchEvent({type:"Display"});
           //waiting
        }

When EventTextIntroComplete is broadcast from TextIntro, we remove textintro from the screen, and set the gameState to STATE_INIT_PLAYER.

        function EventTextIntroComplete(e:Object) {
		textintro.removeMovieClip();
		this.removeEventListener("Display",textintro);
		gameState = STATE_INIT_PLAYER;

	}

Now we must initialize the Player. This is separate from the STATE_INIT so it can double as our “jump-in” point when the the player loses a life.

        function fSTATE_INIT_PLAYER() {
		player = parentTL.attachMovie("FPlayer", "player1",  PLAYER_DEPTH);
		player.setLocation((Stage.width/2),Stage.height-player._height);
		player.setSpeed(10);
		player._alpha = 100;
		player.addEventListener("EventFireMissile", this);
		player.addEventListener("EventPlayerRemoved", this);
		this.addEventListener("Move",player);
		this.addEventListener("Render",player);
		gameState = STATE_PLAY;
	}

Playing The Game

Now that everything is initialized, it’s time to start the action. We do this in the fSTATE_PLAY() function.


        function fSTATE_PLAY() {
		createEnemy();
		dispatchEvent({type:"Move"});
		testCollisions();
		dispatchEvent({type:"Render"});
		removeExplosions();
	}	

There are 3 new things that we do in this function:

      1. createEnemy(): Decides if we will create a new Enemy in this frame.
      2. testcollisions(): Simple collision detection for all objects
      3. removeExplosions(): Clean up the explosions on the screen (recall that the Explosions don’t have a class associated with them).

We also call broadcast the Move and Render events to all objects, just like we had done in Lesson 1. This time though, there are more objects to move (Player, Missiles. EnemyMissiles, Enemies).

Creating Enemies

Since we don’t have any levels or balancing the game for this lesson, creating enemies is very straight-forward process. in the createEnemy function we simply check a random number to see if it less than a an arbitrary “enemy appear rate” (in this case, 5), create a new instance of Enemy:


              function createEnemy() {
                   if (Math.floor(Math.random()*100) < 5) {
			var tempClip:MovieClip;
			var ed:Number =  getNextEnemyDepth();
                        tempClip =  parentTL.attachMovie("FEnemy", "enemy" + ed,  ed);

Set some random values for things like speed, maxhits, firerate, location, face and movement type:

                        tempClip.setFace(Math.floor(Math.random()*4)+1);
			tempClip.setMaxhits(Math.floor(Math.random()*5)+1);
			tempClip.setFirerate(Math.floor(Math.random()*5));
			tempClip.setSpeed(Math.floor(Math.random()*5)+5);
			tempClip.setMovementType(Math.floor(Math.random()*3)+1);
			tempClip.setLocation(Math.floor(Math.random()*Stage.width),0-tempClip._height);

Set up the events for Move and Render and for when the Enemy leaves the screen and when it fires a shot:

                        this.addEventListener("Move",tempClip);
			this.addEventListener("Render",tempClip);
			tempClip.addEventListener("EventEnemyOffScreen",this);
			tempClip.addEventListener("EventEnemyFireMissile",this);

Cache the graphic as bitmap (for slight performnce boost since it do we not transform…but might be negiglible because we do add a Glow Filter), and add it to our array of Enemy objects.

                        tempClip.cacheAsBitmap = true;
			enemies.push(tempClip);
                    }
                 }

To handle the event that are broadcast from the Enemy we must create a couple functions. For EventEnemyOffScreen, this is fairly easy, and is an almost exact copy of EventMissileOffScreen. We create teo functions, one to handle the event, and a generic function to remove an Enemy from the enemies array(). We will use this function for our collision detection routines.


        function EventEnemyOffScreen(e:Object) {
		trace("Enemyoffscreen:" + e);
		e.enemy.removeMovieClip();
		removeEnemy(e.enemy);
	}

	function removeEnemy(enemy:MovieClip) {

		var tempClip:MovieClip = null;
		for (var i=enemies.length;i >= 0; i--) {
			tempClip=enemies[i];
			if (tempClip==enemy) {
				enemies[i].removeMovieClip();
				enemies.splice(i,1);
				this.removeEventListener("Move",tempClip);
				this.removeEventListener("Render",tempClip);
			}
		}
	}

Handling the EventEnemyFireMissile event is almost as easy, since we have seen nearly idential code for EventFireMissile from lesson #1. The only real difference is that we are using the enemymissiles array to hold the EnemyMissile objects instead of missiles array.


       function EventEnemyFireMissile(e:Object) {
		var tempClip:MovieClip;
		var md:Number =  getNextMissileDepth();
		tempClip =  parentTL.attachMovie("FEnemyMissile", "emissile" + md,  md);
		tempClip.setSpeed(10);
		tempClip.setFinishedY(Stage.height);
		tempClip.setLocation(e.startx, e.starty);
		tempClip.addEventListener("EventEnemyMissileOffScreen",this);
		this.addEventListener("Move",tempClip);
		this.addEventListener("Render",tempClip);
		tempClip.cacheAsBitmap = true;
		enemymissiles.push(tempClip);

	}	

	function EventEnemyMissileOffScreen(e:Object) {
		trace("enemy missile off screen:");
		this.removeEventListener("Move",e.missile);
		this.removeEventListener("Render",e.missile);
		e.missile.removeMovieClip();
		removeEnemyMissile(e.missile);
	}

	function removeEnemyMissile(missile:MovieClip) {
		var tempClip:MovieClip = null;
		for (var i=enemymissiles.length;i >= 0; i--) {
			tempClip=enemymissiles[i];
			if (tempClip==missile) {
				enemymissiles[i].removeMovieClip();
				enemymissiles.splice(i,1);
				this.removeEventListener("Move",tempClip);
				this.removeEventListener("Render",tempClip);
			}
		}
	}

Collisions

This is the most “controversial” part of this lesson because I’m going to show you the most simple way to test collisions with Flash. There are many other ways to do it, many of them we have documented on our site http://www.8bitrocket.com, but those are for you implement on your own time. We are going to use standard Flash “bounding box” collision detection utilizing the built-in hitTest() function.

The function for testiing collisions is a monster. I’m going to show you the whole thing before we jump into it, just so you have some idea of what we are getting into:


          function testCollisions() {
		for (var j=enemies.length;j >= 0; j--) {
			if (enemies[j].hitTest(player)) {
					createExplosion(enemies[j]._x,enemies[j]._y);
					enemies[j].removeMovieClip();
					enemies.splice(j,1);
					playerDestroyed();
				}
			for (var i=missiles.length;i >= 0; i--) {
				if (missiles[i].hitTest(enemies[j])) {
					enemies[j].setHits(enemies[j].getHits()+1);
					if (enemies[j].getHits() >= enemies[j].getMaxhits()) {
						createExplosion(enemies[j]._x,enemies[j]._y);
						scoreboard.setScore(scoreboard.getScore()+enemies[j].getFace()*100);
						enemies[j].removeMovieClip();
						enemies.splice(j,1);					

					} else {
						createSmallExplosion(missiles[i]._x,missiles[i]._y);
						scoreboard.setScore(scoreboard.getScore()+enemies[j].getFace()*10);
					}
					missiles[i].removeMovieClip();
					missiles.splice(i,1);

				}
			}
		}

		for (var i=missiles.length;i >= 0; i--) {
			for (var k=enemymissiles.length;k >= 0; k--) {

				if (missiles[i].hitTest(enemymissiles[k])) {
						createSmallExplosion(enemymissiles[k]._x,enemymissiles[k]._y);
						missiles[i].removeMovieClip();
						enemymissiles[k].removeMovieClip();
						missiles.splice(i,1);
						enemymissiles.splice(k,1);
						scoreboard.setScore(scoreboard.getScore()+10);
					}

				if (enemymissiles[k].hitTest(player)) {
						createExplosion(enemymissiles[k]._x,enemymissiles[k]._y);
						enemymissiles[k].removeMovieClip();
						enemymissiles[k].splice(k,1);
						playerDestroyed();							

					}
			 }
		 }
	}

The idea here is to limit the amount of calls to hitTest() that we perform on any given call to testCollisions(). The problem though, is that we have a bunch of different objects to test (Player, Missiles, EnemyMissiles, Enemies), and some need to be tested against each other, and some don’t.

Let’s suppose that at a given time in the game, we have the following on the screen:

  • 1 player
  • 5 player missiles
  • 10 Enemies
  • 15 EnemyMissiles

How many collisions do you suppose we need to test?

  • 1 Player against 10 Enemies (10 tests)
  • 5 player missiles against 10 Enemies (50 tests)
  • 15 enemy missiles against 1 Player (15 tests)
  • 5 player missiles against 15 enemy missiles (75 tests)

So, we would have 150 seperate calls to hitTest(). hat is, with the basic method shown here. There are other methods that split the screen into quadrants, and only perform test among objects in that quadrant. That kind of process is out-of-scope for this lesson.

However, even though we are not covering screen quadrants in this lesson, we can still perform a small trick to limit some of the hitTest() calls in a similar way. Since our Player is always at the bottom of the screen, we can limit calls to hitTest() simply by not performing them is the Enemy or EnemyMissile does not have possibility of hitting the Player. This is a bit of a hack, but, if none of the Enemies or EnemyMissiles are near the player (vertically), we could eliminate 25 hitTest() calls. This code will perform that kind of action:

if (enemies[j]._y > Stage.height - (player._height *2)) {

}

Now to further speed-up processing, what we want to try to do is limit the amount of iterations we run through combining like-tests in a single loop (if possible). This won’t limit the tests required, but it will shorten our processing time by limited the amount of code that is executed.

First off, we know we must test each enemy against the Player. That is 10 interations. We also need to test each Enemy against each player missle. That is 50 iterations, for a total of 60 interations However, if we combine the two into one loop, we should be able to get rid of 10 iterations:


             for (var j=enemies.length;j >= 0; j--) {
			if (enemies[j]._y > Stage.height - (player._height *2)) {

				if (enemies[j].hitTest(player)) {
						createExplosion(enemies[j]._x,enemies[j]._y);
						enemies[j].removeMovieClip();
						enemies.splice(j,1);
						playerDestroyed();
					}
			}
			for (var i=missiles.length;i >= 0; i--) {
				if (missiles[i].hitTest(enemies[j])) {
					enemies[j].setHits(enemies[j].getHits()+1);
					if (enemies[j].getHits() >= enemies[j].getMaxhits()) {
						createExplosion(enemies[j]._x,enemies[j]._y);
						scoreboard.setScore(scoreboard.getScore()+enemies[j].getFace()*100);
						enemies[j].removeMovieClip();
						enemies.splice(j,1);					

					} else {
						createSmallExplosion(missiles[i]._x,missiles[i]._y);
						scoreboard.setScore(scoreboard.getScore()+enemies[j].getFace()*10);
					}
					missiles[i].removeMovieClip();
					missiles.splice(i,1);

				}
			}
		}

Likewise, if we combine the test of enemy missiles hitting the Player (15), and player missiles hitting the enmhy missiles (75), we should be able to get rid of 15 iterations.

               for (var i=missiles.length;i >= 0; i--) {
			for (var k=enemymissiles.length;k >= 0; k--) {

				if (missiles[i].hitTest(enemymissiles[k])) {
						createSmallExplosion(enemymissiles[k]._x,enemymissiles[k]._y);
						missiles[i].removeMovieClip();
						enemymissiles[k].removeMovieClip();
						missiles.splice(i,1);
						enemymissiles.splice(k,1);
						scoreboard.setScore(scoreboard.getScore()+10);
					}
				if (enemymissiles[k]._y > Stage.height - (player._height *2)) {

					if (enemymissiles[k].hitTest(player)) {
							createExplosion(enemymissiles[k]._x,enemymissiles[k]._y);
							enemymissiles[k].removeMovieClip();
							enemymissiles[k].splice(k,1);
							playerDestroyed();
						}
				}
			 }
		 }

25 Iterations might not sound like much, but if combined with the possible elimination of 25 hitTests using our _y trick, and you could see a possible 17% increase in efficiency. With many more objects on the screen, this could be very significant.

For further reference on collision detection, here are links to our articles about pixel-level collision detection from our 8bitrocket site:

Advanced Flash 8 Game Programming : Pixel level collision detection

Advanced Flash 8 Game Programming : Pixel level collision detection Part II (or let’s add Bitmap animation caching)

Advanced Flash 8 Game Programming : Pixel level collision detection Part III (or Let’s start a real game)

Notice in the code above, that there are several functions called when hitTest() is successful.

createExplosion(enemies[j]._x,enemies[j]._y) is called when an Enemy is destroyed or when the Player is destroyed. This function simply attach an FExplosion object from the library, and put it into our explosions array.


        function createExplosion(px,py) {
		var tempClip:MovieClip;
		var ed:Number =  getNextExplodeDepth();
		tempClip =  parentTL.attachMovie("FExplode", "explode" + ed,  ed);
		tempClip._x = px;
		tempClip._y = py;
		tempClip._rotation = Math.floor(Math.random() * 360);
		explosions.push(tempClip)
	}

createSmallExplosion(missiles[i]._x,missiles[i]._y); is called when a Missile hits an Enemy, but the Enemy is not destoyed. Other than attaching FExplosionSmall fro mthe library, the code is essentially the same as createExplosion().


       function createSmallExplosion(px,py) {
		var tempClip:MovieClip;
		var ed:Number =  getNextExplodeDepth();
		tempClip =  parentTL.attachMovie("FExplodeSmall", "explode" + ed,  ed);
		tempClip._x = px;
		tempClip._y = py;
		tempClip._rotation = Math.floor(Math.random() * 360);
		explosions.push(tempClip)
	}

scoreboard.setScore(scoreboard.getScore()+10); is called to award the Player points for the Enemies they destroy.

playerDestroyed(); is called to start the process of removing the Player from the screen, and re-initializing the game. The playerDestroyed() function looks like this:


         function playerDestroyed() {

		if (!player.getDisappear()) {
			scoreboard.setCPUs(scoreboard.getCPUs()-1);
			player.setDisappear(true);
			for (var i = enemies.length; i >= 0; i--) {
				enemies[i].setDisappear(true);
			}
			for (var i = missiles.length; i >= 0; i--) {
				missiles[i].setDisappear(true);
			}
			for (var i = enemymissiles.length; i >= 0; i--) {
				enemymissiles[i].setDisappear(true);
			}			

		}

	}

The same “disappear” functionality that was created for the Enemy objects has now been added to the Player. After we test to see if the the Player is not already disappearing (just in case two missiles hit the ship in close proximity, we don’t want to subtract more than one life), we call the setCPUs() function in ScoreBoard and decrement the count by one. Then we go through the process of setting all the Enemies, Missiles and EnemyMissiles to disappear. This will help smooth the jarring transition from fighting the enemies to restring tthe level or the entire game. When the Player is finished with its disappear process, it broadcasts the EventPlayerRemoved event.

The EventPlayerRemoved() function in HomeWars.as looks like this:

         function EventPlayerRemoved(e:Object) {
		if (scoreboard.getCPUs() <= 0) {
			gameState = STATE_END_SCREEN;
		} else {
			gameState = STATE_SHOW_TEXT_INTRO;
		}
	}

This function is very simple. If the Player has no lives (CPUs) left, the gameState is set to STATE_END_SCREEN, and we end the game. If not, the gameState is set to STATE_SHOW_TEXT_INTRO, and the process of intializing the Player starts again.

Removing Explosions

There is one more thing that must be done by our STATE_PLAY() function in it’s loop. We need to clean-up any explosions that have finished running through their animations. This is done by a call to removeExplosions():


       function removeExplosions() {
		//trace("explosions:" + explosions.length);
		var tempClip:MovieClip = null;
		for (var i=explosions.length;i >= 0; i--) {
			tempClip=explosions[i];
			if (tempClip.exp._currentframe >= tempClip.exp._totalframes) {
				explosions[i].removeMovieClip();
				explosions.splice(i,1);
			}
		}
	}

Since the exposions are not associated with a class, the only way we know that there finished displayed is if they have compled all the frames in their animation. To test this, we use the following code:

        if (tempClip.exp._currentframe >= tempClip.exp._totalframes) {
		explosions[i].removeMovieClip();
		explosions.splice(i,1);
	}

Ending The Game

game-over

To end the game we need to do the following:

      1. Remove all outstanding objects from the screen.
      2. Create an EndScreen object.
      3. Set the finalScore variable of the EndScreen to the Player’s score.
      4. Display the EndScreen.
      5. Wait unti lthe EndScreen is closed.
      6. Set gameState to STATE_INIT and restart the game.

Here is the code we will use to do all those things. We have seen most of this before, so you should be very familiar with how all this works:

       function fSTATE_END_SCREEN() {
		for (var i = enemies.length; i >= 0; i--) {
				removeEnemy(enemies[i]);
			}
		for (var i = missiles.length; i >= 0; i--) {
				removeMissile(missiles[i]);
		}
		for (var i = enemymissiles.length; i >= 0; i--) {
				removeEnemyMissile(enemymissiles[i]);
		}					

		endScreen = parentTL.attachMovie("FEndScreen","endscreen", END_SCREEN_DEPTH);
		endScreen.setLocation(0,0);
		endScreen.setFinalScore(scoreboard.getScore());
		endScreen.addEventListener("EventCloseEndScreen", this);
		gameState = STATE_WAIT_FOR_CLOSE;
	}

	function EventCloseEndScreen() {
		endScreen.removeMovieClip();
		gameState = STATE_INIT;

	}

Test The Game

Now we are ready to test this version of the game.

Remember, the game is now completely random, and because of this, very difficult. In our next lesson we will talk about levels, balancing the game, power-ups, and adding sound. You can download all the game files here.

Read the rest of the series: ‘Anatomy of a Flash Game’

  1. Anatomy of a Flash Game: Lesson 1 - Setting up the game
  2. Anatomy of a Flash Game: Lesson 2 - Creating Enemies & The Game Environment
  3. Anatomy of a Flash Game: Lesson 3 - MochiAds, MochiBot and MochiAds Leaderboards