Refining my OOP structure, and trying to make the display of code as intuitive as possible,
using basic OOP principals this definitely helps but my craft has only gone so far. I am looking to improve upon my practice:
package
{
import flash.display.*;
import flash.text.*;
public class Button extends Sprite
{
protected var _spr:Sprite = new Sprite();
protected var _tf :TextField = new TextField();
protected var c :Number;
protected var xx :Number;
protected var yy :Number;
protected var w :Number;
protected var h :Number;
protected var t :String;
protected var a :Number;
protected var l :Number;
protected var lC :Number;
function Button
(
_col:Number, //beginFill
_x:Number, _y:Number, _w:Number, _h:Number, //drawRect
_t:String = "", //TextField (optional)
_a:Number = 1, //beginFill (optional)
_l:Number = 0, _lC:Number = 0xFFFFFF //lineStyle (optional)
)
{
c = _col;
xx = _x;
yy = _y;
w = _w;
h = _h;
t = _t;
a = _a;
l = _l;
lC = _lC;
_spr.addChild(_tf);
draw();
}
public function draw ():void
{
_spr.graphics.clear ();
_spr.graphics.lineStyle(l, lC);
_spr.graphics.beginFill(c);
_spr.graphics.drawRect (xx, yy, w, h);
var f:TextFormat = new TextFormat;
f.font = "Arial";
_tf.text = t;
_tf.autoSize = TextFieldAutoSize.LEFT;
_tf.x = xx + w/2 - _tf.textWidth / 2;
_tf.y = yy + h/2 - _tf.textHeight / 1.5;
_tf.width = w - 2;
_tf.height = h - 2;
_tf.alpha = 0.75;
_tf.setTextFormat(f);
_tf.selectable = false;
_tf.mouseEnabled = false;
_spr.graphics.endFill ();
}
/* ----------------------- *
* GETTERS *
* ----------------------- */
//OVERRIDE
override public function get x () :Number {return (xx)}
override public function get y () :Number {return (yy)}
override public function get width () :Number {return (w)}
override public function get height () :Number {return (h)}
//SUPPLEMENTARY
public function get col () :Number {return (c)}
public function get text () :String {return (t)}
public function get line () :Number {return (l)}
public function get lineC () :Number {return (lC)}
public function get getObj () :Sprite {return (_spr)}
/* ----------------------- *
* SETTERS *
* ----------------------- */
//OVERRIDE
override public function set x (_n:Number) :void
{ xx = getObj.x = Math.round(_n - xx) }
override public function set y (_n:Number) :void
{ yy = getObj.y = Math.round(_n - yy) }
override public function set width (_n:Number) :void
{ w = Math.round(_n) }
override public function set height (_n:Number) :void
{ h = Math.round(_n) }
//SUPPLEMENTARY
public function set col (_n:Number) :void
{
c = _n;
draw();
}
public function set text (_s:String) :void
{
t = _s;
draw();
}
public function set line (_n:Number) :void
{
l = _n;
draw();
}
public function set lineC(_n:Number) :void
{
lC = _n;
draw();
}
}
}
From the above Button class, what interpretations can you make about my structure & organization? I have read and followed many articles on how you should perform OOP logically and I think I am ready for some tuning, or criticism rather on my understanding of how a class should be in AS3 (circumstantial i know.)
Some specific questions:
When I instantiate 'Button' from my Main class, should I add it to the stage there or
within the Button Class itself using Super?
Will the "1047" Error ever be fixed? What is a sustainable (or more efficient) work-around besides the ones already mentioned here?: http://curtismorley.com/2008/10/15/actionscript-error-1047-parameter-initializer-unknown-or-is-not-a-compile-time-constant/
In order to update width/height changes, I must call my draw function after modification from outside the Override function for width & height specifically. Is there a proper way to call draw() through the Override functions? No error output was provided, it seems doing so is against the rules though so I am just calling draw() from Main as a hack fix.. Possibly write a function within button tethered to event of a changed property?
If anyone bothered to parse this wall of text, thanks for reading and I appreciate any criticism you may have, harsh or otherwise :)
First, your variable names are not decriptive. If you're going to name your variables "c", "t", "lc", etc., at least put inline comments that describe what they're for. Even if you comment the parameters that populate them lower down, there's unnecessary overhead with what you've done.
If you've done that because of the historically poor code completion in the IDE, use Flash Builder or one of the excellent code editing tools that can help you type out lineColor, textField, etc.
On closer inspection, it looks like you've done this at least partially because you've reversed the normal convention, which is to have parameters to functions not use underscores, whereas private/protected storage should use underscores, but otherwise should have the same name as the public property. Changing your conventions to match the rest of the AS3 world would go a long way to making your code more readable.
If you don't want to lose the underscores in your function parameters, you could use a snippet in FB to make setting the instance variable to a parameter of the same name.
Invalidation is very well understood in ActionScript, so I'm not sure why you have an issue. It looks something like this:
protected function invalidate():void {
_isInvalid = true;
addEventListener(Event.ENTER_FRAME, validate);
}
protected function validate(e:Event):void {
removeEventListener(Event.ENTER_FRAME, validate);
if (_isInvalid) {
draw();
}
_isInvalid = false;
}
Call this everywhere you're now calling draw(), and it will reduce the number of times you have to update the display list.
MPO is that you should not have constructor arguments for View Classes, as this makes it impossible to use them from the timeline.
You'll probably find that if you're open to using the timeline, a lot of this hard work goes away--you simply add the behavior via an AS Class and draw the graphics in the IDE (using things like 9-slice scaling, filters, etc., to minimize maintenance hassle). If you want a button that looks different, you draw a different one, attach your behavior Class, and done.
A lot of people don't realize that it's possible to maintain good OOP while allowing the timeline to take over much of the heavy lifting. Hope this gives you what you want, or at least gives you some search terms you can move forward with. Note if you're interested in knowing more about leveraging the timeline, post back. The links to the source code on that one were eaten when the post was archived.
Related
I'm making a space game in AS3 - I have an enemy class named EnemyShip.
I've already registered the Event.ENTER_FRAME and it works correctly - the problem is that my variable, rowXY of type Array, reports both null and a non-null value.. in a pattern.
How can I keep this from happening (or is there an easier way to animate the ships to move in Flash Professional?).
The pattern is as follows:
EnemyShip:
package
{
import flash.display.Sprite;
import flash.events.Event;
public class EnemyShip extends Sprite
{
internal var id:int;
internal var rowOrder:int;
internal var rowXY:Array;
private var dirUp:Boolean = false;
public function EnemyShip()
{
// add event listeners
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
addEventListener(Event.ENTER_FRAME, update);
}
private function update(e:Event):void
{
moveUpAndDown();
trace(rowXY);
function moveUpAndDown():void
{
if (dirUp)
y -= C.ENEMY_SPEED;
else
y += C.ENEMY_SPEED;
}
}
private function onAddedToStage(e:Event):void
{
// get row XY
if (rowOrder == 1)
rowXY = C.ENEMY_ROW_1;
if (rowOrder == 2)
rowXY = C.ENEMY_ROW_2;
if (rowOrder == 3)
rowXY = C.ENEMY_ROW_3;
// set XY position
x = rowXY[0];
y = rowXY[1];
removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
internal function destroy():void
{
rowOrder = null;
rowXY = null;
// remove event listeners
removeEventListener(Event.ENTER_FRAME, update);
// remove from display list
parent.removeChild(this);
}
}
}
C.as (Constants):
package
{
// C stands for constants
public class C
{
// ship constants
public static const FIRE_DELAY:int = 500; // milliseconds
// laser constants
public static const LASER_SPEED:int = 30;
public static const POINTS_KILL:int = 10;
// mcScore constants
public static const SCORE_LOC_X:Number = 345;
public static const SCORE_LOC_Y:Number = -120;
// enemy ship constants
public static const ENEMY_ROW_1:Array = [485, -45];
public static const ENEMY_ROW_2:Array = [485, 25];
public static const ENEMY_ROW_3:Array = [485, 95];
public static const ENEMY_SPEED:int = 5; // 5 pixels
public static const ENEMY_Y_MIN:int = -10;
public static const ENEMY_Y_MAX:int = 10;
}
}
If you're trying to animate using Flash Professional, I would take advantage of their built in Motion Tween feature. You could also animate the ship in your code by taking advantage of the Tween class.
I reproduced the behavior that you've got using this for loop ( you can get the same result by instantiating 3 objects and insert 3 others directly to the stage ) :
for(var i:int = 1; i < 7; i++){
var enemy:EnemyShip = new EnemyShip();
enemy.rowOrder = i;
addChild(enemy);
}
here we can see very clear that where i is 4, 5 or 6, rowOrder will be null which also will fire some #1009 errors for all the instances which has the rowOrder greater that 3 from this line :
x = rowXY[0];
So to avoid that, you can, for example, restrict the value of rowOrder to be between 1 and 3, like this, for example :
enemy.rowOrder = 1 + int(Math.random()*3);
you can also set that value inside the EnemyShip class itself.
...
Hope that can help.
The Solution
It was to my knowledge that the event Event.ENTER_FRAME was only passed to objects on the stage, but this is not true.
The problem was in my EnemyShip class' constructor method - the ENTER_FRAME event was being listened to by both on- and off-stage instances - the off-stage instances had not been assigned a rowXY (this happens in the event ADDED_TO_STAGE).
Old Code:
New Code:
So what's the lesson to be learned here?
Event.ENTER_FRAME happens no matter if the object is displayed or not.
In the screenshot you posted you have this code:
if(rowOrder > 3)
rowOrder = 0;
But when you check rowOrder in onAddedToStage() you don't have a case for 0 -- only 1, 2, or 3. So rowXY never gets set when rowOrder is 0.
Either change that code to set rowOrder to 1 instead of 0, or change your other code to be zero indexed.
I'm making a game where you are a soccer goalie and the ball(brazuca) keeps on falling down and you have to collect it, but I keep getting the incorrect number of arguments error. I'm fairly new to coding and I know that the error means something unexpected happened in the brackets where the error happened but I can't figure out how to fix it. It would be great if anyone can help me with this issue.
class Gamescreen2
package
{
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.*;
import flash.ui.Mouse;
import flash.media.SoundChannel;
public class Gamescreen2 extends MovieClip
{
public var goalie:GoaliePlay;
var gameMusic:GameMusic;
var gameChannel:SoundChannel;
var speed:Number;
//public var army:Array;
public var army2:Array;
//public var gameTimer:Timer;
public var gameTimer2:Timer;
public static var tick:Number;
public static var score:Number;
public function Gamescreen2()
{
goalie = new GoaliePlay();
addChild (goalie);
gameMusic = new GameMusic;
gameChannel = gameMusic.play(0,999);
score = 0;
//army = new Array();
army2 = new Array();
//gameTimer = new Timer(25);
gameTimer2 = new Timer(25);
//gameTimer.addEventListener(TimerEvent.TIMER, onTick);
gameTimer2.addEventListener(TimerEvent.TIMER, onTick);
gameTimer2.start();
tick = 0;
}
public function onTick(timerEvent:TimerEvent):void
{
tick ++;
if (Math.random() < 0.05)
{
var randomX:Number = Math.random() * 550;
var goalieB= new GoalieB(0,0);
army2.push (goalieB);
addChild (goalieB);
}
for each (var goalieB:GoalieB in army2)
{
goalieB.moveDownABit();
if(goalieB.y == 420)
{
score--
trace(score);
this.scoreTxttwo.text = score.toString();
}
if (goalie.collisionArea.hitTestObject(goalieB))
{
goalieB.x == 550;
score += 10;
}
if (score < 1)
{
score = 0;
}
}
}
}
}
class BrazucaPlay
package
{
import flash.display.MovieClip;
public class BrazucaPlay extends MovieClip
{
var speed:Number;
public function BrazucaPlay(startX:Number, startY:Number)
{
x = startX;
y = startY;
speed = Math.random();
}
public function moveDownABit():void
{
//two speed enemies
if (speed >= .25)
{
y = y + 3;
}
}
}
}
Might be the constructor of your GameMusic class.
gameMusic = new GameMusic;
You should have brackets like so:
gameMusic = new GameMusic();
Same with this line:
var newBrazuca = new BrazucaPlay;
Should be:
var newBrazuca = new BrazucaPlay();
If, after adding brackets () you still receive the error, then you should check your custom classes BrazucaPlay and GoaliePlay and make sure their constructors aren't expecting parameters. Also check this function: brazuca.moveDownABitB().
The constructor is the function that is named after the class and is what first runs when instantiate an object. So you do var newBrazuca = new BrazucaPlay(); there is a constructor function in the BrazucaPlay class that would look something like this:
public function BrazucaPlay(){
//some code.
}
If that function actually looked something like this:
public function BrazucaPlay(myParameter:String){ }
Then that would throw the error you're getting because it's expecting you to pass a parameter to it (in this case a string like new BrazucaPlay("blah blah blah"))
EDIT
Now that you've posted more code, the cause is quite clear. The constructor of your BrazucaPlay class is expecting two arguments (a starting x/y position). So when you instantiate a new BrazucaPlay instance, you are required to pass it those two parameters:
var newBrazuca = new BrazucaPlay(0,0);//you need to pass two numbers (starting x and y)
If you don't want to do this, you can change the code to make those parameter OPTIONAL.
//this makes the parameters default to 0 if no value is passed in
public function BrazucaPlay(startX:Number = 0, startY:Number = 0)
{
x = startX;
y = startY;
speed = Math.random();
}
Welcome, user. Okay, a couple of things...
First, just so you know, we need the entire error, as CyanAngel said. It helps us prevent digging through the entire code. (For more tips about getting started, go to stackoverflow.com/help)
Second, this is what your error message means: You are passing the wrong number of arguments (values) to a function. The function requires exactly two, and you're passing more or less than you should.
This is why we need the error: it has the line number of the error, letting us/you narrow in on it directly.
I'm an actionscript newbie so please bear with me. Below is a function, and I am curious how to set default property values for objects that are being created in a loop.
In the example below, these propeties are the same for each object created in the loop: titleTextField.selectable, titleTextField.wordWrap, titleTextField.x
If you pull these properties out of the loop, they are null because the TextField objects have not been created, but it seems silly to have to set them each time. What is the correct way to do this. Thanks!
var titleTextFormat:TextFormat = new TextFormat();
titleTextFormat.size = 10;
titleTextFormat.font = "Arial";
titleTextFormat.color = 0xfff200;
for (var i=0; i<arrThumbPicList.length; i++) {
var yPos = 55 * i
var titleTextField:TextField = new TextField();
titleTextField.selectable = false;
titleTextField.wordWrap = true;
titleTextField.text = arrThumbTitles[i];
titleTextField.x = 106;
titleTextField.y = 331 + yPos;
container.addChild(titleTextField);
titleTextField.setTextFormat(titleTextFormat);
}
There are basically three options here.
You could create a custom class that
acts as a proxy to a TextFormat.
TextFormatProxy, for instance, which
could create a TextFormat in it's
constructor and set each of your
default values. See this link
or google "AS3 proxy pattern."
You could write a custom class that
extends TextFormat and likewise, set
these default values in the
constructor, or in a function of
your choosing.
You could use a little helper
function like this
package {
import flash.display.Sprite;
public class DefaultProperties extends Sprite{
public var s:Sprite;
public function DefaultProperties() {
s = new Sprite();
s.graphics.beginFill(0x00ff00, 1);
s.graphics.drawRect(0, 0, 100, 100);
s.graphics.endFill();
this.setDefaults(s, {x:100, y:200, scaleX:.5});
this.addChild(s);
}
function setDefaults($obj:*, $properties:Object):void {
for (var i in $properties) {
$obj[i] = $properties[i];
}
}
}
}
Without knowing the surrounding code, I'd suggest putting any construction logic in a factory object. If that's overkill, then I'd say your code is fine. Pulling it out of the loop and into a helper method is really just adding another layer of indirection which is not only harder to read, but it's also less efficient since you're introducing another method call on the stack.
Using a factory object is good if your class is really not interested in how things are created, but rather just needs one or more instance of something. That way, the factory is responsible for the creation of the object and you get a nice separation of concerns.
I wouldn't recommend creating a proxy structure that only has the purpose of setting the properties of your instance. I think it's an anti pattern because it favors inheritance over composition and it does so solely for the purpose of code re-use. But doing this means you're locking yourself to that specific implementation, which is likely not what you want. Just because your textfield has certain value, doesn't mean that it is a different type.
This is how your code could look when using the factory pattern, first out, the format factory:
public interface FormatFactory
{
function getInstance():TextFormat;
}
public class TitleFormatFactory implements FormatFactory
{
public function getInstance():TextFormat
{
var format:TextFormat = new TextFormat();
format.size = 10;
format.font = "Arial";
format.color = 0xfff200;
return format;
}
}
Factories may or may not be parameterized:
public interface TextFieldFactory
{
function getInstance(text:String, position:Point, format:TextFormat):TextField;
}
public class TitleFactory implements TextFieldFactory
{
public function getInstance(text:String, position:Point, format:TextFormat):TextField
{
var title:TextField = new TextField();
title.selectable = false;
title.wordWrap = true;
title.text = text;
title.x = position.x;
title.y = position.y;
title.setTextFormat(format);
return title;
}
}
Lastly, this is how you'd use the code:
var formatFactory:FormatFactory = new TitleFormatFactory();
var titleFactory:TextFieldFactory = new TitleFactory();
var format:TextFormat = formatFactory.getInstance();
for (var i = 0; i < arrThumbPicList.length; i++)
{
var position:Point = new Point(106, 331 + 55 * i);
var title:TextField = titleFactory.getInstance(text, position, format);
container.addChild(title);
}
Besides being readable, a huge benefit is that you can now swap the implementations of the factories and thus changing what kind of components you're using, without having to change your actual logic. All you have to do is change the references to the factories.
Also, by separating the concerns you make it easier to focus on one aspect of your code and thus run less of a risk of introducing errors and if you still do, those are often easier to spot and fix. More-over, it's much easier to unit test code when you separate concerns and more importantly, creation logic from business logic.
If I were to approach the same problem I'd use the same as you posted, just because making the new data structure would be unnecessary. An alternative would be to create a function that accepts the textbox as a parameter:
for (var i=0; i<arrThumbPicList.length; i++) {
var titleTextField:TextField = new TextField();
setProperties(titleTextField,i);
}
function setProperties(txt:TextField,offset:int){
var yPos = 55 * offset;
txt.selectable = false;
txt.wordWrap = true;
txt.text = arrThumbTitles[offset];
txt.x = 106;
txt.y = 331 + yPos;
container.addChild(titleTextField);
titleTextField.setTextFormat(titleTextFormat);
i++;
}
Although I'd probably go with the way you posted unless you're adding things from multiple for loops.
Your code is ok for me. Simple and effective. You only have 3 fixed parameters, for me it's not necessary to do more :
titleTextField.selectable = false;
titleTextField.wordWrap = true;
titleTextField.x = 106;
Unlike the other answer, I would do a helper function to get the textfield with all fixed parameters (I would do that ONLY if there was a LOT of stuff to do).
function createMyContextTextField():TextField{
textfield:TextField = new textField();
textfield.selectable = false;
textfield.wordWrap = true;
textfield.x = 106;
return textfield;
}
Okay by deleting correctly I mean am I actually getting rid of the instance or is it just not being drawn anymore? I should mention that I'm trying to delete the instance from within its own class, that is it deletes itself. It 'works' in that the square it draws no longer appears on the screen but again I'm not sure if it's really gone or just not being drawn. Anyway here's the class:
package
{
import flash.display.*;
import flash.events.*;
public class OBJECT_bullet_1 extends Sprite
{
public var X:int = 0; public var Y:int = 0;
public var Y_SPEED:int = 5;
public var DEPTH:int = 9;
public var CONTAINER:Sprite = new Sprite();
public function CREATE(CONTAINER:Sprite,X:int,Y:int):void
{
this.CONTAINER = CONTAINER;
CONTAINER.stage.addEventListener(Event.ENTER_FRAME,STEP);
this.X = X; this.Y = Y;
DRAW();
}
public function STEP(event:Event):void
{
this.graphics.clear();
Y -= Y_SPEED;
if (Y < 20) {Y = 300; CONTAINER.removeChild(this); CONTAINER.stage.removeEventListener(Event.ENTER_FRAME,STEP); CONTAINER.(delete this); CONTAINER = null; return;}
DRAW();
}
public function DRAW():void
{
this.graphics.beginFill(0xCCCC00,1);
this.graphics.drawRect(X - 2,Y - 2,4,4);
this.graphics.endFill();
CONTAINER.addChild(this);
}
}
}
The part I'm concerned about is in the STEP function when it checks to see if Y < 20. You'll notice that it does several things afterwords. Am I deleting it correctly? If so is there anything I am doing to delete it that I don't need to?
Yes to both questions. To ensure an object is deleted, all you have to do is remove all references to it. The child reference and event callback are the only ones the above code is aware of, and you have taken care to remove them both. Nullifying your own container reference is unnecessary, as is whatever you think CONTAINER.(delete this) does.
There are some other significant problems with your supplied code. I made some improvements and heavily commented all changes to explain why I made them.
// You should avoid using the default package. Using the default package
// can make it difficult later on if you start having naming conflicts.
package com.stackoverflow.example {
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.utils.getTimer;
// Class names are spelled in CamelCase by convention. Also, note
// that "Object" has a special meaning in AS3 so you should avoid
// using it to refer to anything else. I used here "Entity" instead.
public class EntityBullet1 extends Sprite {
// ALLCAPS when used are reserved for static const names.
// A good use of static consts is to store "magic numbers".
public static const DEFAULT_COLOR:uint = 0xCCCC00;
public static const DEFAULT_SPEED_X:Number = 0;
public static const DEFAULT_SPEED_Y:Number = -100;
public static const DEFAULT_SIZE:Number = 4;
// I'm calculating the time between frames for smoother movement.
public var lastTime:int;
public var color:uint = DEFAULT_COLOR;
public var size:int = DEFAULT_SIZE;
// Instead of separate x and y vars, you can use the Point class.
public var pos:Point;
public var speed:Point;
// Instead of a "create" method do all creation inside the constructor!
public function EntityBullet1(x:Number = 0, y:Number = 0) {
pos = new Point(x, y);
speed = new Point(DEFAULT_SPEED_X, DEFAULT_SPEED_Y);
// You don't need the parent container to access the ENTER_FRAME
// event. Every DisplayObject has its own. Much simpler.
addEventListener(Event.ENTER_FRAME, firstStep);
}
public function draw():void {
// Keep all drawing inside the draw function. Previously,
// clear() was being called inside the step method.
graphics.clear();
graphics.beginFill(color);
graphics.drawRect(pos.x - size/2, pos.y - size/2, size, size);
graphics.endFill();
}
// On the first frame, the field "lastTime" is still uninitialized.
// This method initializes it to the current time and hands off
// future events to the proper step() method.
public function firstStep(event:Event):void {
removeEventListener(Event.ENTER_FRAME, firstStep);
addEventListener(Event.ENTER_FRAME, step);
lastTime = getTimer();
step(event);
}
public function step(event:Event):void {
// To move at a fixed rate regardless of how fast the framerate is,
// you need to calculate the time delta.
var cur:int = getTimer();
var delta:Number = (cur - lastTime) / 1000.0;
lastTime = cur;
// Position equals velocity times time.
pos.x += speed.x * delta;
pos.y += speed.y * delta;
draw();
// Note that all DisplayObjects already have references to their
// parent containers called "parent"!
if (pos.y < 20) {
if (parent != null) parent.removeChild(this);
removeEventListener(Event.ENTER_FRAME, step);
}
}
}
}
I need to monitor the direction a user is indicating using the four directional arrow keys on a keyboard in ActionScript 3.0 and I want to know the most efficient and effective way to do this.
I've got several ideas of how to do it, and I'm not sure which would be best. I've found that when tracking Keyboard.KEY_DOWN events, the event repeats as long as the key is down, so the event function is repeated as well. This broke the method I had originally chosen to use, and the methods I've been able to think of require a lot of comparison operators.
The best way I've been able to think of would be to use bitwise operators on a uint variable. Here's what I'm thinking
var _direction:uint = 0x0; // The Current Direction
That's the current direction variable. In the Keyboard.KEY_DOWN event handler I'll have it check what key is down, and use a bitwise AND operation to see if it's already toggled on, and if it's not, I'll add it in using basic addition. So, up would be 0x1 and down would be 0x2 and both up and down would be 0x3, for example. It would look something like this:
private function keyDownHandler(e:KeyboardEvent):void
{
switch(e.keyCode)
{
case Keyboard.UP:
if(!(_direction & 0x1)) _direction += 0x1;
break;
case Keyboard.DOWN:
if(!(_direction & 0x2)) _direction += 0x2;
break;
// And So On...
}
}
The keyUpHandler wouldn't need the if operation since it only triggers once when the key goes up, instead of repeating. I'll be able to test the current direction by using a switch statement labeled with numbers from 0 to 15 for the sixteen possible combinations. That should work, but it doesn't seem terribly elegant to me, given all of the if statements in the repeating keyDown event handler, and the huge switch.
private function checkDirection():void
{
switch(_direction)
{
case 0:
// Center
break;
case 1:
// Up
break;
case 2:
// Down
break;
case 3:
// Up and Down
break;
case 4:
// Left
break;
// And So On...
}
}
Is there a better way to do this?
You can keep track of whether each key is down or not by listening for all KEY_DOWN and KEY_UP events, and storing each key state in an array. I wrote a class a while ago to do just that (included at the end of my answer).
Then you are no longer tied to the event model to know if a certain key is down or not; you can periodically check every frame (or every timer interval). So you could have a function like:
function enterFrameCallback(e:Event):void
{
var speed:Number = 1.0; // net pixels per frame movement
thing.x += (
-(int)Input.isKeyDown(Keyboard.LEFT)
+(int)Input.isKeyDown(Keyboard.RIGHT)
) * speed;
thing.y += (
-(int)Input.isKeyDown(Keyboard.UP)
+(int)Input.isKeyDown(Keyboard.DOWN)
) * speed;
}
which would take into account all possible combinations of arrow key presses. If you want the net displacement to be constant (e.g. when going right and down at same time, the object moves X pixels diagonally, as opposed to X pixels in both horizontal and vertical directions), the code becomes:
function enterFrameCallback(e:Event):void
{
var speed:Number = 1.0; // net pixels per frame movement
var displacement:Point = new Point();
displacement.x = (
-(int)Input.isKeyDown(Keyboard.LEFT)
+(int)Input.isKeyDown(Keyboard.RIGHT)
);
displacement.y = (
-(int)Input.isKeyDown(Keyboard.UP)
+(int)Input.isKeyDown(Keyboard.DOWN)
);
displacement.normalize(speed);
thing.x += displacement.x;
thing.y += displacement.y;
}
Here is the Input class I wrote (don't forget to call init from the document class). Note that it also keeps track of mouse stuff; you can delete that if you don't need it:
/*******************************************************************************
* DESCRIPTION: Defines a simple input class that allows the programmer to
* determine at any instant whether a specific key is down or not,
* or if the mouse button is down or not (and where the cursor
* is respective to a certain DisplayObject).
* USAGE: Call init once before using any other methods, and pass a reference to
* the stage. Use the public methods commented below to query input states.
*******************************************************************************/
package
{
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.display.Stage;
import flash.geom.Point;
import flash.display.DisplayObject;
public class Input
{
private static var keyState:Array = new Array();
private static var _mouseDown:Boolean = false;
private static var mouseLoc:Point = new Point();
private static var mouseDownLoc:Point = new Point();
// Call before any other functions in this class:
public static function init(stage:Stage):void
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown, false, 10);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp, false, 10);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown, false, 10);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp, false, 10);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove, false, 10);
}
// Call to query whether a certain keyboard key is down.
// For a non-printable key: Input.isKeyDown(Keyboard.KEY)
// For a letter (case insensitive): Input.isKeyDown('A')
public static function isKeyDown(key:*):Boolean
{
if (typeof key == "string") {
key = key.toUpperCase().charCodeAt(0);
}
return keyState[key];
}
// Property that is true if the mouse is down, false otherwise:
public static function get mouseDown():Boolean
{
return _mouseDown;
}
// Gets the current coordinates of the mouse with respect to a certain DisplayObject.
// Leaving out the DisplayObject paramter will return the mouse location with respect
// to the stage (global coordinates):
public static function getMouseLoc(respectiveTo:DisplayObject = null):Point
{
if (respectiveTo == null) {
return mouseLoc.clone();
}
return respectiveTo.globalToLocal(mouseLoc);
}
// Gets the coordinates where the mouse was when it was last down or up, with respect
// to a certain DisplayObject. Leaving out the DisplayObject paramter will return the
// location with respect to the stage (global coordinates):
public static function getMouseDownLoc(respectiveTo:DisplayObject = null):Point
{
if (respectiveTo == null) {
return mouseDownLoc.clone();
}
return respectiveTo.globalToLocal(mouseDownLoc);
}
// Resets the state of the keyboard and mouse:
public static function reset():void
{
for (var i:String in keyState) {
keyState[i] = false;
}
_mouseDown = false;
mouseLoc = new Point();
mouseDownLoc = new Point();
}
///// PRIVATE METHODS BEWLOW /////
private static function onMouseDown(e:MouseEvent):void
{
_mouseDown = true;
mouseDownLoc = new Point(e.stageX, e.stageY);
}
private static function onMouseUp(e:MouseEvent):void
{
_mouseDown = false;
mouseDownLoc = new Point(e.stageX, e.stageY);
}
private static function onMouseMove(e:MouseEvent):void
{
mouseLoc = new Point(e.stageX, e.stageY);
}
private static function onKeyDown(e:KeyboardEvent):void
{
keyState[e.keyCode] = true;
}
private static function onKeyUp(e:KeyboardEvent):void
{
keyState[e.keyCode] = false;
}
}
}