A really simple AS3 keyboard manager class: OneKeyManager

Hey, all! I'd like to share with you a keyboard input utility I recently made. I wrote it for myself, but I have decided to share it here, in the event that you might find it as easy, useful and convenient as I do. When you write code for keyboard input, there is certain functionality that you are going to always want. There are a few things that come to mind in this regard: First, you need to know whether a key is being held down or not. Secondly, you need to know if a key has just been pressed. Thirdly, you need to know if a key has just been released.

Let's focus on the second thing for a moment. The routine that detects if a key has just been pressed needs to only happen once per keypress. Let's think about the KeyboardEvent.KEY_DOWN event. This event, as it stands anyway, has a big difference from, say, a MouseEvent.CLICK event. The big difference is that the CLICK event gets dispatched only once per mouse click. The keyboard event keeps getting dispatched repeatedly, as long as you keep holding down the key. This effect is known as the keyboard repeat rate, and is actually part of your operating system. I have written about this before in the tutorial on making the left-right-shooter game. The solution is to use a Boolean variable for each key. Set this Boolean variable to true when you get a keypress from that key. Only set it to false again when that same key is released. Finally (and what makes it all work) use the value of this Boolean variable as part of your test to see whether you should process your key down event, like so:

if(event.keyCode == Keyboard.RIGHT && rightArrow == true)

Well, anyway, I am officially tired of writing code like this again and again every time I want to counteract the keyboard repeat rate! It seems like I ought to be able to just encapsulate this functionality into some kind of class I can use again and again. After checking out a couple of the keyboard managers that other people have come up with, I realized they were almost what I wanted but not quite. One of the best was by Senocular, but (as I recall) it satisified requirement 1, but didn't quite satisfy requirement 2 (requirement 3 is not as crucial, as there is no repeat rate involved there).

At first, I just modified Senocular's class and added in the functionality I wanted. I realized, though, that coding it was complicated by the fact that his key manager manages any number of keys, and uses an object to keep track of them all. This is fine for just keeping track if a key is down. I found myself bogging down trying to add the "just pressed" and "just released" parts, though, because I had to again make it capable of managing any number of keys. I was a little less than happy with the result, so I set about making my own KeyManager class. Again, though, I tried to write a class that handled any number of keys, using techniques similar to what Senocular had done (using generic objects to store the keys. Seems like there were static methods involved, too). And again I bogged down. It just seemed too complicated when it really shouldn't have been.

Then an inspiration came to me: Why not make a simpler class, whose sole responsibility was to provide the functionality I needed, but do it for just one key? Also, instead of a bunch of static methods, why not just let it be an ordinary class that you make instances of? The Boolean variable that I formerly created for each key could just be an instance variable in the class. Each instance of the class would get its own copy of this variable, so no more making multiple Booleans! This one simple (private) Boolean variable would be called _isDown. I could make it read-only from the outside by adding a public getter called isDown (without the underscore). 

All you would have to do is to create an instance of this class for each keyboard key you want to program! Having to create mutiple instances is the only drawback I could see. But, in a typical application, you might only program a few keyboard keys anyway. And if you found yourself programming too many keys, you could automate it by using loops and arrays, but in my own use, I really don't anticipate that happening too often.

I also envisioned that the class would hold references for functions that it would call when a key is pressed or released, and that these could be written and set from outside the class. They would be written like ordinary keyboard event handlers. The difference is that the class would take care of nullifying the keyboard repeat rate for me. So unlike ordinary KEY_DOWN and KEY_UP event handlers, these functions would only be called once per key press/release.

One final functionality would be the capability to activate or deactivate an instance of the class. This would be done by setting an active property to true or false, and this would either add or remove the stage listeners depending on the Boolean value passed in. Instead of just deleting an instance of the class, then, you could turn it on and off at will.

So I called this class OneKeyManager, meaning that it's understood that this class manages exactly one keyboard key. Having been very happy with the result, I decided to share it. Whether you use it or not is up to you, but I can tell you that having created two previous key managers (albeit with some help on the first one), the third time seems to be the charm. I certainly intend to use it, and I believe that if you try it, you will probably like it too. And you certainly can't beat the price.

There is also another companion class, KeyList, and on the next page, I'll introduce you to both classes. Then, on the final page, I'll share with you how to use them effectively and give you a demo to download.

The classes: KeyList and OneKeyManager

Note: the classes on this page are included in the ZIP file download at the top of the next page. No need to copy and paste them from here (although you can!).

