INTRODUCTION
This is the documentation of my first AS2 game written entirely in OOP(Object Oriented Programming).
The game will be a standard space shoot-em-up with 3 enemy ships and the player firing lasers. There will be only 1 level at first but the design will leave the possiblility of adding levels easily. There will be a basic Score and Number of lives.
This documentation is for the benefit it my own learning as well as others who are struggling to implement the new Actionscript 2.0 paradigm in Flash MX2004.
Much thanks and respect for Squize from www.1coin1play.com for letting me use his spriteSheet for this example.
I will follow a reasonably traditional OOP design structure:
- Requirements Doc
- Use cases
- Class Diagrams
- Interaction Diagrams
- Test First Coding
- Iterations
- Testing
- Release
REQUIREMENTS
Spaceship:
- Moves by Left, Right, Up, Down Keys
- Fires lasers by Space Bar
Enemy:
- Moves right to left at random height
- Has 3 instances
- When it goes offscreen at left, comes back on at right
BackGround:
- Moves from left to right
- foreground moves faster than background (parallax)
Laser:
- Fires from spaceship when space bar pressed
Game:
- If laser hits enemy, player gain points
- If spaceship collides with Enemies, lose a life
- If player gains winningAmount, then s/he wins, game restarts
- If more than 3 lives lost, game over, player loses
USE CASES Use cases are a diagrammatic representation of the requirements. They show all the different things that will happen in the game, or all the different actions that will take place (see picture 1).
Pic. 1: Use cases
This is all you need to do. Just nut it out roughly on a piece of paper. This diagram could well give a clue to the classes and methods that will finally be used.
After you do this, it is time to work out what classes you will need and what attributes and methods those classes may need.
CLASS DIAGRAMS
Class Diagrams show the relationships between classes. In our diagram below (see picture 2) the Spaceship, Enemy, Laser and Background classes are all contained within the Game class. That means that instances of these classes are declared inside the Game class. This is called aggregation or "has a" relationship as opposed to inheritance which is described as a "is a" relationship. The Game class is aggregated in our Flash file.
Pic. 2: Class Diagram
CLASS DESCRIPTIONS
Spaceship:
Attribute |
Type |
Description |
speed |
Number |
value of x increment |
moveShip() |
method |
move the ship with keys |
setShipX(x) |
setter |
accessor method for _x |
getShipX() |
getter |
return _x |
setShipY(y) |
setter |
accessor method for _y |
getShipY() |
getter |
return _y |
Enemy:
Attribute |
Type |
Description |
enSpeed |
Number |
value of x increment |
moveEn() |
method |
move the Enemy randomly |
setEnX(x) |
setter |
accessor method for _x |
getEnX() |
getter |
return _x |
setEnY(y) |
setter |
accessor method for _y |
getEnY() |
getter |
return _y |
Laser:
Attribute |
Type |
Description |
fire() |
method |
start move when space bar pressed |
setLaserX(x) |
setter |
accessor method for _x |
getLaserX() |
getter |
return _x |
setLaserY(y) |
setter |
accessor method for _y |
getLaserY() |
getter |
return _y |
Background:
Attribute |
Type |
Description |
scroll() |
method |
move R to L |
setBackX(x) |
setter |
accessor method for _x |
getBackX() |
getter |
return _x |
setBackY(y) |
setter |
accessor method for _y |
getBackY() |
getter |
return _y |
Game:
Attribute |
Type |
Description |
score |
Number |
player points |
lives |
Number |
player lives |
checkCollisions() |
method |
Collision Detection |
CheckLives(lives) |
method |
if lives = 0, stop game |
checkScore() |
method |
check and display score |
Interaction Diagrams
Interaction diagrams map out the messages passed via methods to accomplish the requirements that we started with. I have no idea at the moment if this plan will work, because I dont know if Flash will deal with composition. The Game class contains the other classes and is a manager of the game. But I am not sure if I can control a class that extends from a MovieClip inside a class. Well here is the plan, let's see if it works or not. Plans are not set in concrete. You are allowed to throw them away if they dont work, or revise, or even start again from scratch. The most important thing is the process. Going through the process crystallises the problems and shows us the way to the solutions. It's like a storyboard for a film. You draw it up to get the feel of the script and the shooting needs. Once you have worked it all out you throw it away and direct from instinct. You know the story backwards because you went throught the storyboard process. Same here.
Here is the first Interaction diagram i drew up (see picture 3). I have even made some changes and realised a few things I had left out of the Class Diagrams. Like that moveship needs a direction (Left, Right, Up, Down) and I could pass a variable "dir" based on what key was pressed. "dir" can be a string ("left") or a number (1,2,3,4). Doesn't matter.
Pic. 3: Interaction diagram
TEST FIRST I created a new Flash file and made a spaceship_mc, linked it to ship_mc and put "Spaceship" in the class field. Then in the Actions panel i wrote this code:
// attach spaceship_mc
attachMovie("ship_mc", "myShip", getNextHighestDepth());
// declare variables
var enArray:Array; // empty at the moment
var bulletArray:Array; // empty
var myGame = new Game(myShip , enArray , bulletArray);
// game loop
_root.onEnterFrame = function(){
myGame.moveShip("right");
}
Then I wrote minimal code so that the classes would just compile.
class Game{
// Game class - manages the gameplay and
// contains the other classes
// declare variables
var ship:Spaceship;
var enemyArray:Array;
var laserArray:Array;
// constructor
function Game(_ship:Spaceship, _enArray:Array, _bullArray:Array){
ship = _ship;
enemyArray = _enArray;
laserArray = _bullArray;
}
}
Then in the Spaceship.as file I wrote:
class Spaceship extends MovieClip{
} Then compile, but no errors. But there should be because the moveShip method doesnt yet exist. ?? HuH ? Nothing comes up in the debugger. Pretty crappy error checking... Oh well...
Now i have to write the Game.moveShip method... I will write that and test it.
I wrote the moveship method but realised that I would have to write another method "checkKey" that will check which key is pressed and return dir as a String ("left", etc).
// === in Game class
// check key press method
function checkKey():String{
if(Key.isDown(Key.RIGHT)){
dir = "right";
}
if(Key.isDown(Key.LEFT)){
dir = "left";
}
if(Key.isDown(Key.UP)){
dir = "up";
}
if(Key.isDown(Key.DOWN)){
dir = "down";
}
return dir;
}
// moveship method
// 1. check for keyPresses
// 2. assign variable dir as per 1
// 3 pass dir to spaceship.move
function moveShip(dir:String){
ship.move(dir);
} Compiled that and got the error: "Line 38: There is no method with the name 'move'."
Perfect, starting to get errors; that is exactly what is supposed to happen. Now to write the "ship.move" method to get rid of the error.
This is the idea of "test first" - to implement the methods in the test suite until they fail and then code the methods one at a time until it meets the requirements and then move on to the next method.
I got the ship to move: here is how I did it...
class Game{
// declare variables
var ship:Spaceship;
var enemyArray:Array;
var laserArray:Array;
var dir:String;
// ==== constructor ==============
function Game(_ship:Spaceship, _enArray:Array,
_bullArray:Array){
ship = _ship;
enemyArray = _enArray;
laserArray = _bullArray;
}
// ==== check key press method =======
function checkKey():String{
if(Key.isDown(Key.RIGHT)){
dir = "right";
}
else if(Key.isDown(Key.LEFT)){
dir = "left";
}
else if(Key.isDown(Key.UP)){
dir = "up";
}
else if(Key.isDown(Key.DOWN)){
dir = "down";
}
else dir="stop";
//this works
//trace(dir + " - in game.checkey");
return dir;
}
// === moveship method =====
// 1. check for keyPresses
// 2. assign variable dir as per 1
// 3 pass dir to spaceship.move
function moveShip(dir:String){
ship.move(checkKey());
}
} // end class game And the Spaceship class:
class Spaceship extends MovieClip{
var dir:String;
function move(_dir:String){
dir = _dir;
if(dir=="left") _x -= 20;
if(dir=="right")_x += 20;
if(dir=="up") _y -= 20;
if(dir=="down") _y += 20;
if(dir=="stop"){
_x +=0;
_y +=0;
}
}
} // end class Spaceship I had trouble passing dir to the ship.move method, then I tried passing checkKey direct because it returns dir anyway, so it worked. Then i had to put a stop on it. Whenever any of the keys are up. The motion of the ship is a bit jerky but it works so far and I can smooth out the movement later.
But I know that my design works. I just had to pass the spaceship instance to the game constructor... and hey presto...
What next? hmmm, maybe fire the laser... ok off I go...
FIRING THE LASERS
This took me 2 days. I can't believe it. I kept getting the wrong bullet firing... Here is my first attempt at the fireLaser Method:
// in game class
// ====== fire laser ========
function fireLaser_old(){
if(Key.isDown(Key.SPACE)){
// set start pt of laser
laserArray[bulletNum]._y = ship.getY();
laserArray[bulletNum]._x = ship.getX();
// if space bar pressed set flag
hasFired = true;
bulletNum++;
if(bulletNum >=5){
bulletNum = 0;
}
}
}
That was pretty close to what I ended up with. The real trouble was with the "movelaser" method. Oh and yeah, Ii had the "fireLaser" in the enterFrame loop in my fla... Big mistake: it has to go in a onKeyDown event in the .fla. Here it is so far:
// code in .fla - actions panel
attachMovie("ship_mc", "myShip", getNextHighestDepth());
var enArray = new Array(3);
var bulletArray = new Array(5);
// attach bullet array
for(var i =0;i < 5;i++){
attachMovie("laser_mc", "laser"+i, 100 + i);
bulletArray[i] = _root["laser"+i];
}
// declare and initialise Game class instance
// and pass it ship, enemyArray and bulletArray
var myGame = new Game(myShip , enArray , bulletArray);
// ======= Key capture ============ //
someListener = new Object();
someListener.onKeyDown = function () {
myGame.fireLaser();
};
Key.addListener(someListener);
// game loop
_root.onEnterFrame = function(){
myGame.checkKey();
myGame.moveShip(dir);
myGame.moveLaser();
} That was a bit better but still no proper firing of the right bullet. It was firing about 2 later than when the space bar was pressed. Oh yeah, btw, here is the moveLaser method I was using at this time:
// ========= move laser ==========//
function moveLaser_old(){
if(hasFired==true){
laserArray[bulletNum]._x += 30;
}
if(laserArray[bulletNum]._x > Stage.width)
{
laserArray[bulletNum]._x = -10;
hasFired = false;
}
}
// ============== //
Here is the final (for now) of the "fireLaser" method. It is very simple. All it does is get the position of the ship and assign that position to the laser. Then the laser Number is incremented and if that number is greater than 5 it is reinitialised to 0:
// ========= FIRE LASER ====== //
function fireLaser(){
if(Key.isDown(Key.SPACE)){
// set start point
laserArray[bulletNum]._y = ship.getY();
laserArray[bulletNum]._x = ship.getX();
// increment the bullet number
++bulletNum;
// if more than 5 bullets , start again at 0
if (bulletNum>5) {
bulletNum = 0;
}
}
} And here is the final "moveLaser" method. Again very simplified. It goes through the array and if there is a bullet available it moves it. It is still not perfect but it will do for the moment.
// ======= MOVE LASER ======= //
function moveLaser(){
var bulleti = 0;
while (bulleti < 6) {
laserArray[bulleti]._x += 30;
bulleti++;
}
}
// ============= // Here is the whole Game class as it stands up to now:
class Game{
// ========= declare variables
var ship:Spaceship;
var enemyArray:Array;
var laserArray:Array;
var dir:String;
var bulletNum:Number = 0;
// ==== constructor ==============
function Game(_ship:Spaceship, _enArray:Array,
_bullArray:Array){
ship = _ship;
enemyArray = _enArray;
laserArray = _bullArray;
}
// ==== check key press method =======
function checkKey():String{
if(Key.isDown(Key.RIGHT)){
dir = "right";
}
else if(Key.isDown(Key.LEFT)){
dir = "left";
}
else if(Key.isDown(Key.UP)){
dir = "up";
}
else if(Key.isDown(Key.DOWN)){
dir = "down";
}
else dir="stop";
return dir;
}
// === moveship method =====
// 1. check for keyPresses
// 2. assign variable dir as per 1
// 3 pass dir to spaceship.move
function moveShip(dir:String){
ship.move(checkKey());
}
// ========= FIRE LASER ====== //
function fireLaser(){
if(Key.isDown(Key.SPACE)){
// set start pt
laserArray[bulletNum]._y = ship.getY();
laserArray[bulletNum]._x = ship.getX();
// increment the bullet number
++bulletNum;
// if more than 5 bullets , start again at 0
if (bulletNum>5) {
bulletNum = 0;
}
}
}
// ======= MOVE LASER ======= //
function moveLaser(){
var bulleti = 0;
while (bulleti < 6) {
laserArray[bulleti]._x += 30;
bulleti++;
}
} // ============= //
} // ------ END CLASS ------- // And the Spaceship class with getX and getY methods:
class Spaceship extends MovieClip{
var dir:String;
// ===== move =========
function move(_dir:String){
dir = _dir;
if(dir=="left") _x -= 20;
if(dir=="right")_x += 20;
if(dir=="up") _y -= 20;
if(dir=="down") _y += 20;
if(dir=="stop"){
_x +=0;
_y +=0;
}
}
// ====== getX ====
function getX():Number{
return _x;
}
// ======= getY =====
function getY():Number{
return _y;
}
} // ===== end Spaceship class And the Laser class:
class Laser extends MovieClip{
// ======= CONSTRUCTOR ========= //
function Laser(x:Number, y:Number){
_x = x;
_y = y;
}
}
Download the files as they are up to here. You may have to make your own sprites.
Next, the baddies!
(continues on page 2) |