November 2, 2009, 9:46 pm
AS3 Tank Game Tutorial Part 1
I've never posted a full tutorial for anything here before, but since I have a fascination for tank games (I LOVE TANK GAMES!!!1111one) I felt it only appropriate to post a tutorial with full code (for once ;) ). It isn't aimed at an advanced audience so code will be commented mercilessly and with intent to wound clarify.

Let's get started; our document class (if you are compiling in Flash CS3/4, you will need to set this first class as your document class! Those using mxmlc are assumed to know what to do) will simply create a new tank, add it to the stage, and add an event listener for ENTER_FRAME events to coordinate our game world:

package {	
	import flash.events.Event;	
	import flash.display.Sprite;	
	import flash.display.DisplayObject;	
	
	/**
	* @author tinyrobot
	*/
	
	//metadata tag that sets up our movie's stage properties
	[SWF(backgroundColor="#FFFFFF", frameRate="60", width="550", height="400")]
	
	public class TankGame extends Sprite {
		
		private var tank : Tank;
		
		/**
		 * Our constructor just creates a new tank, sets its position and adds it.
		 * It also sets up the timing mechanism for our game logic.
		 * Positioning the tank will eventually be the job of our World class (when
		 * we create one later!)
		 */
		public function TankGame() {
			tank = new Tank();
			tank.x = 50;
			tank.y = 50;
			addChild(tank);
			addEventListener(Event.ENTER_FRAME, update);
		}
		
		/**
		 * This method provides the synchronization of our game world: now it only
		 * tells one entity to update itself, but eventually it will synchronize
		 * all game objects.
		 */
		private function update(e : Event) : void{
			tank.update();
		}
	}
}

The comments I feel are fairly clear; we are using one update method to make sure that all game objects update themselves at a regular interval. This is the simplest method of timing, in future parts of this tutorial I will discuss attempts to create more accurate timing. I'm also not including package declarations properly since I'm going to cover that later in a topic on refactoring.

Now that's the basic entry point of our game out of the way; the fun part that will make the tank move follows in the Tank class:

package {
	import flash.geom.Matrix;
	import flash.display.DisplayObject;
	import flash.ui.Keyboard;
	import flash.events.KeyboardEvent;
	import flash.display.Loader;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.display.Sprite;

	/**
	 * @author tinyrobot
	 */
	public class Tank extends Sprite {

		/* a dictionary, mapping a keycode to a boolean. This allows us to handle
		   multiple keypresses. A much lazier way of doing Senocular's KeyObject:
		   http://www.senocular.com/flash/actionscript.php?file=ActionScript_3.0/com/senocular/utils/KeyObject.as */
		private var keys : Object = new Object();
		private var speed : Number = 0;
		private var rotationSpeed : Number = 2; //how much the tank will rotate by
		private var angle : Number = 0;
		private var tankView : DisplayObject; //our view

		public function Tank() {
			//we need an event listener so that we have a stage instance to listen
			//to keystrokes on!
			addEventListener(Event.ADDED_TO_STAGE, added);
			//load the tank image!
			var loader : Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, init);
			loader.load(new URLRequest("tank.png"));
		}
    
		private function init(e : Event) : void {
			tankView = e.target.loader as DisplayObject;
			addChild(tankView);
		}

		private function added(e : Event) : void {
			/*now we have a stage instance accessible we can add event listeners for the
		  keystrokes*/
			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
			stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
		}

		private function onKeyDown(e : KeyboardEvent) : void {
			keys[e.keyCode] = true; //set the keycode to 'on' in our keys dictionary
		}

		private function onKeyUp(e : KeyboardEvent) : void {
			keys[e.keyCode] = false; //set the keycode to 'off' in our keys dictionary
		}

		public function update() : void {
			/* Check the relevant keys in the keys dictionary and act appropriately
			   if they are down! */
			if(keys[Keyboard.LEFT]) {
				angle -= rotationSpeed;
				rotate(-rotationSpeed);
			}
			if(keys[Keyboard.RIGHT]) {
				angle += rotationSpeed;
				rotate(rotationSpeed);
			}
			if(keys[Keyboard.UP]) {
				speed += 0.1;
			}
			if(keys[Keyboard.DOWN]) {
				speed -= 0.1;
			}
			
			//since our tank image points upwards, we need to take the sine of angle
			//and the -cosine of angle, since our start point looks like an L shape
			//instead of an r shape.
			//See http://pixwiki.bafsoft.com/mags/5/articles/circle/sincos.htm
			//for a decent explanation!
			this.x  = x + speed * Math.sin(angle * Math.PI / 180);
			this.y  = y + speed * -Math.cos(angle * Math.PI / 180);
			
		}
		
		/**
		 * This function takes an angle in degrees, and rotates our tank view to
		 * match it.
		 */
		private function rotate(ang : Number) : void{
			//get the transformation matrix of our view (all display objects have one!)
			var m : Matrix = tankView.transform.matrix;
			//translate our matrix left by half width and up by half height
			//this makes sure the origin of our rotation is the centre of our tank
			m.tx -= tankView.width/2;
			m.ty -= tankView.height/2;
			//rotate the matrix by the angle in radians
			m.rotate(ang * (Math.PI / 180));
			//translate the matrix back to its original position
			m.tx += tankView.width/2;
			m.ty += tankView.height/2;
			//set our view's matrix to apply the rotation to the view
			tankView.transform.matrix = m;
		}
	}
}

Again, the code is quite simple and probably quite self explanatory. Every update (called by the TankGame class, remember?) we will check to see if left, right, up or down are pressed. Left and right will alter our angle of movement, and up and down will alter the speed at which we travel in that direction. The setting of our x and y position just involves adding our speed to our angle of movement, such that our x and y position lie on a point at the apex of that angle. As in the comments, there's a good tutorial here.

Notice how this class contains quite a lot of logic to do with our tank. It checks for input, it responds to input, it modifies the position of the tank and it modifies the rotation of the 'view' of our tank. In a later installment I'll discuss better sepaaration of these things, which will help to make our game more flexible and therefore better equipped to cope with increased complexity as we go along.

That's all for this tutorial, all comments welcome! As an exercise, there's enough code here to add the ability for the Tank to fire a Bullet - see if you can extend this example to make your tank fire bullets! You can find the tank image (I'm no artist!) here.

2 comments.
November 14, 2009, 8:54 am
#1 Mathias
Can you please upload the .FLA (source file) because I have an error :S
November 17, 2009, 4:18 pm
#2 TinyRobot
Hi Mathias, feel free to pop me an email to let me know what the error is: tinyrobot (at) tinyrobot (dot) co (dot) uk There's no fla I'm afraid, I compile using the flex SDK - so your fla would be empty, but with just the Document class set to be the TankGame class. I'm still working on the second part of this tutorial, but I wanted to build up a framework first so that I can go through it step by step :)
Add Comment
Name:

Comment: