We are now on Twitter!


Penguin wearing teacher's hatGame Development With FlashDevelop Part 2

Actors

In part 1, we built a basic framework. In this tutorial, we will build upon that framework by adding a spaceship for the player to control.

The first thing we will do is to create an Actor class. This will be an abstract base class for all of the objects that we will populate our game world with. We'll give it a few variables which almost all objects or actors in the game will have - x and y coordinates, speed, height and width, and finally a BitmapData object which will have the graphic representing the actor loaded into it. Also we will create a few empty functions - Update() and Render() which, in subclasses of Actor, will handle updating the state of the actor each frame and drawing the object.

package  
{
	import flash.display.*;
	
	public class Actor 
	{
		public var x:Number;
		public var y:Number;

		public var speed:Number;
		
		public var width:int;
		public var height:int;
		public var graphic:BitmapData
		
		public function Actor() 
		{
			
		}
		
		public function Render():void
		{
			
		}
		
		public function Update() :void
		{
			
		}		
	}
}

In preparation for creating a subclass of Actor, we're going to need a graphic of a spaceship. Create a folder named "images" in your project folder and unzip the contents of the zip file downloaded in part 1 into the folder. The player.png file is the spaceship graphic that will represent the player.

Now add another class to the project called Player. This class will extend Actor. Add empty functions overriding the Render and Update functions of Actor. We'll also pass some parameters to the contructor for the x and y coordinates and the speed of the player's ship. Also we'll need to import the flash.display and flash.geom classes:

package  
{
	import flash.display.*;
	import flash.geom.*;
	
	public class Player extends Actor
	{		
		public function Player(X:Number, Y:Number, Speed:Number) 
		{
			x = X;
			y = Y;
			speed = Speed;
		}
		
		public override function Render():void
		{
			
		}
		
		public override function Update() :void
		{
			
		}	
	}
}

Next we need to somehow reference the graphic of the spaceship. Add the following code to the player class:

		[Embed(source="../images/player.png")]
		private var i:Class;

The syntax for this may seem a bit odd if you are not used to AS3 but basically it creates a class i which will contain the graphic embedded in the line above.

Add the following lines to the constructor:

var image:DisplayObject= new i();
graphic= new BitmapData(image.width, image.height, true, 0x00000000);

In the first line, we've created an instance of the class holding our spaceship graphic. The second line creates a BitmapData object with the height and width of the spaceship graphic, transparency support set to true and each pixel of the bitmap filled in the with transparent pixels. Now we can draw the spaceship onto the player's bitmap:

graphic.draw(image);

We will also store the height and width of the image in the height and width variables. The completed constructor now looks like this:

public function Player(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;
			
	x = X;
	y = Y;
	speed = Speed;
}

Now we'll update the Render() function to draw the player's bitmap onto the current State object's bitmap.

public override function Render():void
{
	State.currentState.buffer.copyPixels( graphic, graphic.rect, new Point(x - (width / 2 ), y - (height / 2)) );
}

The first parameter is the bitmap we want to draw onto the back buffer. The next is a rectangle representing the section of that bitmap that we wish to draw. In this case we want to draw the entire bitmap. Then the x and y coordinates at which we want to draw it. By subtracting half the height and width from the x and y coordinates, we get the bitmap drawn centred upon the x and y position of the player. It is a matter of individual preference whether you want the x and y coordinates to represent the top left corner of the graphic or the centre of the graphic.

Now lets add a Player object to the PlayState class. We'll add a new variable of type Player and then create an instance of Player in PlayState's constructor:

private var player:Player;
		
public function PlayState() 
{
	player = new Player( 30, 350, 1.5 );
}

Now we'll make PlayState's Render function call player's Render function.

		
public override function Render():void
{
	buffer.fillRect( buffer.rect, 0x000000 );	
	player.Render();
}

The final step to making the spaceship graphic appear on screen is to make sure that the current State's Render() function is called each frame in our mxml file so add an additional line to the EnterFrame function:

public function enterFrame(event:Event):void
{
	State.currentState.Render();
			
	canvas.graphics.clear();
	//canvas.graphics.beginFill( 0xFF0000, 1 );
	canvas.graphics.beginBitmapFill(State.currentState.backBuffer, null, false, false);
	canvas.graphics.drawRect(0, 0, this.width, this.height);
	canvas.graphics.endFill();
}

Run the application and you should see the spaceship in the bottom left hand corner of the screen.

Now let's make the spaceship move. To start with, we'll just make the spaceship move to the right across the screen. Change Player's Update function as follows:

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

Call the player's Update function from PlayState's Update function:

public override function Update():void
{
	player.Update();
}

And finally in the EnterFrame function in the mxml file, just before the call to the current State's Render function, we'll call the current State's Update function:

public function enterFrame(event:Event):void
{
	State.currentState.Update();			
	State.currentState.Render();
			
	canvas.graphics.clear();
	canvas.graphics.beginBitmapFill(State.currentState.backBuffer, null, false, false);
	canvas.graphics.drawRect(0, 0, this.width, this.height);
	canvas.graphics.endFill();
}

Run the application and the spaceship will now make its way across the screen and disappear off the right hand side.

Some optimisations

Looking at the Player class Render() function, every frame the height and width get divided by two but the results of these calculations will always be the same. It would make sense to calculate these at the start and store the values. As this is likely to be something that other subclasses of Actor will do, we will add two new variables to the Actor class:

public var widthHalved:Number;
public var heightHalved:Number;

In Player's constructor we can calculate these values:

heightHalved = height / 2;
widthHalved = width / 2;

Now these variables can replace the calculations in the Render function:

public override function Render():void
{
	State.currentState.buffer.copyPixels( graphic, graphic.rect, new Point(x - widthHalved, y - heightHalved) );
}

Keyboard controls

Now we will look at keyboard events and how we can make the spaceship move around the screen using the arrow keys.

Add a new class called KeyboardManager. In the KeyboardManager class, add two empty functions keyDown and keyUp that take a KeyboardEvent as a parameter. We'll make these static as we're not going to be creating any instances of this class. Also we need to import flash.ui.keyboard and flash.events.*.

package  
{
	import flash.events.*;
	
	public class KeyboardManager 
	{
		
		public function KeyboardManager() 
		{
			
		}
		
		public static function KeyDown(event:KeyboardEvent):void
		{				
				
		}
		
		public static function KeyUp(event:KeyboardEvent):void
		{				
				
		}
		
	}
}

In the mxml file, create a function addEventListeners which will add in a couple of event listeners so that the KeyUp and KeyDown functions get called in response to keys being pressed and released. Also this function needs to call canvas.setFocus() so that the game receives the keyboard events. Go to the creationComplete function and make it call the function we've just created.

public function creationComplete():void
{				
	addEventListeners();		
}
		
public function addEventListeners():void
{
	canvas.setFocus();
	this.addEventListener(KeyboardEvent.KEY_DOWN, KeyboardManager.KeyDown);
	this.addEventListener(KeyboardEvent.KEY_UP, KeyboardManager.KeyUp);
}

Now go back to the KeyboardManager class and add a static variable to hold a dictionary storing which keys are pressed. We'll need another import statement to load in the Dictionary class.

package  
{
	import flash.events.*;
	import flash.utils.*;	
	
	public class KeyboardManager 
	{
		public static var keysPressed:Dictionary = new Dictionary();

Next make the KeyDown and KeyUp functions set values of true and false respectively in the keysPressed Dictionary to keep track of which keys are currently pressed.

public static function KeyDown(event:KeyboardEvent):void
{				
	keysPressed[event.keyCode] = true;	
}
		
public static function KeyUp(event:KeyboardEvent):void
{				
	keysPressed[event.keyCode] = false;		
}

Now that we have a means of tracking which keys are pressed, we can change the Player class Update function to use the KeyboardManager and move the player according to which keys are pressed.

The Player class needs a couple of additional import statements adding:

import flash.ui.Keyboard;
import mx.core.Application;

Then replace the old Update function with the new version below. Note the checks on the value of x to prevent the spaceship from moving off the edges of the screen.

public override function Update() :void
{
	if ( KeyboardManager.keysPressed[Keyboard.LEFT] ) x -= speed;
	if ( KeyboardManager.keysPressed[Keyboard.RIGHT] ) x += speed;
	
	
	if ( x - widthHalved < 0 ) x = widthHalved;
	if ( x + widthHalved > Application.application.width ) x = Application.application.width - widthHalved;
}

Before the keyboard controls will work, we need to call setFocus on the canvas so that it receives the keyboard events. We will do this in the activate function in the mxml file and also in the click event handler as, when the application runs in a browser, it might not get focus until it is clicked on.

If you run the game and then click outside its window, the keyboard controls will no longer work. When the application loses and the regains focus from the operating system, we need to reset the focus onto the canvas.

We'll do this in the mxml file by adding a new function activate.

public function activate(event:Event):void
{
	canvas.setFocus();			
}

public function click(event:Event):void
{			
	canvas.setFocus();		
}

Run the game and the spaceship can now be controlled with the left and right arrow keys. Obviously vertical movement could be accomplished by checking keysPressed[Keyboard.UP] and keysPressed[Keyboard.DOWN] and changing the y coordinate. As this is going to be a space invaders type game, we just want the spaceship to move left and right along the bottom of the screen.

A space invaders style game needs plenty of alien spaceships. In the next part we're going to look at how we can add the alien fleet.