Actionscript: Am I deleting this class instance correctly? - actionscript-3

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);
}
}
}
}

Related

AS3 Update Variable Reports Null.. in a pattern

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.

AS3 Projectile moves incorrectly

So I'm currently attempting to make a prototype for a Bullet Hell game and I've run into a bit of a dead end.
So far I can move my player perfectly, the boss moves back and forth as he is supposed to, however the projectiles have some funny behaviour. Basically, when the boss moves left/right, so do the projectiles as if they are stuck to him. They move on the y as they are supposed to, except they stop just short of the player and move no further, so I'm hoping anyone can take a look at my code and give me a hand with what's going on.
Note: Ignore the rotation stuff, that's for later implementation, I was just laying the ground work.
Projectile.as
package
{
import flash.display.Stage;
import flash.display.MovieClip;
import flash.events.Event;
public class Projectile extends MovieClip
{
private var stageRef:Stage;
private var _xVel:Number = 0;
private var _yVel:Number = 0;
private var rotationInRadians = 0;
private const SPEED:Number = 10;
public function Projectile(stageRef:Stage, x:Number, y:Number, rotationInDegrees:Number)
{
this.stageRef = stageRef;
this.x = x;
this.y = y;
this.rotation = rotationInDegrees;
this.rotationInRadians = rotationInDegrees * Math.PI / 180;
}
public function update():void
{
this.y += SPEED;;
if(x > stageRef.stageWidth || x < 0 || y > stageRef.stageHeight || y < 0)
{
//this.removeChild(this); <- Causing a crash, will fix later
}
}
}
}
Boss.as
package
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class Boss extends MovieClip
{
private var stageRef:Stage;
private var _vx:Number = 3;
private var _vy:Number = 3;
private var fireTimer:Timer;
private var canFire:Boolean = true;
private var projectile:Projectile;
public var projectileList:Array = [];
public function Boss(stageRef:Stage, X:int, Y:int)
{
this.stageRef = stageRef;
this.x = X;
this.y = Y;
fireTimer = new Timer(300, 1);
fireTimer.addEventListener(TimerEvent.TIMER, fireTimerHandler, false, 0, true);
}
public function update():void
{
this.x += _vx;
if(this.x <= 100 || this.x >= 700)
{
_vx *= -1;
}
fireProjectile();
projectile.update();
}
public function fireProjectile():void
{
if(canFire)
{
projectile = new Projectile(stageRef, this.x / 200 + this._vx, this.y, 90);
addChild(projectile);
canFire = false;
fireTimer.start();
}
}
private function fireTimerHandler(event:TimerEvent) : void
{
canFire = true;
}
}
}
Edit: Current suggestions have been to do the following:
stage.addChild(projectile); and this.parent.addChild(projectile); both which have the projectile firing from the top left corner (0, 0) and not constantly firing from the current center of the Boss.
The other issue, which has been untouched, is the fast that the projectile stops moving after a certain point and remains on the screen.
Another Edit:
After commenting out the code with the timer I have found that the projectile stops moving entirely. The reason why it was stopping after a certain amount of time was due to the timer, when the timer elapsed the projectile stopped and another would fire.
So now I need the projectile to constantly fire and move until it hits the edge of the screen, any ideas?
The problem is you are 'addChild'ing your projectiles to your Boss as opposed the stage (or the same display level as your Boss). When your Boss moves, your projectiles will move relative to him (ie, when he moves sideways, so will they).
When your boss fires a projectile, use a custom event to trigger a fireProjectile method in the Class that is your Boss' display parent. Instantiate your projectiles there and addChild them to the same object to which you addChild your Boss (possibly the stage?).
Alternatively, if you don't want to use a custom event, in your current fireProjectile method change the addChild line to:
this.parent.addChild(projectile);
This will add projectiles to the parent object of your Boss. Although that line seems, slightly, like cheating to me.

Flash AS3 (shape class) CPU usage and optimization

I'm new to Flash Actionscript 3.0 and object programming in general. I'm trying to create a simple game, which is drawing a shape based on steering.
public class Player extends Shape
{
public var X,Y,v,vX,vY,size,a,r:Number;
public var k,counter,leftKey,rightKey,_color:uint;
public var line:Shape = new Shape();
public var dot:Shape = new Shape();
/*...*/
/*constructor, giving values to variables here, not important*/
/*...*/
public function Move():void
{
a=a+0.05*k;
//player controls k parameter k=0 by default
//k=1 when right key pressed
//k=-1 when left key pressed
vX=v*Math.cos(a);
vY=v*Math.sin(a);
X=X+vX;
Y=Y+vY;
dot.x=X+vX*size/(2*v);
dot.y=Y+vY*size/(2*v);
if (counter==0)
{
line.graphics.lineTo(X,Y);
if (Math.random()<0.008) counter=12;
} else
{
line.graphics.moveTo(X, Y);
counter--;
}
}
}
Function Move is in my Player class, which is called from inifinite TimerEvent function in my Main Class
public function mainLoop(TimerEvent:Event):void
{
for (var i:uint=0; i<players; i++) player[i].Move();
}
It seems to be working well at the beginning but after some time CPU usage raises dramatically and game becomes unplayble. I belivie it's caused by my shape (line) getting more and more complex.
Is there some reasonable way to optimize it? Can I somehow draw a line in less consuming way? I tried to convert it to bitmap but that looked ugly and didn't really help.
Thanks and cheers!
You're right in assuming that your slowdown in coming from your shape code - vector data is redrawn every frame in flash, so the more complex it is, the longer it takes to draw. Some solutions, depending on what you're willing to do:
Your fidelity is way to high - you're calling your Move function every frame; you probably don't need it that high, as the difference in movement since the last frame is probably less than a pixel. Sample your position every X frames instead (where X is the level of fidelity your willing to go down to). This can be done with a simple counter in the enter frame
If you don't need to keep the entire history of the drawing, put all your points into an array/vector, culling the length as needed. Then every frame, do a line.graphics.clear() and just draw the points in the array
If you do need to keep the entire history, then keep a BitmapData under your line (e.g. the size of the stage). Every so often, draw the line to the BitmapData and call clear() on your graphics to get right of the vector data. You shouldn't notice any loss in quality (set smoothing to true when you're drawing)
I'd do the first point in any case, then choose between the second and third, depending on your use case
Expanding my comment, try something like this:
public class Player extends Shape
{
public var X,Y,v,vX,vY,size,a,r:Number;
public var k,counter,leftKey,rightKey,_color:uint;
public var line:Shape = new Shape();
public var dot:Shape = new Shape();
/*...*/
/*constructor, giving values to variables here, not important*/
/*...*/
public function Player(){
//draw shapes
graphics.lineStyle(1);
graphics.drawCircle(0,0,r);
graphics.lineTo(size,0);//can't test this now, but make sure the line is in the same direction as rotation 0 (guessing it's to the right)
//your other constructor code here
}
public function Move():void
{
a=a+0.05*k;
//player controls k parameter k=0 by default
//k=1 when right key pressed
//k=-1 when left key pressed
vX=v*Math.cos(a);
vY=v*Math.sin(a);
X=X+vX;
Y=Y+vY;
x=X+vX*size/(2*v);
y=Y+vY*size/(2*v);
rotation = a * 57.2957795;//quick'n'dirty radians to degrees
}
and if you want to draw the trails you can try something like this:
var canvas:Bitmap = new BitmapData(state.stageWidth,stage.stageHeight,true,0xFF000000);
var ct:ColorTransform = new ColorTransform(1,1,1,.1);
public function mainLoop(TimerEvent:Event):void
{
for (var i:uint=0; i<players; i++) {
player[i].Move();
canvas.draw(player[i],player[i].transform.concatenatedMatrix,ct);
}
}
Hope this makes sense.
Update
Here is a standalone code snippet to illustrate the idea above(which has untested syntax):
package {
import flash.display.Bitmap;
import flash.geom.ColorTransform;
import flash.display.BitmapData;
import flash.text.TextField;
import flash.ui.Keyboard;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.utils.Dictionary;
import flash.display.Sprite;
public class PlayerMoveTest extends Sprite {
private var keys:Dictionary = new Dictionary();
private var players:Vector.<Player> = new Vector.<Player>();
private var trails:BitmapData;
private var fade:ColorTransform = new ColorTransform(1,1,1,.1);
public function PlayerMoveTest() {
addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event):void{
trails = new BitmapData(stage.stageWidth,stage.stageHeight,true,0x00FFFFFF);
addChild(new Bitmap(trails));
for(var i:int = 0 ; i < 2; i++){
var p:Player = addChild(new Player(10+i*10)) as Player;
p.x = stage.stageWidth * .5;
p.y = stage.stageHeight * .5;
players.push(p);
}
stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP,onKeyUp);
stage.addEventListener(Event.ENTER_FRAME,update);
}
private function onKeyDown(e:KeyboardEvent):void{
keys[e.keyCode] = true;
}
private function onKeyUp(e:KeyboardEvent):void{
keys[e.keyCode] = null;
}
private function update(e:Event):void{
if(keys[Keyboard.LEFT] != undefined) {players[0].a -= .05;players[1].a += .05;}
if(keys[Keyboard.RIGHT] != undefined) {players[0].a += .05;players[1].a -= .05;}
if(keys[Keyboard.UP] != undefined) {players[0].s += .15;players[1].s -= .15;}
if(keys[Keyboard.DOWN] != undefined) {players[0].s -= .15;players[0].s += .15;}
for(var i:int = 0 ; i < players.length; i++) {
players[i].move();
trails.draw(players[i],players[i].transform.concatenatedMatrix,fade);
}
}
}
}
import flash.display.*;
class Player extends Shape{
public var vx:Number,vy:Number,a:Number,size:Number,r:Number,s:Number;
public function Player(size:Number){
init(size);
}
private function init(size:Number):void{
vx = vy = a = s = 0;
this.size = size;
this.r = size * .25;
graphics.lineStyle(1);
graphics.drawCircle(0,0,r);
graphics.lineTo(size,0);
}
public function move():void{
rotation = a * 57.2957795;
vx = Math.cos(a) * s;
vy = Math.sin(a) * s;
x += vx;
y += vy;
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x > stage.stageWidth) x = stage.stageWidth-width;
if(y > stage.stageHeight) y = stage.stageHeight-height;
}
}
You can test this code here and here's a preview:
Use the arrow keys to drive(up arrow accelerates, left/right steer).
The first player is the smaller one, having the correct controls, the other is simply mirroring the previous controls)

