November 2, 2009, 9:46 pm
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.
So I thought it would be funny, knocked up a little music in milkytracker, found a cow sound effect (because obviously unicorns go moo) and went at it. Initially I thought it would be fun to just see if I could do the whole thing from scratch in 2 hours, but sadly my effort fell short - everything was sort of there, but there was no real game to play! The problem is, I initially wanted to do it just as a joke really; but as I thought more about it (while sick) I thought it would be quite fun to have a space invaders clone that wasn't necessarily to formulaic - specifically with regards to enemy movement. My current ambition is to implement different enemy behaviours, so that the game has a more organic feel - I don't actually want the game to be 'winnable' necessarily; alien invaders by virtue of actually managing to get here would be unlikely to be thwarted by a single tenacious human adversary!
Instead I think it would be sensible to implement a finite state machine for the unicorns, and alter that as gameplay progresses to maintain the player's interest. Naturally difficulty should progress as a function of time, although I think it would be cool to alter the nature of the difficulty (not just making enemies faster, for example). So the premise of this game is space invaders, with novel enemy behaviours - kind of like... well... galaga really. But with polychromatic unicorns! Anyway, it'll be fun to program the different behaviours, which is the main point of these things for me ;) 
