ENTER_FRAME event idle occurrence or was gotoAndPlay called? - actionscript-3

I have an MC with frame labels that are jumped through. Currently, I can detect when it starts a new label, but I also want to be able to restart the actions should the current label get called with gotoAndPlay() without ENTER_FRAME calling it 30 times per second. Any idea how to filter that out?
private function onNewFrame(e:Event) {
if(e.target.currentLabel != _currentLabel) {
// started new label (working)
trace("New label: "+ e.target.currentLabel);
}else if(e.target.currentFrame == _currentFrame && e.target.isPlaying) {
// repeated frame (doesn't work)
trace("Repeated label: "+ e.target.currentFrame);
}
_currentFrame = e.target.currentFrame;
_currentLabel = e.target.currentLabel;
}

As far as I understand, you need to know next two things:
1). When movieclip advances to next frame with the new frame label;
2). When the next frame has the same label as the previous.
First of all MovieClip doesn't has "isPlaying" property. It sounds unrealistic, but there is no built-in method to check whether MC is playing. So e.target.isPlaying will always return false; therefore your second "if" statement will also always return false.
Also you don't need that "isPlaying" check at all because MovieClip dispatches Event.ENTER_FRAME only when it plays.
So this will work:
var testMc:TestMC = new TestMC();
testMc.addEventListener(Event.ENTER_FRAME, onNewFrame);
testMc.play();
private function onNewFrame(e:Event):void
{
if (e.target.currentLabel != _currentLabel)
{
// started new label (working)
trace("New label: " + e.target.currentLabel);
}
else if (e.target.currentFrame !== _currentFrame)
{
// started frame with the same label
trace("New frame with same label: " + e.target.currentFrame);
}
_currentFrame = e.target.currentFrame;
_currentLabel = e.target.currentLabel;
}
But maybe I didn't understand your question?

I ended up solving this problem by manually storing frame label indices in the class constructor. The MovieClip property currentFrameLabel should take care of this, however my tests show that it is always undefined.
public class DynamicMC extends MovieClip {
private var _currentLabel:String;
private var _currentFrame:int;
private var _labels:Object = {};
public function DynamicPlanComparison() {
super();
findLabels();
}
private function findLabels():void {
for(var i:uint = 0; i < currentLabels.length; i++) {
var l:FrameLabel = currentLabels[i];
_labels[l.name] = l;
}
}
private function onNewFrame(e:Event) {
if(e.target.currentLabel != _currentLabel) {
// started new label
trace("New label: "+ e.target.currentLabel);
}else if(_labels[e.target.currentLabel].frame == e.target.currentFrame) {
// repeated label
trace("Repeated label: "+ e.target.currentLabel);
}
_currentFrame = e.target.currentFrame;
_currentLabel = e.target.currentLabel;
}
}

Related

How to check if user clicked on smaller object before larger object?

