//HUMAN pilot CLASS - takes keyboard input and sets commands for ship controlled by pilot
package pilots
{
import flash.events.*;
import ships.*;
public class HumanPilot extends Pilot
{
private var _commandLeft:Boolean = false;
private var _commandRight:Boolean = false;
public function HumanPilot()
{
super()
SpaceGame.instance.stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
SpaceGame.instance.stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
}
public function keyDownHandler(event:KeyboardEvent)
{
if(_ship != null) //ship could be null, in which case we want to avoid errors...
{
//Handle rotation:
if (event.keyCode == 65) _commandLeft = true; //A
if (event.keyCode == 68) _commandRight = true; //D
processCommands()
}
}
public function keyUpHandler(event:KeyboardEvent)
{
if(_ship)
{
if (event.keyCode == 65) _commandLeft = false; //A
if (event.keyCode == 68) _commandRight = false; //D
processCommands()
}
}
public function processCommands()
{
_commandRotation = 0;
if(_commandLeft) _commandRotation = -_ship.turnRate;
if(_commandRight) _commandRotation = _ship.turnRate;
}
}
}
The important thing here are the words “extends Pilot”. This means that the HumanPilot class is actually a Pilot class, but with a few things attached. So HumanPilot class already has the _commandRotation and _commandThrust variables, and we don't need to write them again.
In the HumanPilot constructor, we have the function super() - this just calls the parent class's constructor (which in the case of Pilot doesn't do anything).
But Why use inheritance?
Well other Pilot types (namely the AI ones) will also be using Pilot functionality (so the command variables are shared, but so are certain functions, such as selectNearestEnemy() or reassignTarget() etc.). If I write a selectNearestEnemy() function in the Pilot class, I don't have to paste the same function into all the other classes that will be needing it.
The problem with inheritance is that it breaks encapsulation – now HumanPilot is dependent on Pilot, so I have to take both classes into account when I'm working on them. So if one day I realise that Pilot should take an argument in its constructor, I will have to modify the super() function calls in all the classes that inherit from Pilot. This is the kind of extra work which well-designed OOP should help us avoid. But sometimes inheritance is the obvious solution – if you have many different classes that all share the same core of functionality, inheritance is probably the way to go. (The link I provided about inheritance and encapsulation is a really nice explanation of OOP for stupid people like me).
In "the biz" (I just made that up, real programmers don't say that) referring to relationships between classes we talk about "is a" or "has a". A dog "is a" mammal, so the class Dog would extend the class Mammal. That's easy. But a car "has" wheels - there wouldn't be inheritance there. This is clear but there are murkier examples.
For instance, what about a wheel? A wheel "is a" circle, right? I'd disagree. It has a circle shape. But it has a lot more functionality and uses than a circle. So the relationship between wheel and circle would be a "has a", I think (the circle would be the visual variable, or the collision detection variable within the wheel class). It hurts my head to think about it sometimes.
So back to the HumanPilot class. I've added two more variables, Booleans this time: commandLeft and commandRight. This is because of the fact that we can't trust a human player (bloody stupid dolt that he/she probably is) only to press one of the buttons at time. This input is then processed in the "processInput()" function into something the ship class can read easily (in this case, an angle).
So we have a function which is called whenever a key is pressed, then another function which is called whenever a key is released. It takes as its argument a KeyboardEvent - this is what is passed from the event listener on the stage to the function that deals with it. Inside the KeyboardEvent I can access the variable keycode, which is ASCII code for the key pressed by the user. I've decided to use the codes for A and D to deal with rotating the ship.
This will set the _commandRotation to the angle at which the ship is to be rotated.
I've set the angle to the ship's turn rate, but where is that? In the ship class of course. Maybe I've broken encapsulation a little bit because the Pilot is accessing the Ship's turn rate (I just don't know anymore!). But seeing as I know all ships will have a turn rate, I don't think should lead to any problems (famous last words). The reason I've made the _commandRotation an angle, and not just a command (turn left, turn right) is that, especially for the AI player, there will be times that I'll need the ship to turn at less than its turn rate - it'll be clearer when we get there.
Anyway, look what I did for the keyboard input:
SpaceGame.instance.stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
What is SpaceGame.instance? I hear you cry. Well, my SpaceGame.as file now looks like this:
//THE DOCUMENT CLASS
package
{
import flash.display.*;
import ships.Ship;
import flash.events.*;
public class SpaceGame extends MovieClip
{
/*this will allow other classes to access the instance
of the document class created at the beginning of the game */
private static var _instance:SpaceGame;
public static function get instance():SpaceGame
{
return _instance;
}
public function SpaceGame()
{
_instance = this;
var myShip:Ship = new Ship();
addChild(myShip);
}
}
}
I've created a new variable called _instance which is simply a pointer to the single instance (remember the difference between classes and instances) of document class SpaceGame created when the game starts. The variable is static, as is its “getter” function. A static variable is a variable which is accessed through the class definition, as opposed to an instance of the class. Wuh?
Let's go back to Plato and his horse. In any specific instance of a horse, for instance my imaginary horse Buttercup, we have several variables specific to that instance. So Buttercup's variables of position, hair-colour, speed, strength, cuteness, affinity to kicking people in sensitive areas are all specific to her. But if I think of the ideal horse, the blue-print for all other horses (the class definition) then there are certain variables that are true to that class, and can be accessed through the ideal as opposed to a single instance. Variables such as class = mammal, diet = herbivore, inspirationForBadTeenageGirlArt = true. So instead of accessing Buttercup.diet I'd go straight to Horse.diet. I'm sure you didn't bother reading that paragraph. I wouldn't have.
Anyway, this means I can access the “get” function for _instance directly through the class definition of SpaceGame. Access simply points to the instance of the game created in the SpaceGame constructor (that's what “this” refers to). I can do this because there will only ever be one instance of the class SpaceGame, but its a bit weird - I'm accessing the instance through the class definition. urgh...
So from any other class, I can now type SpaceGame.instance and be accessing the document class SpaceGame. And remember what we did before? We accessed the stage through the document class. All this leads to the rather convoluted way of accessing the stage: Spacegame.access.stage. If there's a better way of doing this, please inform me!
The nice thing about this is that the HumanPilot class has its event listeners assigned from within its constructor, and I don't need to apply them from anywhere else.
Anyway, we saw in the HumanPilot class that I was setting _commandRotation to the ship's turn rate. So let's have a look inside our Ship class:
//SHIP CLASS - controls ship attributes and movement
package ships
{
import flash.display.*;
import flash.events.*;
import pilots.*;
public class Ship extends Sprite
{
//SHIP ATTRIBUTES
private var _turnRate:Number = 300.0/25.0;
private var _shipImage:MovieClip;
private var _controllingPilot:Pilot;
public function get turnRate():Number
{
return _turnRate;
}
public function Ship()
{
_controllingPilot = new HumanPilot();
_controllingPilot.ship = this;
_shipImage = new blueLightFighterImage();
addChild(_shipImage);
x = 275;
y = 200;
addEventListener(Event.ENTER_FRAME, updateShip)
}
public function updateShip(event:Event)
{
rotation += controllingPlayer.commandRotation;
}
}
}
In the ship class, I have a variable called currentPilot. Current because every ship will automatically have an AIPilot assigned to it (we'll be programming that in a later tutorial), but depending on what happens within the level, that AIPilot might be overridden by the player, or by a puppet master. For now, we're just going to create a HumanPilot and have it be the ship's pilot. Notice that we have to set the HumanPilot's _ship variable because it doesn't have one by default (the setter allows us to do this, even though the _ship variable is protected).
We also have a private variable called _turnRate. This will be set when the ship is created, but not modified later (though who knows? We might find a need for that later). So it was a "getter" but no "setter". This is the value by which the ship turns in a single frame. I've written it as 300.0/25.0 because it's easier for me to understand – that means 300 degrees per second (25 frames per second – remember we set that frame rate earlier in this tutorial?). The .0 is because of the convention for denoting Numbers literals.
There are certain things the ship will do every frame, such as move or rotate, so we're going to have an updateShip function which is called every frame (done with an event listenener again - Event.ENTER_FRAME can be accessed anywhere, not just from the stage). We need to keep this function as simple as possible, because it will be called for every ship 25 times a second. No expensive equations here, please.
So far, all we're doing in the update ship is rotating it depending on the command being sent by the currentPilot (in this case, the user). We simply add the value of shipPilot.commandRotation to the ship's rotation.
When you test your movie, however, you might notice that the A button doesn't register. This is because it is a flash keyboard shortcut that selects some tool or another. You have to go to the control menu and select "Disable keyboard shortcuts". Then it should work, and you'll have something that hopefully looks like this:
press A and D to rotate the ship
Isn't that ridiculously exciting?
What we really want is to have the ship flying around, but before we can do that we need to program a very useful, if slightly technical (read boring) tutorial. For most movement in this game we're going to use 2D vectors (remember those? Wish you had listened in Maths or Physics now?). Those will be the subject of our next tutorial…
"I can do this because there will only ever be one instance of the class SpaceGame, but its a bit weird - I'm accessing the instance through the class definition. urgh..."
ReplyDeleteNothing weird about this. It's often done in PHP as well. It's similar to a "singleton". =)
Sorry for posting multiple times, but I wanted to point this out as well:
ReplyDelete"All this leads to the rather convoluted way of accessing the stage: Spacegame.access.stage. If there's a better way of doing this, please inform me!"
It's funny; I actually asked you this question on the Flash Kit boards. I rather like the method in which you do it, because it means not having to pass along a stage reference to all classes that need it - that's not proper encapsulation, and it's just plain messy!
If you are just asking in regards to the static class variable, I'll just refer to my previous comment. Using the base class like a singleton is perfectly acceptable. =)