We are now on Twitter!


Penguin wearing teacher's hatGame Development With FlashDevelop Part 3

Aliens

Welcome to part 3 of our tutorial series. In this installment, a fleet of alien spaceships is going to make an appearance.

We're going to use the graphic below for our alien spaceships. You'll notice that, unlike the player's spaceship, here we have two animation frames in the one graphic, i.e. two slightly different versions of the same spaceship so that we can animate the spaceship by switching between the two.

Recall how in part 2, we created a subclass of Actor called Player. We're going to create another subclass of Actor called Alien...

package  
{
	import flash.display.*;
	import flash.geom.*;
	
	public class Alien extends Actor
	{	
		[Embed(source="../images/alien.png")]
		private var i:Class;
		
		private var frameCounter:Number;
		private var frameCounterMax:Number;
		private var numberOfFrames:int;
		private var frameRect:Rectangle;
		
		public function Alien(X:Number, Y:Number, Speed:Number) 
		{
			var image:DisplayObject= new i();
			graphic = new BitmapData(image.width, image.height, true, 0x00000000);
			graphic.draw(image);
			
			height = image.height;
			width = image.width / 2;			
			heightHalved = height / 2;
			widthHalved = width / 2;
			
			x = X;
			y = Y;
			speed = Speed;
			
			frameCounter = 0;	
			frameCounterMax = 10;
			numberOfFrames = 2;			
		}
		
		public override function Render():void
		{
			frameRect = new Rectangle( 0, 0, width, height );
			State.currentState.buffer.copyPixels( graphic, frameRect, new Point(x - widthHalved, y - heightHalved) );
		}
		
		public override function Update() :void
		{			
		}	
	}
}

Most of this code should be familiar from the work done in Part 2 where the Player class was introduced but there are some significant differences. Because an Alien's graphic will now contain two versions of the spaceship, we cannot use graphic.rect in the Render function or we will get two spaceships next to each other on screen. Also the width variable should contain the width of just one frame so we have to use width = image.width / 2;

To replace graphic.rect, a new variable, frameRect, of type Rectangle has been introduced, and also variables called frameCounter, frameCounterMax and numberOfFrames which will control when we switch between the animation frames. For now, we will ignore these variables and hard code frameRect to be a rectangle containing the left half of the graphic. When we have some aliens appearing on screen and can see what we are doing, we will use these variables to animate the aliens.

To add some aliens to the game, we need a variable in PlayState where we can store a collection of instances of Alien. There are different ways to do this, for example using ArrayCollections, but we are going to use a Vector.

private var aliens:Vector.<Alien>;

This is basically an array which we have defined as being able to only contain elements of type Alien. If we had some subclasses of Alien, then they too could be stored in the vector.

In the constructor, initialize the Vector and populate it with some Aliens. The push function is used to add an object to a Vector. We're going to use a loop to make it easy to add a large number of Aliens to the Vector. As we want them to appear in a uniform grid, we can define a few variables for how many rows and columns we want, the start position and the spacing between them.

public function PlayState() 
{
	player = new Player( 30, 350, 1.5 );
	
	aliens = new Vector.<Alien>();

	var rows:int = 4;
	var columns:int = 8;
	var rowSpacing:int = 40;
	var columnSpacing:int = 40;
	var startX:Number = 30;
	var startY:Number = 30;
	var speed:Number = 0.5;
	
	for ( var i:int = 0; i < rows * columns; i++ )
	{
		var currentColumn:int = i % columns;
		var x:int = startX + currentColumn * columnSpacing;
		var currentRow:int = i / columns;
		var y:int = startY + currentRow * rowSpacing;
		
		aliens.push( new Alien( x, y, speed ) );				
	}
}

We now have a collection of aliens but we can't see them. Update PlayState's Render function to iterate through the aliens Vector and call the Render function of each alien as follows:

public override function Render():void
{
	buffer.fillRect( buffer.rect, 0x000000 );
	player.Render();
	
	for each (var alien:Alien in aliens) 
	{
		alien.Render();
	}
}

Run the application and you'll see a fleet of aliens hovering above the player. Now we have to make the aliens move. Whilst we are editing the PlayState class, change the Update function to call the aliens' Update functions as we did for the Render function:

public override function Update():void
{
	player.Update();
	
	for each (var alien:Alien in aliens) 
	{
		alien.Update();
	}
}

How are we going to move the alien fleet around? We want the aliens to always stay in formation and move from left to right across the screen then change direction and head back again. As all aliens move in the same direction, we'll assign a static variable to the Alien class to store the direction. We'll also define a couple of constants named LEFT and RIGHT with values of -1 and 1 respectively.

public static const LEFT:int = -1;
public static const RIGHT:int = 1;
public static var direction:int = RIGHT;

public override function Update() :void
{				
	x += speed * direction;
}

The aliens will now move to the right and disappear off the screen. We need to check when the fleet has reached the edge of the screen and make it change direction. The position of each alien needs to be checked before the Update function runs for any of the aliens - the first alien in the top left of the fleet might be in the middle of the screen and able to move right but an alien on the right hand side might already be at the edge of the screen.

To deal with this, we need another function in Alien which we will call CheckForDirectionChange. This needs to be called for each Alien from the PlayState Update function before the loop where the Aliens' Update function is called:

public override function Update():void
{
	player.Update();
	
	for each (var alien:Alien in aliens) 
	{
		alien.CheckForDirectionChange();
	}
	
	for each (alien in aliens) 
	{
		alien.Update();
	}
}

Note that in the second for each loop, var alien:Alien can be replaced with just alien as we have already declared the alien variable in the first for each loop. The CheckForDirectionChange function itself just needs to check the x and y coordinates and current direction and change the direction if the alien is too close to the edge of the screen and still heading towards the edge.

public function CheckForDirectionChange():void
{
	if ( x < 30 && direction == LEFT )
		direction = RIGHT;
	if ( x > 570 && direction == RIGHT )
		direction = LEFT;			
}

The values of 30 and 570 can of course be changed depending upon how close to the edge you want the aliens to go. The aliens will now keep moving back and forth in formation without going off the edge of the screen.

Instead of making the aliens simply reverse direction, let's make them move down the screen before setting off across the screen again. We could accomplish this in a number of ways but the easiest way would be, whenever a direction change is required, set a counter to a value > 0 and whilst that counter is > 0, the Update function could move the aliens down and decrement the counter.

We are going to use a static variable for the counter as it will apply to all of the aliens, and we'll also use a static boolean variable to ensure that the counter only gets decremented once per frame.

private static var verticalMoveCounter:int;
private static var decrementVerticalMoveCounterFlag:Boolean;
		
public function CheckForDirectionChange():void
{
	decrementVerticalMoveCounterFlag = false;
	if ( x < 30 && direction == LEFT )
	{
		direction = RIGHT;
		verticalMoveCounter = 20;
	}
	if ( x > 570 && direction == RIGHT )
	{
		direction = LEFT;	
		verticalMoveCounter = 20;
	}		
}
	
public override function Update() :void
{				
	if ( verticalMoveCounter > 0 )
	{
		if ( !decrementVerticalMoveCounterFlag )
		{
			verticalMoveCounter--;
			decrementVerticalMoveCounterFlag = true;
		}
		y += speed;
	}
	else
		x += speed * direction;
}	

Now lets make use of that second animation frame. Add a couple of extra lines to the Alien's Update function so that each time Update is called it increments the frameCounter and then if frameCounter hits frameCounterMax, it resets to zero.

public override function Update() :void
{				
	if ( verticalMoveCounter > 0 )
	{
		if ( !decrementVerticalMoveCounterFlag )
		{
			verticalMoveCounter--;
			decrementVerticalMoveCounterFlag = true;
		}
		y += speed;
	}
	else
		x += speed * direction;
	
	frameCounter++;
	if ( frameCounter >= frameCounterMax )
		frameCounter = 0;
}	

Then change the Render function so it calculates which section of the alien spaceship graphic should be displayed each frame. Multiplying frameCounter by numberOfFrames and then dividing by frameCounterMax will give a value, that when rounded down, will give us the zero based number of the animation frame we want to render. This can be multiplied by the width of a single frame to give the correct x value for the top left corner of the rectangle containing the desired frame.

public override function Render():void
{
	var frameX:int;			
	frameX = width * (int)( frameCounter * numberOfFrames / frameCounterMax );
			
	frameRect = new Rectangle( frameX, 0, width, height );
	State.currentState.buffer.copyPixels( graphic, frameRect, new Point(x - widthHalved, y - heightHalved) );
}	

Experiment with changing the value of frameCounterMax to increase or decrease the speed of the animation.

The animation here is very simple but the same principles can be applied to more complex situations, for example, animating a human figure with many frames of animation so that it appears to be walking.

When you run the application now, you should have a result like this:

At the moment, the aliens make their way inexorably down the screen and there is nothing the player can do about it. In the next installments, we will look at adding missiles and collision detection so that the player can stop them!