Hey Everyone so I am currently working on a game and the objective is for the user to click on objects on the stage. But the user has to click on the largest objects first before the user clicks on the smaller objects. I wanted to make it that if the user clicks on a smaller object first and not the larger one then the game will be over. I thought I could go about setting this up with booleans for each object on the stage but my if statements arent cutting it here is how I have it set up:
Here are my objects and booleans I use:
//Add box references
public var box_1:MovieClip;
public var box_2:MovieClip;
public var box_3:MovieClip;
public var box_4:MovieClip;
//Booleans
private var b1:Boolean;
private var b2:Boolean;
private var b3:Boolean;
private var b4:Boolean;
now I set all the Booleans to false and if the user clicks on one of the objects I set the Boolean to true.
Here are my if statements tied to my main ENTER_FRAME Listener:
private function level_1():void
{
if (b1)
{
mainScreen.box_1.gotoAndPlay("B1GONE");
b1 = false;
}else
if (b2)
{
mainScreen.box_2.gotoAndPlay("B2GONE");
b2 = false;
}else
if (b3)
{
mainScreen.box_3.gotoAndPlay("B3GONE");
b3 = false;
}else
if (b2 && !b1)
{
endGameCondition();
}
}
On this statement:
if (b2 && !b1)
{
endGameCondition();
}
I was trying to state that if box_2 is true meaning that its clicked on and box_1 hasnt been clicked on yet which is the larger object then the game is now over due to the user not clicking on the largest object first. I have it setup to where box_1 is the largest object and the others are the next size down.
Can anyone see why this isnt working correctly or if there is a better method in doing this?
**** UPDATE HOW MY MAIN CLASS IS SETUP NOW **************
Where I add all my Movie clips and variables:
public class boxTapEngine extends MovieClip
{
//Screens
private var mainScreen:mcMainScreen;
//Add box references
public var box_1:MovieClip;
public var box_2:MovieClip;
public var box_3:MovieClip;
public var box_4:MovieClip;
private var aBoxesArray:Array;
in my constructor function:
aBoxesArray = [box_1, box_2, box_3, box_4];
//AddMainScreen
mainScreen = new mcMainScreen();
mainScreen.x = (stage.stageWidth / 2);
mainScreen.y = (stage.stageHeight / 2);
stage.addChild(mainScreen);
//Initiate numbers
nLevel = 1;
for each(var box:MovieClip in aBoxesArray)
{
box.addEventListener(MouseEvent.CLICK, boxClick);
}
finally on the boxClick function:
private function boxClick(e:MouseEvent):void
{
var box:MovieClip = e.currentTarget as MovieClip; //get a reference to one that was just clicked
box.mouseEnabled = false; //we'll use this as a flag to know if it's been clicked yet
box.gotoAndPlay("BGONE");
//check to see if previous boxes have been clicked.
//this will iterate through all the boxes in order
for (var i:int = 0; i < aBoxesArray.length; i++)
{
if(aBoxesArray[i] == box) return; //if we've reached the currently clicked box, all is good, no need to keep checking so let's exit this loop and function
if (!aBoxesArray[i].mouseEnabled)
{ //one of the previous boxes hasn't been clicked yet
endGameCondition();
}
}
}
Probably isn't working because your final else if statement won't ever be reached (because you're handling b2 == true earlier on which will then bypass all other else statements). Plus your setting b2 to false when you handle it earlier, so it will always be false by the time it gets your final statement.
You need to move that final else if before you check for the other things. See code comments:
//Do this first, and not as a else if
if (b2 && !b1){
endGameCondition();
return; //no need to check checking things if it's game over
}
//now you can do the rest
if (b1)
{
mainScreen.box_1.gotoAndPlay("B1GONE");
b1 = false;
}else
if (b2)
{
mainScreen.box_2.gotoAndPlay("B2GONE");
b2 = false;
}else
if (b3)
{
mainScreen.box_3.gotoAndPlay("B3GONE");
b3 = false;
}
As an aside, you don't need the enter frame handler, you can just check everything on click. Something like this would work:
var boxes:Array = [box_1,box_2,box_3,box_4]; //make sure this is in order of what needs to be clicked first
//if you wanted to actually sort them dynamically based off total size (regardless of what order you've stuffed them in the array), you could add in something like this, which will order them from biggest to smallest:
boxes.sort(function(a,b){
//compare which item has a greater total area (width * height)
if(a.width * a.height > b.width * b.height) return -1; //A is bigger, so put a before b in the array
if(a.width * a.height < b.width * b.height) return 1; //put b before a in the array
return 0; //return 0 if they are the same
});
for each(var box:MovieClip in boxes){
box.addEventListener(MouseEvent.CLICK, boxClick,false,0,true);
}
function boxClick(e:Event):void {
var box:MovieClip = e.currentTarget as MovieClip; //get a reference to one that was just clicked
box.mouseEnabled = false; //we'll use this as a flag to know if it's been clicked yet
box.gotoAndPlay("BGONE");
//or you could just do this to get rid of the box:
if(box.parent) box.parent.removeChild(box);
//check to see if previous boxes have been clicked.
//this will iterate through all the boxes in order
for(var i:int=0;i<boxes.length;i++){
if(boxes[i] == box) return; //if we've reached the currently clicked box, all is good, no need to keep checking so let's exit this loop and function
if(!boxes[i].mouseEnabled){ //one of the previous boxes hasn't been clicked yet
endGameCondition();
}
}
}
//Store your clips in an array
var myClips:Array = [mc1,mc2,mc3,mc4];
//get the clip sizes and store it to an array.
var sizes:Array = new Array();
for (var i:uint = 0; i < myClips.length; i++) {
sizes.push(myClips[i].width);
}
//apply Numeric array sort.
sizes.sort(Array.NUMERIC);
function onClickAction(e:MouseEvent):void {
//Check wheather the array is empty or not.
if (sizes.length != 0) {
//Check wheather the clicked object bigger or not.
if (e.target.width == sizes[sizes.length - 1]) {
trace("Bigger");
e.target.alpha = .5;
sizes.splice(sizes.length-1,1);
} else {
trace("Smaller");
}
}
}

AS3: Why is a line created with .graphics appearing in two different places and when removed with parent.visible = false, only one goes?

Nobody seems to have this question already so I asked it because I've spent a few hours trying to debug this and can't find a solution;
Essentially, I have a function called draw, which is declared in my document class:
public function draw(Target: MovieClip,mX: int,mY: int,lX: int,lY: int):void {
Target.graphics.clear();
Target.graphics.lineStyle(1,0x000000,1);
Target.graphics.moveTo(mX,mY);
Target.graphics.lineTo(lX,lY);
}
I call it later to draw two lines, on two different MovieClips:
draw(Line,Line.mX,Line.mY,Mirror.x + (Mirror.width / 2),Line.lY);
draw(nextLine,(Mirror.x + (Mirror.width / 2)),200,(Mirror.x + (Mirror.width / 2)),0);
where
var Line: MovieClip = new MovieClip();
var Mirror: MovieClip = new mirror();
and Mirror is draggable, so Mirror.x changes whenever it is dragged.
Line is a line made using .graphics and Line.mX is equal to the Line.graphics.moveTo X value last time it was modified. Line.mY is the same, but for the Y coordinate. I set these values by doing this:
Line.mX = 0;
Line.mY = 200;
Line.lX = 550;
Line.lY = 200;
But with whatever values I want to draw the line, with lX and lY being equal to the X and Y coordinates of Line.graphics.lineTo. Then I draw Line using my draw function like this:
draw(Line,Line.mX,Line.mY,Line.lX,Line.lY);
Then it gets more complex because, actually, Line is just one line in an array of lines, created like this:
public var lines = [line0,line1,line2,line3,line4,line5,line6,line7,line8];
and each of those lines is created like this (with 0 being replaced by the line's number, respectively):
public var line0: MovieClip = new MovieClip();
then I give each line a number and a name, add them to the stage and hide them like this:
for each(var setupLine:MovieClip in lines) {
setupLine.num = (lines.indexOf(setupLine));
setupLine.name = ('line' + setupLine.num);
addChild(setupLine);
setupLine.visible = false;
}
Then, after making line0 visible, because I need to see it at the start, I loop through each line in a function that runs on ENTER_FRAME, and set the value of nextLine to a different value each time I run the loop like this:
for each(var Line:MovieClip in lines) {
nextLine = this['line' + (Line.num + 1)];
}
Within that loop, I then loop through a few other arrays, then check for a collision with the selected Line and another selected MovieClip from another array, which I wont go into or this question will be longer than the code for node.js.
So essentially, if the collision with the two MovieClips is present, I draw the line that I mentioned at the top of my question. But for some reason, although Line draws correctly, nextLine draws correctly, but a duplicate of it is drawn across the Y axis at 0, and stops where nextLine is on the Y axis (nextLine is vertical, so it has the same Y value at the start as at the end).
Even stranger, when I try to hide nextLine if the collision with the two MovieClips is no longer present, using this code:
nextLine.visible = false;
it only hides the version of nextLine that runs along the top of the stage, which I didn't even intend to create in the start.
EDIT
here is a link to the current source code
Here is a link to the entire project files with the original source code
copy/paste the new source code from the pastebin link to get the new version
Thanks in advance,
-Raph
I figured out how to do this, code is
package {
import flash.events.*;
import flash.utils.*;
import flash.display.*;
[SWF(backgroundColor="0xbdc3c7")]
public class LightStage extends MovieClip {
//import classes
public var globeClass:Globe = new Globe();
public var mirrorClass:Mirror = new Mirror();
public var lineClass:Line = new Line();
//create all stage objects
public var curLine:Line
public var nextLine:Line;
public var curMirror:Mirror;
//create containers
public var mirrors:Vector.<Mirror> = new Vector.<Mirror>(); //a vector is an array, but every member has to be (or subclass) the specified class
public var globes:Vector.<Globe> = new Vector.<Globe>();
public var lines:Vector.<Line> = new Vector.<Line>();
trace('lightstage: working');
//create level object
public var curLevel:int = -1;
//create dependent variables
public var kill: Boolean = true;
//init function
public function LightStage() {
//setup MovieClips
var i:int = 0;
for (i = 0; i < 4; i++) {
mirrors.push(new Mirror());
}
for (i = 0; i < 4;i++ ) {
globes.push(new Globe());
}
var tmpLine:Line;
for (i = 0; i < 10; i++) {
tmpLine = new Line();
lines.push(tmpLine);
addChild(tmpLine);
tmpLine.visible = false;
}
//create ENTER_FRAME listener
stage.addEventListener(Event.ENTER_FRAME,enterFrame);
//start the game
levelUp();
}
//levelUp function
public function levelUp() {
curLevel++;
curLine = lines[curLevel]; //set line to the current level
curLine.curX = 0;
curLine.curY = 200;
curLine.draw(550, 200);
curLine.visible = true;
//show and position mirrors and globes
curMirror = mirrors[curLevel];
addChild(curMirror);
curMirror.x = 250;
curMirror.y = 350;
var curGlobe:Globe = globes[curLevel];
addChild(curGlobe);
curGlobe.x = 100;
curGlobe.y = 50;
//set mirror types
curMirror.gotoAndStop(2);
trace("you are now on level " + (curLevel + 1) + "!");
}
//ENTER_FRAME function
public function enterFrame(event:Event) {
//line1.visible = true;
for (var i:int = 0; i < lines.length;i++){
if (i < lines.length - 1) nextLine = lines[i + 1]; //check for out of bounds before assignment next line
if (lines[i].visible == true) {
kill = true;
for each(var mirror:Mirror in mirrors) {
if (lines[i].visible && mirror.stage && mirror.hitTestObject(lines[i])) { //for efficiency, do the hit test last in the if statement
for each(var globe:Globe in globes) {
//Looped through Mirrors and Lines and checked for collision - if collision is present, we loop through globes here
if (nextLine && nextLine.stage) {
addChild(nextLine);
}
//check for active globes
if (lines[i].visible && lines[i].hitTestObject(globe)) {
//check if the selected line touches the selected globe - if it does then we will start the timer for that globe
if (!globe.running){
globe.start();
//trace('timing');
kill = false;
}
}
else {
globe.reset();
}
switch(mirror.currentFrame) {
case 1:
break;
case 2:
//trace('live a life you will remember' + Math.random());
if(nextLine) nextLine.visible = true;
lines[i].draw(mirror.x + (mirror.width / 2),lines[i].curY);
if (nextLine) {
nextLine.curX = mirror.x + (mirror.width / 2);
nextLine.curY = 200;
nextLine.draw(mirror.x + (mirror.width / 2), 0);
}
kill = false;
break;
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
case 12:
trace(mirror.currentFrame);
kill = false;
break;
}
}
}
else if (lines[i].visible && mirror.stage && lines[i].stage){
if (kill && nextLine){
nextLine.graphics.clear();
nextLine.visible = false;
}
}
}
}
}
}
}
}
//MIRROR CLASS DECLARATION
import flash.events.MouseEvent;
class Mirror extends MovieClip {
trace('mirror: working');
public function Mirror() {
this.addEventListener(MouseEvent.MOUSE_DOWN,onDown,false,0,true);
}
private function onDown(e:MouseEvent):void {
//add the mouse up listener on the stage, that way it's consistent even if the user drags so fast that the mouse leaves the bounds of the mirror
stage.addEventListener(MouseEvent.MOUSE_UP, onUp, false, 0, true);
this.startDrag();
}
private function onUp(e:MouseEvent):void {
//we need to remove the listener from the stage now
stage.removeEventListener(MouseEvent.MOUSE_UP, onUp, false);
this.stopDrag();
}
}
//LINE CLASS DECLARATION
import flash.display.Graphics;
class Line extends MovieClip {
trace('line: working');
public var curX:int;
public var curY:int;
public function Line():void {
}
public function draw(toX:int,toY:int):void {
graphics.clear();
graphics.lineStyle(1,0x000000,1);
graphics.moveTo(curX,curY);
graphics.lineTo(toX, toY);
curX = toX;
curY = toY;
}
}
//GLOBE CLASS DECLARATION
import flash.display.MovieClip;
import flash.events.TimerEvent;
import flash.utils.Timer;
class Globe extends MovieClip {
trace('globe: working');
private var timer:Timer = new Timer(3 * 100, 5);
public function Globe():void {
timer = new Timer(300, 5);
timer.addEventListener(TimerEvent.TIMER, repeatShine, false, 0, true);
}
public function reset():void {
timer.reset();
}
public function start():void {
timer.start();
}
public function get running():Boolean { return timer.running; };
private function repeatShine(e:TimerEvent):void {
}
}

timerEvent within enterFrame function?

I'm calling a timerEvent function from enterFrame, it's running the timerEvent function on everyFrame. Is there a way to control it?
I've got my bullets firing function with timerEvent of 500, so it shoots a bullet every half a second on 24fps. It's working fine for now. Now I want to change bullet speed and skin with respect to weapon.
////////////////////////////////
//this function is called first time within an EnterFrame function
///////////////////////////////
function weaponCheck():void
{
switch (weaponState)
{
case STATE_GUN :
gun();
break;
case STATE_DOUBLE_GUN :
doubleGun();
break;
}
}
function gun():void
{
trace("single gun");
laserTimer = new Timer(600);
laserTimer.addEventListener(TimerEvent.TIMER, timerListener);
laserTimer.start();
function timerListener(e:TimerEvent):void
{
var tempLaser:MovieClip = new Laser();
var tempGunBlast:MovieClip = new Gun_blast_01();
tempLaser.x = player.x +((player.width/2)+12);
tempLaser.y = player.y;
tempGunBlast.x = stage.mouseX + 104;
tempGunBlast.y = tempLaser.y;
Lasers.push(tempLaser);
addChildAt(tempLaser,1);
addChildAt(tempGunBlast, 3);
if (tempGunBlast.currentFrame >= tempGunBlast.totalFrames)
{
removeChild(tempGunBlast);
}
}
}
function doubleGun():void
{
trace("Double gun");
doubleGunTimer = new Timer(400);
doubleGunTimer.addEventListener(TimerEvent.TIMER, timerListener2);
doubleGunTimer.start();
function timerListener2(e:TimerEvent):void
{
var tempLaser:MovieClip = new doubleG();
var tempGunBlast:MovieClip = new Gun_blast_01();
tempLaser.x = player.x +((player.width/2)+12);
tempLaser.y = player.y;
tempGunBlast.x = stage.mouseX + 104;
tempGunBlast.y = tempLaser.y;
Lasers.push(tempLaser);
addChildAt(tempLaser,1);
addChildAt(tempGunBlast, 3);
if (tempGunBlast.currentFrame >= tempGunBlast.totalFrames)
{
removeChild(tempGunBlast);
}
}
}
///////////////////////////////////////////////////
//Following function is called within an enterFrame event function
/////////////////////////////////////////////////
function playGame():void
{
weaponCheck();
blah1();
blah2();
blah3();
testForEnd();
}
function testForEnd():void
{
if (level == 3)
{
laserTimer.stop();
weaponState = STATE_DOUBLE_GUN;
weaponCheck();
}
}
So when the game runs for first time, it works fine and uses the timer event of 600 to hit the bullets, but when level == 3 and weaponState changes, the 2nd firing function doubleGun(); is called but it starts to fire the bullets on a per frame count, not on a controlled timerEvent. Please Help. Thanks.
Why don't you drop timers and use enter frame listener as a manner to count time? You are already calling weaponCheck() from an enterframe listener. Make it so that the actual gun() and doublegun() calls will only generate animation, such as firin' mah lazers and blasts, and the main function will just count time.
function weaponCheck():void
{
this.reloading+=this.weaponFiringSpeed; // you alter this to make your weapon fire slower or faster
if (this.reloading<FULLY_RELOADED) return; // we are not yet ready to fire
this.reloading=0;
switch (weaponState) // and here we fire with the gun state
{
case STATE_GUN :
gun();
break;
case STATE_DOUBLE_GUN :
doubleGun();
break;
}
}
function gun():void
{
trace("single gun");
var tempLaser:MovieClip = new Laser();
var tempGunBlast:MovieClip = new Gun_blast_01();
tempLaser.x = player.x +((player.width/2)+12);
tempLaser.y = player.y;
tempGunBlast.x = stage.mouseX + 104;
tempGunBlast.y = tempLaser.y;
Lasers.push(tempLaser);
addChildAt(tempLaser,1);
addChildAt(tempGunBlast, 3);
}
And similarly double gun. FULLY_RELOADED is a constant, reloading is a variable used to track time, it should be a property of the one who's firing.
Note, this approach requires you to manage your "tempGunBlast"s elsewhere, perhaps in the very weaponCheck function, if so, modify it as follows:
function weaponCheck():void
{
if (tempGunBlast) if (tempGunBlast.currentFrame >= tempGunBlast.totalFrames)
removeChild(tempGunBlast);
this.reloading+=this.weaponFiringSpeed; // you alter this to make your weapon fire slower or faster
if (this.reloading<FULLY_RELOADED) return; // we are not yet ready to fire
... // rest of code unchanged
You will most likely not be able to copypastely implement this, but please try.

Resolving a 1069 error between a child object and a parent function

I've shortened the code samples below so that it's more readable. Here's the rub:I create a whole bunch of MovieClips containing the letters a-z. These MovieClips are children of a parent MovieClip called "levelTwo", where levelTwo is a kind of a logic manager for the level.
When they're clicked, they send off some data to levelTwo for evaluation (It's Hangman). Problem is that on click I get a 1069 error. It WAS a 1061 error until I added the event.target.parent bit in LetterButton.
Here's the relevant code:
package {
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.Event;
public class LetterButton extends MovieClip {
var buttonText:String;
public function LetterButton(lText:String,objX:int,objY:int)
{
trace ("Creating new button");
x = objX;
y = objY;
buttonText = lText;
letterText.text = buttonText;
this.stop();
addEventListener(MouseEvent.MOUSE_OVER,onMouseOver);
addEventListener(MouseEvent.MOUSE_OUT,onMouseOut);
this.addEventListener(MouseEvent.CLICK,onMouseClick);
}
private function onMouseOver(event:Event):void
{
gotoAndStop(2);
letterText.text = buttonText;
//trace ("You're over me and my text is " + buttonText);
}
private function onMouseOut(event:Event):void
{
gotoAndStop(1);
letterText.text = buttonText;
//trace ("You're out of me and my text is " + buttonText);
}
private function onMouseClick(event:Event):void
{
trace ("I am clicked and I am " + buttonText);
event.target.parent.checkGuess(buttonText);
}
}
}
And the relevant bit from levelTwo:
public function checkGuess(guess:String):void
{ //Check to see if the guess matches the string
trace ("Guess: "+guess);
for(var i:int=0;i<answer.length;i++)
{
if(guess == answer.charAt(i))
{
censoredAnswer[i] = guess;
trace ("Got one right");
answerField.text = answerRedisplay(); //Do it now or it won't update for the check
}
}
if (answerField.text == answer)
{
setWin();
}
}
I see no magic here. You subscribe to LetterButton instance, so you get event.target reference pointing to it.
I think a better approach would be to subscribe to parent clip, that contains all the LetterButton instances. MouseEvent.CLICK is a bubbling event, so you will get your handler triggered on parent every time something is clicked inside it.
See the sample code.
// inside level two class
addEventListener(MouseEvent.CLICK,onMouseClick);
private function onMouseClick(event:MouseEvent):void
{
var target:LetterButton = event.target as LetterButton;
if (target == null) {
return;
}
// here we know, that some LetterButton instance was clicked
// and target var holds it's reference
// either make buttonText public, or create a getter/accessor
// var text:String = target.buttonText;
var text:String = target.getButtonTextSomehow();
checkGuess(text);
}

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