How can I use Action Script 3.0 to make random placed Symbols fly by?

I'm trying to make a simple animation with Flash CS4 and Action Script 3.0 to make a number of Symbols fly by from right to left constantly. What I want is that once a symbol has reached the end of the screen it is destroyed and another one is placed at the start position.
I intend to give each symbol a random speed and create a random symbol each time one is 'destroyed'. Any clues where I can start?
As you seem new to flash as a platform I would think writing classes shouldn't be your first port of call when learning ActionScript. Definitely just play about on the timeline for now and learn the basics. As very simple solution to this, I would suggest creating a MovieClip in the library with a class name like 'MyBall'... then paste this onto the first frame of the main timeline et voila.
// Create some variables to store data
var numberOfBalls : int = 20;
var myBalls : Array = [];
var xVelocities : Array = [];
var maxXVelocitySpeed : Number = 5;
var minXVelocitySpeed : Number = 2;
// Add your orginal balls to the stage
for (var i : int = 0; i < numberOfBalls; i++)
{
var myBall : MyBall = new MyBall();
myBall.x = -(Math.random() * stage.stageWidth);
myBall.y = Math.random() * stage.stageHeight;
var xVelocity : Number = minXVelocitySpeed + (Math.random() * (maxXVelocitySpeed - minXVelocitySpeed));
myBalls.push(myBall);
xVelocities.push(xVelocity);
addChild(myBall);
}
// Add a listener for enter frame events
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
//Run this code on every frame to move the balls and reposition them if they are off the stage
function enterFrameHandler(event : Event) : void
{
for each( var myBall : MyBall in myBalls)
{
var ballIndex : int = myBalls.indexOf(myBall);
myBall.x += xVelocity[ballIndex];
if (myBall.x > stage.stageWidth)
{
myBall.x = -(Math.random() * stage.stageWidth);
myBall.y = Math.random() * stage.stageHeight;
}
}
}
First, turn your symbols into MovieClips. Then create a base class MySymbol.as for your symbols, something like:
package {
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.Point;
public class MySymbol extends MovieClip
{
public var speed:Number; // Pixels moved per frame
public function MySymbol(speed:Number, startPosition:Point)
{
this.speed = speed;
this.addEventListener(Event.ENTER_FRAME, update);
this.x = startPosition.x;
this.y = startPosition.y;
}
private function update():void
{
this.x -= this.speed;
if (this.x < 0 - this.width) { // We're at the left edge
this.removeEventListener(Event.ENTER_FRAME, update);
this.dispatchEvent(new Event(Event.COMPLETE));
}
}
}
}
Then make sure your movie clips are exported for AS3 (the "linkage" option on the item in the library). Make the class name for each item unique (e.g. MySymbol1, MySymbol2), and set the base class to MySymbol.
Your document class might look something like this:
package {
import flash.display.MovieClip;
import flash.events.Event;
import MySymbol; // Not strictly needed
public class DocumentClass extends flash.display.MovieClip
{
private static var SYMBOLS:Array = new Array(MySymbol1, MySymbol2);
public function DocumentClass()
{
// Create five symbols:
for (var i:int = 0; i < 5; i++) {
makeSymbol();
}
}
private function makeSymbol():void
{
// Pick a random symbol from the array:
var symType:Class = SYMBOLS[Math.random() * SYMBOLS.length];
// Construct the new symbol:
var loc:Point = new Point(stage.stageWidth, Math.random() * stage.stageHeight);
var sym:MySymbol = new symType(1 + Math.random() * 30, loc);
// Listen for the object hitting the left edge:
sym.addEventListener(Event.COMPLETE, remakeObject);
this.addChild(sym);
}
private function remakeObject(e:Event):void
{
e.target.removeEventListener(Event.COMPLETE, remakeObject);
this.removeChild(e.target);
// Replace the dead symbol:
makeSymbol();
}
}
}
It is a lot more efficient if instead of destroying and re-creating an object that flies off-stage you re-use the existing one and move it back to the right. But this is an optimization you can implement later, if things become slow.
Note that all the code above is UNTESTED and I have not coded AS3 in a while, so there's likely at least a few bugs in it. Hopefully it will serve as a good enough starting point.
Define a Circle (symbol) class that extends Sprite/Shape and has a velocity variable
Draw a circle (or whatever) with a random color
Math.floor(Math.random() * 0xffffff)
Assign a random value to velocity
minVelocity + Math.floor(Math.random() * velocityRange)
Create a start() method inside the Circle class that registers an enter frame handler
Increment this.y inside the enter frame handler, and dispatch a 'recycleMe' event if y is more than the max value.
Create N instances of Circle, addChild them, and call their start() methods.
listen to 'recycleMe' events on each of them, and reset the value of y from the handler.
Here's a few prompts to get you started.
MovieClips have an x and y property. If you were to add to these numbers over time you would see the MovieClip move along the x and/or y axis of the stage. Look into doing this using the Event.ENTER_FRAME which will allow you to change the values every time the screen is going to update.
Your stage will have a given width (a stageWidth property). You probably want to monitor when your MovieClip's x property is greater than the width of your stage. If it is remove (removeChild) it and add a new one (addChild) and place it back at the start x/y position.

What is the most effective way to test for combined keyboard arrow direction in ActionScript 3.0?

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;
}
}
}