I have a confession to make about the KeyList class. The truth is that I copied and pasted its code from another source, so I can't claim any kind authorship whatsoever, except maybe the name of the class. However, I believe I could be forgiven for this small bit of plagiarism, as the class is just simply a list of public static constants. The whole thing is generic enough that I'm sure it doesn't matter, but I think everyone should have a KeyList class, however you come by it. I suppose I could have thought up my own names for the keys, but I probably would have come up with exactly the same ones for the most part. I mean, what other names are you going to come up with for the letters A-Z, for example?

Anyway, this class is extremely handy to have on hand! With a public static constant defined for virtually every keyboard key, you are freed from using the raw keyCode numbers, and can just use the names of the constants instead. Further, by importing the class (or having it in the classpath) you will get code hinting, too. So you not only don't have to remember the keyCodes, you don't have to remember the names, either!

Here's the KeyList class:

package {
	public class KeyList {
		public static const BACKSPACE:int = 8;
        public static const TAB:int = 9;
        public static const ENTER:int = 13;
        public static const COMMAND:int = 15;
        public static const SHIFT:int = 16;
        public static const CONTROL:int = 17;
        public static const ALT:int = 18;
        public static const PAUSE:int = 19;
        public static const CAPS_LOCK:int = 20;
        public static const ESCAPE:int = 27;

        public static const SPACE:int = 32;
        public static const PAGE_UP:int = 33;
        public static const PAGE_DOWN:int = 34;
        public static const END:int = 35;
        public static const HOME:int = 36;
        public static const LEFT:int = 37;
        public static const UP:int = 38;
        public static const RIGHT:int = 39;
        public static const DOWN:int = 40;

        public static const INSERT:int = 45;
        public static const DELETE:int = 46;

        public static const ZERO:int = 48;
        public static const ONE:int = 49;
        public static const TWO:int = 50;
        public static const THREE:int = 51;
        public static const FOUR:int = 52;
        public static const FIVE:int = 53;
        public static const SIX:int = 54;
        public static const SEVEN:int = 55;
        public static const EIGHT:int = 56;
        public static const NINE:int = 57;

        public static const A:int = 65;
        public static const B:int = 66;
        public static const C:int = 67;
        public static const D:int = 68;
        public static const E:int = 69;
        public static const F:int = 70;
        public static const G:int = 71;
        public static const H:int = 72;
        public static const I:int = 73;
        public static const J:int = 74;
        public static const K:int = 75;
        public static const L:int = 76;
        public static const M:int = 77;
        public static const N:int = 78;
        public static const O:int = 79;
        public static const P:int = 80;
        public static const Q:int = 81;
        public static const R:int = 82;
        public static const S:int = 83;
        public static const T:int = 84;
        public static const U:int = 85;
        public static const V:int = 86;
        public static const W:int = 87;
        public static const X:int = 88;
        public static const Y:int = 89;
        public static const Z:int = 90;

        public static const NUM0:int = 96;
        public static const NUM1:int = 97;
        public static const NUM2:int = 98;
        public static const NUM3:int = 99;
        public static const NUM4:int = 100;
        public static const NUM5:int = 101;
        public static const NUM6:int = 102;
        public static const NUM7:int = 103;
        public static const NUM8:int = 104;
        public static const NUM9:int = 105;

        public static const MULTIPLY:int = 106;
        public static const ADD:int = 107;
        public static const NUMENTER:int = 108;
        public static const SUBTRACT:int = 109;
        public static const DECIMAL:int = 110;
        public static const DIVIDE:int = 111;

        public static const F1:int = 112;
        public static const F2:int = 113;
        public static const F3:int = 114;
        public static const F4:int = 115;
        public static const F5:int = 116;
        public static const F6:int = 117;
        public static const F7:int = 118;
        public static const F8:int = 119;
        public static const F9:int = 120;
        // F10 is considered 'reserved' by Flash
        public static const F11:int = 122;
        public static const F12:int = 123;

        public static const NUM_LOCK:int = 144;
        public static const SCROLL_LOCK:int = 145;

        public static const COLON:int = 186;
        public static const PLUS:int = 187;
        public static const COMMA:int = 188;
        public static const MINUS:int = 189;
        public static const PERIOD:int = 190;
        public static const BACKSLASH:int = 191;
        public static const TILDE:int = 192;

        public static const LEFT_BRACKET:int = 219;
        public static const SLASH:int = 220;
        public static const RIGHT_BRACKET:int = 221;
        public static const QUOTE:int = 222;
	}
}

Not much to explain there, right? What could be more straightforward? You might want to place this class in one of your own packages, making the appropriate changes to the package statement. For our purposes, and for the demo, we'll just store it in the default package, which will be the same folder as the fla file. Here is the OneKeyManager class:

package {
	import flash.display.Stage;
	import flash.events.KeyboardEvent;
	import flash.ui.Keyboard;
	/**
	 * manages the state of ONE single keyboard key
	 * 
	 * @author Jody Hall
	 */
	public class OneKeyManager {
		private var _stage:Stage;
		private var _keyCode:int;
		private var _pressHandler:Function;
		private var _releaseHandler:Function;
		private var _isDown:Boolean;
		private var _isActive:Boolean;
		
		public function OneKeyManager(theStage:Stage, 
								   keyCode:int, 
								   pressHandler:Function = null, 
								   releaseHandler:Function = null) {
			_stage = theStage;
			_keyCode = keyCode;
			_pressHandler = pressHandler;
			_releaseHandler = releaseHandler;
			_isDown = false;
			
			_stage.focus = _stage;
			_stage.addEventListener(KeyboardEvent.KEY_DOWN, _stage_onKeyDown);
			_isActive = true; //set flag to indicate KEY_DOWN listener is active
		}
		private function _stage_onKeyDown(event:KeyboardEvent):void {
			if (event.keyCode == _keyCode && _isDown == false) {
				_isDown = true;
				_stage.addEventListener(KeyboardEvent.KEY_UP, _stage_onKeyUp);
				if (_pressHandler != null) {
					_pressHandler(event);
				}
			}
		}
		private function _stage_onKeyUp(event:KeyboardEvent):void {
			if (event.keyCode == _keyCode) {
				_stage.removeEventListener(KeyboardEvent.KEY_UP, _stage_onKeyUp);
				_isDown = false;
				if (_releaseHandler != null) {
					_releaseHandler(event);
				}
			}
		}
		public function set active(value:Boolean):void {
			if (_isActive != value) {
				_isActive = value;
				if (_isActive) {
					_stage.focus = _stage;
					_stage.addEventListener(KeyboardEvent.KEY_DOWN, _stage_onKeyDown);
				} else {
					_stage.removeEventListener(KeyboardEvent.KEY_DOWN, _stage_onKeyDown);
				}
			}
		}
		public function get active():Boolean {
			return _isActive;
		}
		public function set pressHandler(value:Function):void {
			_pressHandler = value;
		}
		public function set releaseHandler(value:Function):void {
			_releaseHandler = value;
		}
		public function get isDown():Boolean {
			return _isDown;
		}
	}
}

To me, this class is pretty straightforward and simple, too, but I'll just note a few things. First, the class needs a reference to the stage, so that's mandatory in the constructor's parameter list. Also mandatory is the keycode of whatever key you want to control. This can be passed in as a constant, example: KeyList.RIGHT for the right arrow key. I'll get into how to use this class on the next page, but right now I'll just say that with these two pieces of information passed in (stage and keyCode), you have everything you need to use the "isDown" functionality. The pressHandler and releaseHandler functionality is entirely optional.

I realize this may seem different than my usual tutorial where I explain every single line, but I believe that this class is simple enough that it just kind of documents itself. If you are interested in how it works, just read the code through a time or two and think about what it's doing. Briefly, it stores a reference to the stage, the keyCode, and optionally stores references to one or two event handler functions that you want to use for a pressHandler and a releaseHandler. There are variables that keep track of whether the key is down, and whether the key is active or not. And that's it!

Demo for the OneKeyManager class

Source File: 

Now let's set about creating a demo for the OneKeyManager class. I have created a very simple FLA file and placed it in a folder of its own. I've also placed in the folder the two classes we talked about on the previous page of this article. In any case, you may download all of these files from the link above.

I have drawn a very simple Player object on the stage. I hope you weren't expecting fancy artwork, but I drew my little "guy" so that it is obvious that he is facing to the right. After finishing the drawing, I selected it and converted it to a MovieClip and called the resulting symbol "Player" (Note that the Player symbol was created with its registration point in the center). Then, in the properties panel, I gave the instance on the stage the instance name of "player."

 Creating your fla file

Next, let's turn our attention the actions panel. Click on frame one of the actions layer, and press F9. Since the two classes were placed in the same folder as the FLA file, they are both available to our timeline code without importing them. That's because the folder that the FLA file is in is always in the classpath.

First, we'll make an instance of the OneKeyManager class for the right arrow key. We'll call the instance rightArrow. When you begin typing:

var rightArrow:OneKeyManager = new OneKeyManager(

...as soon as you type the left parentheses, you will get code hinting telling you what kind of arguments the OneKeyMananger class is expecting. The first one is the stage, the second one is the keyCode. As you keep typing:

var rightArrow:OneKeyManager = new OneKeyManager(stage, KeyList.

...as soon as you type the dot after KeyList, you will get another code hinting window that you can scroll. You can simply pick the word RIGHT from the list. Or if you type the R and the I, it will zero right in on it for you. Press ENTER to accept it. And this is how easy it is to program the right arrow key! Finishing the statement:

var rightArrow:OneKeyManager = new OneKeyManager(stage, KeyList.RIGHT);

We'll stop there, although we optionally could go on to provide the names of the functions we want to use for the pressHandler and / or the releaseHandler. Those functions would have to be written, of course, and if we provided their names here, we could go on to write them. However, if we later decide we want to add those, we can also just use the public setter methods that the OneKeyManager class provides for that purpose, as we'll soon demonstrate. Let's make an instance for the left arrow key too:

var leftArrow:OneKeyManager = new OneKeyManager(stage, KeyList.LEFT);

Now we have two instances, leftArrow and rightArrow. Those instances simply represent those keyboard keys. With just this much established, we can use the isDown functionality. Let's write that into an ENTER_FRAME event hander:

var rightArrow:OneKeyManager = new OneKeyManager(stage, KeyList.RIGHT);
var leftArrow:OneKeyManager = new OneKeyManager(stage, KeyList.LEFT);

this.addEventListener(Event.ENTER_FRAME, loop);

function loop(event:Event):void {
	if(rightArrow.isDown) {
		player.x += 10;
	}
	if(leftArrow.isDown) {
		player.x -= 10;
	}
}

Notice how natural the language in this event handler is! It almost reads like plain English! If the right arrow is down, then do this. If the left arrow is down, do that. You can name your instances whatever you want, whatever word or words you think best describes your keyboard key. Now, let's proceed to write a pressHandler for the left arrow key that makes the player face to the left:

leftArrow.pressHandler = leftArrow_onPress;

This sets the pressHandler for the left arrow to a function named leftArrow_onPress, which is a function we have yet to write, so let's do that now:

leftArrow.pressHandler = leftArrow_onPress;

function leftArrow_onPress(event:KeyboardEvent):void {
	player.scaleX = -1;
}

Notice that the function is written like any other keyboard event handler, with an event parameter ready to "catch" the event object passed to it. This is required, as when our class handles the KEY_DOWN event, it calls the function we've set (even though it's outside the class, the class now has a reference to it) and just passes along the same event object it received. This event object is something you may or may not ever use (you already know the event.keyCode, for example), but it is there should you ever need it. I debated about whether to leave it in or not, and decided it didn't hurt anything, and since we should all be used to writing event handler functions, it shoudn't be any problem. You just write this like you would write any others.

Writing a pressHandler for the right arrow key is exactly the same process:

var rightArrow:OneKeyManager = new OneKeyManager(stage, KeyList.RIGHT);
var leftArrow:OneKeyManager = new OneKeyManager(stage, KeyList.LEFT);

this.addEventListener(Event.ENTER_FRAME, loop);

function loop(event:Event):void {
	if(rightArrow.isDown) {
		player.x += 10;
	}
	if(leftArrow.isDown) {
		player.x -= 10;
	}
}

leftArrow.pressHandler = leftArrow_onPress;

function leftArrow_onPress(event:KeyboardEvent):void {
	player.scaleX = -1;
}
rightArrow.pressHandler = rightArrow_onPress;

function rightArrow_onPress(event:KeyboardEvent):void {
	player.scaleX = 1;
}

When you test the movie, you will find that the little "guy" can move left and right, and that he always faces in the correct direction. And if we wanted to, we could define other things that happen when these same keys are released. You can also see how easy it would be to define more keys. You could easily define another set of keys that do the same thing the left and right keys are doing now. Suppose we wanted to define the "D" key, and make it do the same thing the right arrow key is doing. The command could be:

var dKey:OneKeyManager = new OneKeyManager(stage, KeyList.D, rightArrow_onPress); 

Since the rightArrow_onPress function has previously been defined, we can "save a step" by just taking advantage of the OneKeyManager class's constructor, whose third parameter allows us to go ahead and pass in a reference to the function we want to use for a pressHandler. Cool, eh? Let's go ahead and define the "A" key to do the same action as the left arrow:

var aKey:OneKeyManager = new OneKeyManager(stage, KeyList.A, leftArrow_onPress);

Of course, this just makes the new set of keys mimic the first set in terms of the pressHandler. If you want them to move the player, too, you just have to edit the ENTER_FRAME handler and test for the newly defined keys, example:

if(rightArrow.isDown || dKey.isDown) {

The two vertical pipes, are, of couse, the OR operator. The statement is saying that if either the right arrow key is down or the d key is down, then do the following. And likewise, you would have to modify the if statement for the left arrow key to include the a key.

So far we haven't defined any release handlers. But before we do that, let's make a really quick two-frame MovieClip called Arrow Key. I drew a really quick graphic and converted it to a MovieClip symbol, centering its registration point. I edited the inside of it, gave it two frames, and made the second frame's graphic a bit smaller. I dragged out two instances of it, placing one of them upside down using the transform tool. They represent the left and right arrow keys. I gave them instance names of leftKey and rightKey. When I press the left arrow key on the keyboard, the leftKey object will go to its own frame 2, and when I release the key, it will go back to frame 1 again. Likewise the right arrow key.

Next, let's just go ahead and rewrite the code and simplify it. We'll just supply the references to our press and release handlers right in the contructors this time:

var rightArrow:OneKeyManager = new OneKeyManager(stage, KeyList.RIGHT, rightArrow_onPress, rightArrow_onRelease);
var leftArrow:OneKeyManager = new OneKeyManager(stage, KeyList.LEFT, leftArrow_onPress, leftArrow_onRelease);
var dKey:OneKeyManager = new OneKeyManager(stage, KeyList.D, rightArrow_onPress, rightArrow_onRelease);
var aKey:OneKeyManager = new OneKeyManager(stage, KeyList.A, leftArrow_onPress, leftArrow_onRelease);

this.addEventListener(Event.ENTER_FRAME, loop);

function loop(event:Event):void {
	if(rightArrow.isDown || dKey.isDown) {
		player.x += 10;
	}
	if(leftArrow.isDown || aKey.isDown) {
		player.x -= 10;
	}
}
function rightArrow_onPress(event:KeyboardEvent):void {
	player.scaleX = 1;
	rightKey.gotoAndStop(2);
}
function leftArrow_onPress(event:KeyboardEvent):void {
	player.scaleX = -1;
	leftKey.gotoAndStop(2);
}
function rightArrow_onRelease(event:KeyboardEvent):void {
	rightKey.gotoAndStop(1);
}
function leftArrow_onRelease(event:KeyboardEvent):void {
	leftKey.gotoAndStop(1);
}

It should be clear what this code is doing. You can see that you have the option of setting the handlers in the constructor. OR you can use the setter method after the object is already constructed. Further, you could also do some fancy stuff like, for example, dynamically changing pressHandlers. Just set the pressHandler variable equal to a different function! This should be bringing to mind some cool possibilites! Anyway, here's how the code now behaves:

Note: You will have to click on the swf once to give it focus. Then try the left and right arrow keys. Also try the A and D keys. There is no code in place to keep the player from wandering off the stage. I'll leave that as an exercise for the reader.

One final note: When you test your movies in the Flash authoring tool, if you notice some keyboard keys not working, just pull down the Control menu of the flash player (the flash player's Control menu, not flash's Control menu) and select "Disable Keyboard Shortcuts." This will prevent the flash authoring tool from "stealing" the keystrokes you've programmed. For example, pressing the "B" key in the Flash authoring tool is the keyboard shortcut for selecting the "brush" tool. Flash's keyboard shortcuts are active even when your swf is running, so if you've programmed the "B" key to do something in your program, it won't work. You may not notice it, but by pressing the "B" key, all you are doing is selecting the brush tool in Flash. Checking off "disable keyboard shortcuts" fixes it, like I said. If you close the Flash authoring tool and open it again, you will have to select this option all over again--it doesn't stay selected between sessions.

I hope you like this utility and find it useful. Be sure to let me know if you do. Anyway, I intend to start writing more articles and sharing some of my Flash experiments here. These articles will probably be less like tutorials and more like just a description of the experiment, but I think you'll like them, as they will include a lot of "how to" type stuff.

Jody Hall, January 2012