Creating an Asteroids Flash Game Using the Display List Part 4: Detecting Collisions and Creating Particle Explosions

Chris Moeller Web Asteroids Flash Game Part 4- Adding Explosions

In the last section, we created an asteroids class to easily create a variety of asteroids, as well as move them around on screen.

In this section, we’ll create a way to detect collisions between the asteroids and the ship or bullets, and create particle explosions when the collisions happen, as well as break apart asteroids upon collision.

Adding Collision Detection to Bullets, Asteroids and the Ship Class

We now have to detect when a collision has happened between and asteroid and either the ship or a bullet, and create an explosions when one has happened.

Since only asteroids need to be interacted with, we will only need to do collision detection on them, but against both the ship and bullets.

We’ll want to create an explosion object to be created whenever a collision has occured. So create a new class with a base class of type MovieClip in the Entities folder again.

Explosion.as

package Entities 
{
	import flash.display.MovieClip;
	import flash.geom.Point;
	/**
	 * ...
	 * @author Chris Moeller
	 */
	public class Explosion extends MovieClip
	{
		private var radius:Number;
		public var finished:Boolean;
		private var size:int;
		
		private var random_offsets:Array;
		private var random_sizes:Array;
		
		private const num_points:int = 12;
		public var life:Number;
		public var max_life:Number;
		
		public function Explosion(x:int, y:int, max_life:Number, size:int = 0 ) 
		{
			this.x = x;
			this.y = y;
			this.size = size;
			this.life = Game.current_time;
			this.max_life = max_life;
			
			graphics.lineStyle(2, 0x333333);
			finished = false;
			
			random_offsets = new Array();
			random_sizes = new Array();
			
			var high:Number = 10 ;
			var low:Number = 0;
			
			var high2:int = 3;
			var low2:int = 1;
			
			for (var i:int = 0; i < num_points; i++)
			{
				var random_x:Number = Math.floor(Math.random() * (1 + high - low)) + low+i;
				var random_y:Number = Math.floor(Math.random() * (1 + high - low)) + low+1;	
				random_offsets.push(new Point(random_x, random_y));
				random_sizes.push(Math.floor(Math.random() * (1 + high2 - low2)) + low2);
			}

		}
		public function Update():void 
		{
			//going to want to draw points around the outside of a circle of radius size, and have it increasing
			//so need to go around the circle (centered at x,y=0,0) and draw lines outward
			graphics.clear();
			var selected_color:int = 16-Math.round((Game.current_time-life) / max_life*16);
			var color_val:String = selected_color.toString(16);
			
			var color:uint  = uint("0x" + color_val + color_val + color_val + color_val + color_val + color_val);
			graphics.beginFill(color);
			for (var i:int = 0; i < num_points; i++)
			{				
				graphics.drawRect(
				size * Math.cos(((i + 1) * 360 / num_points) * (Math.PI / 180)) + random_offsets[i].x, 
				size * Math.sin(((i + 1) * 360 / num_points) * (Math.PI / 180)) + random_offsets[i].y, 
				random_sizes[i], random_sizes[i]);
			}
			graphics.endFill();
			size += ((max_life-(Game.current_time-life))/max_life)*5;
		}
		
	}

}

Explosion Function/Constructor: We create two arrays- one to hold all the random offsets of the particles we will create, and for the random sizes of the particles.

Update Function: The main objective of this function is to update the position of all the particles of the explosion, with the size of the explosion increasing each frame, as well as the color and alpha value fading each time.

Next, we also have to change "game.as" to actually run the collision detection for each frame.
We have to handle when a collision has happened by creating an explosion, removing the asteroid and bullet, and creating two smaller asteroids, if applicable.

Game.as

package
{
	import Entities.Asteroid;
	import Entities.Bullet;
	import Entities.Explosion;
	import Entities.Ship;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.MovieClip;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	
	import flash.utils.getTimer;

	public class Game
	{
		public var stage:Stage;
		public var ship:Ship;
		
		private const LEFT:int = 37;
		private const UP:int = 38;
		private const RIGHT:int = 39;
		private const SPACE:int = 32;	
		private const DOWN:int = 40;
		
		public static var stageWidth:int;
		public static var stageHeight:int;
		
		public static var current_time:Number; //used to track the time of the current frame
		private var bullets:Array; //used to hold all the bullets
		private var firing_delay:Number; //the delay before creating a new bullet when the space bar is held down
		private var last_fired:Number; //the time the last bullet was created
		private var bullets_max_life:Number; //the max life of the bullets
		private var space_down:Boolean; //Either set to true if the spacebar is being held down, or false if released
		
		private var asteroids:Array;
		private var explosions:Array;
		
		public function Game(stage:Stage)
		{
			this.stage = stage;
			stageHeight = stage.stageHeight;
			stageWidth = stage.stageWidth;
			trace("Game created");
			
			//create the ship
			ship = new Ship(stage.stageWidth / 2 - 5, stage.stageHeight / 2 - 10);
			stage.addChild(ship);
			
			//now for input
			stage.addEventListener(KeyboardEvent.KEY_DOWN, KeyDown);
			stage.addEventListener(KeyboardEvent.KEY_UP, KeyUp);	
			
			bullets = new Array();
			firing_delay = 200;
			last_fired = 0;
			bullets_max_life = 1000;
			space_down = false;
			
			asteroids = new Array();
			//setup four first asteroids
			asteroids.push(new Asteroid(stageWidth / 5, 2 * stageHeight / 3, 0, 0, 0));
			asteroids.push(new Asteroid(stageWidth / 5, stageHeight / 4, 0, 1, 0));
			
			asteroids.push(new Asteroid(2*stageWidth / 3, 1 * stageHeight / 3, 0, 2, 0));
			asteroids.push(new Asteroid(2*stageWidth / 3, 5 * stageHeight / 5, 0, 3, 0));
			
			for (var i:int = 0; i < asteroids.length; i++ )
				stage.addChild(asteroids[i]);
				
			explosions = new Array();
			
		}
		public function Update(e:Event):void
		{
			current_time = getTimer();
			
			ship.Update();
			
			if (space_down && current_time-last_fired > firing_delay)
			{
				var angle_rad:Number = ship.rotation * Math.PI / 180;
				bullets.push(new Bullet(ship.x, ship.y, current_time, angle_rad));
				last_fired = current_time;
				stage.addChild(bullets[bullets.length - 1]);
			}
			
			var bullets_length:int = bullets.length - 1;
			for (var i:int = bullets_length; i > -1;i-- )
			{
				bullets[i].Update();
				if (current_time-bullets[i].life > bullets_max_life)
				{
					stage.removeChild(bullets[i]);
					bullets.splice(i, 1);
				}
			}
			
			for (var k:int = asteroids.length-1; k > -1;k-- )
				asteroids[k].Update();

			//removing explosions
			var explosions_length:int = explosions.length - 1;
			for (var m:int = explosions_length; m > -1; m-- )
			{
				explosions[m].Update();
				if (current_time-explosions[m].life > explosions[m].max_life)
				{
					stage.removeChild(explosions[m]);
					explosions.splice(m, 1);
				}
			}

			//now we'll check collisions
			var asteroids_length:int = asteroids.length - 1;
			for (var k1:int = asteroids_length; k1 > -1;k1-- )
			{
				var bullets_length2:int = bullets.length - 1;
					for(var k2:int = bullets_length2; k2 > -1;k2-- )
						if (MovieClip(asteroids[k1]).hitTestObject(bullets[k2]))
						{
							stage.removeChild(bullets[k2]);
							bullets.splice(k2, 1);
							DestroyAsteroid(k1);
						}
						
				if (MovieClip(asteroids[k1]).hitTestObject(ship)&&ship.visible)
				{
					ship.visible = false;	
					
					explosions.push(new Explosion(
					ship.x + ship.width / 2,
					ship.y + ship.height / 2, 1000, ship.width / 4));
					
					stage.addChild(explosions[explosions.length - 1]);
					
					DestroyAsteroid(k1);							
				}
			}
		}
		
		public function DestroyAsteroid(asteroid_hit:int):void
		{
			explosions.push(new Explosion(
			asteroids[asteroid_hit].x + asteroids[asteroid_hit].width / 2,
			asteroids[asteroid_hit].y + asteroids[asteroid_hit].height / 2, 1000, asteroids[asteroid_hit].width/4));	
			
			stage.addChild(explosions[explosions.length - 1]);
			
			//now delete the old asteroid, and add 2 new ones in it's place if there are any more sizes left
			var old_asteroid:Asteroid = asteroids[asteroid_hit];
			//if there are more sizes to chose from
			if (old_asteroid.size != Asteroid.Sizes.length - 1)
			{
				
				var rand_dir:int = Math.floor(Math.random() * (1 + Asteroid.Directions.length - 1 ));
				
				var rand_dir2:int = rand_dir - 2;
				if (rand_dir - 2 < 0)
					rand_dir2 = rand_dir + 2;
				
				var rand_type:int = Math.floor(Math.random() * (2 ));
				var rand_type2:int = Math.floor(Math.random() * (2 ));
				
				//add 2 new asteroids at half the size
				asteroids.push(new Asteroid(
				old_asteroid.x, 
				old_asteroid.y, 
				old_asteroid.size + 1, 
				rand_dir, 
				rand_type));
				stage.addChild(asteroids[asteroids.length - 1]);
				
				asteroids.push(new Asteroid(
				old_asteroid.x, 
				old_asteroid.y, 
				old_asteroid.size + 1, 
				rand_dir2, 
				rand_type2));
				stage.addChild(asteroids[asteroids.length - 1]);
				
				trace("creating two new asteroids with dir = " + rand_dir + ", " + rand_dir2);
			}
			stage.removeChild(asteroids[asteroid_hit]); 
			asteroids.splice(asteroid_hit, 1);
		}
		
		public function KeyDown(e:KeyboardEvent):void
		{
			if (ship.visible == false)
				return;
				
			if(e.keyCode==RIGHT)
				ship.rotation_amount = 15;				
			if(e.keyCode==LEFT)
				ship.rotation_amount = -15;
			if(e.keyCode==UP)
				ship.thrust = 1;
			if(e.keyCode==DOWN)
				ship.thrust = -1;
			if(e.keyCode==SPACE)
				space_down = true;	
		}
		public function KeyUp(e:KeyboardEvent):void
		{
			if(e.keyCode==RIGHT||e.keyCode==LEFT)
				ship.rotation_amount = 0;				
				
			if(e.keyCode==UP||e.keyCode==DOWN)
				ship.thrust = 0;	
			if(e.keyCode==SPACE)
				space_down = false;
		}
	}
}

Line 72: We create an array for the explosions.

Line 103-113: We check for any explosions that are too old and remove them, from the display and also our array.

Line 115-141: We'll loop through all the asteroids, then looping through the bullets, use flash's built in 'hitTestObject' function to check for a collision.
If so, we'll remove the bullet, and call our function to destroy the asteroid.

After that, we check if there is a collision with the ship, and if so make it invisible, create an explosion for the ship, and call the 'DestroyAsteroid' function.

Line 143: This is the function we use for creating the explosion on an asteroid, removing it from the display and our array, and creating two smaller asteroids (if there are any smaller ones in our 'sizes' array in our asteroid class).

Line 145: First we create a new explosion, and add it to the stage to be displayed.

Line 152: We'll create a temporary variable for the asteroid that was hit.

Line 154: Check if this is the last(smallest) size in the asteroids array, if it is not, we'll need to create 2 smaller asteroids.

Line 185-186: Lastly, we'll remove the asteroid that was hit from our asteroids array, and the display list.

Tutorial Demo

In the next section we’ll create a way to keep track of the lives and score, as well as create the levels, the ability to pause and restart, and a basic menu GUI.

Download the source Code and please leave me any comments or feedback you have.

Other Articles in this Series

Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *