Creating a Asteroids Flash Game Part 4: Using Keyboard Controls to Move Around the Ship on screen

Chris Moeller Asteroids Flash Game Part 4 - Rotating the Ship

In the previous section, we created a base class for all our game objects, and created and rendered our space ship.

In this section, we’ll add keyboard control to make our ship be able to move around on screen, and actually see our game start coming to life!

Getting Keyboard Input, and Storing Multiple Key Presses

When the flash window is in focus, whatever is “clicked on” will generally have focus. Using blitting, luckily we only have to worry about the stage having focus, and handling keyboard input from it.
To be able to get when a key has been pressed, we have to modify ‘Main.as’.

package
{
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;

    public class Main extends Sprite
    {
   	 private var game:Game;
   	 public function Main():void
   	 {
   		 if (stage) init();
   		 else addEventListener(Event.ADDED_TO_STAGE, init);
   	 }

   	 private function init(e:Event = null):void
   	 {
   		 removeEventListener(Event.ADDED_TO_STAGE, init);
   		 // entry point

   		 //create the game object passing in the swf width and height
   		 game = new Game(stage.stageWidth, stage.stageHeight);

   		 //add the game bitmap to the screen/ Main.as Sprite to make it visible
   		 addChild(game.bitmap);

   		 //Create the main game loop
   		 addEventListener(Event.ENTER_FRAME, Run);

   		 //add keylisteners
   		 stage.addEventListener(KeyboardEvent.KEY_DOWN, game.KeyDown);
   		 stage.addEventListener(KeyboardEvent.KEY_UP, game.KeyUp);
   	 }

   	 private function Run(e:Event):void
   	 {
   		game.Update();
   		game.Render();
   	 }
    }
}

The only change here is adding the two listeners to the stage, to recieve keyboard input. From there, we’re passing the event into our game object into two new functions.

Rather than just reacting whenever a key has been pressed, whether it be before our game loop or afterwards, it is best to store it in an array, and check to see if any keys have been pressed in our game loop.

The ‘Game’ class will handle when a key has been pressed, and decide what to do when certain keys are down. The following is the updated ‘Game‘ class.

package
{
	import Entities.Ship;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.KeyboardEvent;
	import flash.geom.Rectangle;
	import flash.utils.getTimer;

	public class Game
	{
		public var bitmap:Bitmap;
		public static var Renderer:BitmapData;

		private var ship:Ship

		private var keys_down:Array

		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 function Game(stageWidth:int, stageHeight:int)
		{
			trace("Game created");
			Renderer = new BitmapData(stageWidth, stageHeight, false, 0x000000);
			bitmap = new Bitmap(Renderer);

			ship = new Ship(Renderer.width / 2 - 5, Renderer.height / 2 - 10, 10, 20);
			keys_down = new Array();
		}
		public function Render():void
		{
			Renderer.lock();
			Renderer.fillRect(new Rectangle(0, 0, Renderer.width, Renderer.height), 0x000000);

			ship.Render();

			Renderer.unlock();
		}

		public function Update():void
		{
			if (CheckKeyDown(LEFT))
				ship.RotateLeft();

			if (CheckKeyDown(RIGHT))
				ship.RotateRight();

			if (CheckKeyDown(UP))
						ship.Thrust(1);
			if (CheckKeyDown(DOWN))
				ship.Thrust( -1);

			ship.Update();

		}

		public function KeyUp(e:KeyboardEvent):void
		{
			//position of key in the array
			var key_pos:int = -1;
			for (var i:int = 0; i < keys_down.length; i++)
				if (e.keyCode == keys_down[i])
				{
					//the key is found/was pressed before, so store the position
					key_pos = i;
					break;
				}
			//remove the keycode from keys_down if found
			if(key_pos!=-1)
				keys_down.splice(key_pos, 1);
		}

		public function KeyDown(e:KeyboardEvent):void
		{
			//check to see if the key that is being pressed is already in the array of pressed keys
			var key_down:Boolean = false;
			for (var i:int = 0; i < keys_down.length; i++)
				if (keys_down[i] == e.keyCode)
					key_down = true;

			//add the key to the array of pressed keys if it wasn't already in there
			if (!key_down)
				keys_down.push(e.keyCode);
		}

		public function CheckKeyDown(keycode:int):Boolean
		{
			var answer:Boolean = false;
			for (var i:int = 0; i < keys_down.length; i++)
				if (keys_down[i] == keycode)
				{
					answer = true;
					break;
				}
			return answer;
		}
	}
}

Several things have happened to be able to handle the keyboard input. First, the game class now has an array used to store which keys are currently down (keys_down).

At the top we also define the keys that will be important to us with their respective keycodes - up, down, left, right, space. This way we can refer to them by name instead of trying to remember their keycode offhand. If you don’t know what the keycode is of a certain key, just trace out the ‘e.Keycode’ in the ‘KeyDown’ function.

Next in the constructor, all we have to do is initialize the keys_down array.

In the ‘Update’ function, we use the new function ‘CheckKeyDown’ to loop through the currently pressed keys, to find out if the ones we are interested in are down. We then run the new function in the ship to move it, and finally call the ship update function to have it update anything that it needs to do.

The ‘KeyUp’ function is called when a key on the keyboard is released, called  from ‘Main.as’. It  goes through the array of currently held down keys, checks to see if the key that was released was in the array, and splices/deletes it from the array.

Lastly, ‘CheckKeyDown’ is used to loop through the ‘keys_down’ array, and check to see if a key is stored in it, which would mean it is currently down.

Adding Ship Movement

Now that we can tell which keys are currently down, and can call a function depending on the key that is down, we’ll need to add functions for the ship to actually respond and do something.

These functions were already called in “Game.as”, so we need to create these to be able to compile.

Open up ‘Ship.as’ and make the following changes:

package Entities
{
	import flash.display.Sprite;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;

	public class Ship extends GameSprite
	{
		private var rotate_amount:int;
		public var rotate_offset:Point;

		private var speed:Point;
		private var speed_multi:Number = .5;
		private var friction:Number = .95;

		public function Ship(x:int, y:int, width:int, height:int)
		{
			super(x, y, width, height);
			image_sprite = new Sprite();
			image_sprite.graphics.lineStyle(.1, 0xFFFFFF);
			rotate_amount = 15;
			speed =  new Point(0, 0);
			rotate_offset = new Point(width / 2, height / 2);

			//create the ship drawing
			image_sprite.graphics.moveTo(0, height);
			image_sprite.graphics.lineTo(width / 2, 0);
			image_sprite.graphics.lineTo(width, height);

			//draw the line across
			image_sprite.graphics.moveTo((7*height/8 -height)/(-height/(width/2)), 7*height/8 );
			image_sprite.graphics.lineTo((7 * height / 8 -height) / (height / (width / 2)) + width, 7 * height / 8);
		}
		override public function Render():void
		{
			var matrix:Matrix = new Matrix();
			matrix.translate(-rotate_offset.x, -rotate_offset.y);
			matrix.rotate(angle);
			matrix.translate(rotate_offset.x, rotate_offset.y);
			matrix.translate(x, y);

			Game.Renderer.draw(image_sprite, matrix);
		}
		override public function Update():void
		{
			x += speed.x;
			y += speed.y;
			speed.x *= friction;
			speed.y *= friction;
		}
		public function RotateLeft():void
		{
			//first convert angle in rads
			var angle_deg:int = Math.round(angle * (180.0 / Math.PI));
			angle_deg -= rotate_amount;
			angle = angle_deg * (Math.PI / 180.0);
		}
		public function RotateRight():void
		{
			var angle_deg:int = angle * (180.0 / Math.PI);
			angle_deg += rotate_amount;
			angle = angle_deg * (Math.PI / 180.0);
		}
		public function Thrust(dir:int=1):void
		{
			var angle_deg:int = angle * (180.0 / Math.PI);
			if (dir == 1)
			{
				speed.x +=speed_multi * Math.sin(angle);
				speed.y -= speed_multi * Math.cos(angle);
			}
			else
			{
				speed.x -=speed_multi * Math.sin(angle);
				speed.y += speed_multi * Math.cos(angle);
			}
		}
	}
}

First we needed to add variables to handle the movement of the ship, with ‘rotate_amount’ being how much we rotate the ship each update, and ‘rotate_offset’ going to be the point around which we rotate the ship.

By default, if we rotate the ship, it will rotate around the top left corner - which is very useful and consistent. But for the ship, we don’t want it spinning around the top left corner, and instead around the bottom ?’s, and middle of the ship.

Side Note:

You’ll find if you use the flash IDE, you can change the “registration point’, so that it can be rotated around any point, even outside of the object. This can be useful, but by default it sets this to be the center of the object, which can be frustrating and confusing to new users.

Next we need variables to store the current speed in both the ‘x’ and ‘y’ direction (flash’s vector type, ‘Point’) as the ‘speed’ variable. We create the ‘speed_multi’ variable, to store how much the ship will be able to move each frame, and finally ‘friction’ to slow down the ship and bring it to a stop, so the player won’t be able to lose control as easily.

In the render function, we add some matrix manipulation. To be able to get the ship to rotate around the point we want, we first have to move the desired rotation point to the top left, perform the rotation, than return it to where it was, so that it looks like it rotated in place. After that, we’re free to translate/move it to it’s actual location on screen.

Next, in the ‘Update’ function we displace the ship in the x and y direction by their respective speeds. Then, to emulate friction, we’ll multiple the speeds by the friction value, so that the ship slows down each frame.

In the ‘RotateLeft’ and ‘RotateRight’ functions, we first convert the angle to degrees, add our rotation amount, and then convert it back into radians. You could do the rotation amount in radians from the start, but I find it easier to visualize degrees.

Finally, in the ‘Thrust’ function, we add to the speed in both directions, depending on the angle that the ship is facing. Using Cos and Sin (Trig), we can be used to split the force into an amount in the X and Y direction, and apply it to the ship. If the ship is at 0 degrees, we have 100% thrust in the Y direction, and 90 degrees, 100% thrust in the x direction.

Tutorial Demo

In the next section we’ll make the ship able to shoot bullets, as well as have the bullets and the ship "wrap" around to the other side of the screen when they reach an edge of the screen.

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

Other Articles in this Series

Bookmark the permalink.

11 Comments

  1. Thanks for the tutorials, they are very clear and are helping me to improve my coding.

    I noticed that in your tutorials, you are adding constants for each of the keys used in your game, e.g.

    private const LEFT:int = 37;

    I normally use import the flash.ui.Keyboard class and then refer to the required key in the following way (e.g.)…

    if (CheckKeyDown(Keyboard.LEFT)) etc.

    Is there are reason you do not do this, or is it just a preference?

    Thanks again,
    Innes

    • Thanks for the feedback!

      I hadn’t realized before that flash had a built in class with all the keycodes stored in it, but that would save time. I’ll use your method in the future for sure!

      There’s really no point in needing to know the keycodes, as far as I can see, so using the built in method will save time compared to needing to trace out the keycodes yourself, and assign constants for them.

      I’ll update the tutorials soon with the new method, thanks again!

  2. Array indexOf() can help to shorten(simplify) some methods:

    public function KeyDown(event:KeyboardEvent):void {
    if(keys_down.indexOf(event.keyCode) == -1) {
    keys_down.push(event.keyCode);
    }
    }

    public function KeyUp(event:KeyboardEvent):void {
    var key_pos:int = keys_down.indexOf(event.keyCode);
    if (key_pos > -1) {
    keys_down.splice(key_pos, 1);
    }
    }

    public function CheckKeyDown(keycode:int):Boolean {
    return keys_down.indexOf(keycode) > -1;
    }

    and using build in search is probably faster, but don’t quote me on that, i’m very new to AS3

  3. First tutorial for Action Script 3 for me , this is so great !!
    Thank you very much !!

  4. if you don’t want to use array to find what key have been pressed you can use

    switch(e.keyCode)
    {
    case Keyboard.LEFT:
    {
    TurnLeft = false;
    break;
    }
    case Keyboard.RIGHT:
    {
    TurnRight = false;
    break;
    }
    case Keyboard.UP:
    {
    MoveForward = false;
    break;
    }
    case Keyboard.DOWN:
    {
    MoveBack = false;
    break;
    }
    }

  5. Thanks so much for the excellent tutorial! I’m coming from the C++ and Java worlds, so this has been a fantastic way to get the feel for a new language.

    That being said, I have a suggestion for improving the performance of your keyboard routine. Instead of creating an array to track items that are pressed which requires searching linearly through the array each time a key is pressed, released, and checked, create a boolean array map of all the keycodes. This allows you to directly update each key individually and look it up in one step.
    A quick search online found that the maximum keycode is 222, so if you do the following:

    private static const MAX_KEYCODE:int = 222;
    var key_states:Array;

    public function Game(stageWidth:int, stageHeight:int)
    {
    key_states = new Array(MAX_KEYCODE);
    for (var i:int = 0; i = 0 && e.keyCode = 0 && e.keyCode = 0 && keycode < MAX_KEYCODE)
    return key_states[keycode];
    else
    return false;
    }

    Cheers, and thanks again!
    Steve

  6. Hello there! I just found your tutorials, and I think they are fantastic. 😀 I just got a doubt here: Why you call the super methods for Update, Render, and the Constructor? I quite didn’t get it.

    Thanks for the good work!

    • Super is used to call the base classes version of that function if we want it to handle stuff that they would have in common.

      Like for the constructor, the base class can take just an x,y,width,height, and assign it/ do whatever we want with that stuff, so we just use it to do the stuff that is already setup in the base class.

  7. Thank you!

    this string
    [QUOTE]
    if (stage) init();
    else addEventListener(Event.ADDED_TO_STAGE, init);
    [/QUOTE]

    helped me. without this string debugger return Error.

Leave a Reply

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