Wednesday, October 24, 2012

Creating EasyKeyboard class: Part 6

In this tutorial we'll add the ability to listen to combinations of keys.

Firstly, create a new class called ComboListener. This will be used to listen to multiple keys at once - so the public properties are 2 arrays and a function - keyCodes, flags, handler. The keyCodes array will be filled with key codes of the keys to listen. The flags array will have as many references as keyCodes array has, and will consist of true/false booleans:

package com.kircode.EasyKeyboard 
{
/**
 * ...
 * @author Kirill Poletaev
 */
public class ComboListener 
{

public var keyCodes:Array;
public var handler:Function;
public var flags:Array;

public function ComboListener(codes:Array, func:Function, booleans:Array) 
{
keyCodes = codes;
handler = func;
flags = booleans;
}

}

}

Now go to EasyKeyboard class and add a new method called addComboListener, which has 2 parameters - keyCodes and handler. The keyCodes is an array of numbers, which will be applied to ComboListener object directly. The handler is applied as it is too. The flags array is generated based on the number of elements in the keyCodes array, default value for each element is false.

/**
 * Add event listener for a combination of keys using key codes.
 * @paramkeyCodes Array of key codes for the combination.
 * @paramhandler Function to execute when the combination is held.
 */

public function addComboListener(keyCodes:Array, handler:Function):void {
var flags:Array = [];
for (var i:int = 0; i < keyCodes.length; i++) {
flags.push(false);
}
listeners.push(new ComboListener(keyCodes, handler, flags));
}

Now go to kDown function and update each if...statement so that the condition that checks if current element of the listeners array is a class of KeyListener or HoldListener stands first. In the beginning of the function, add 2 variables - u and comboKeys, both integers and set to 0 by default.

Add third if...statement in the for...loop in this function, check if the listener is an instance of ComboListener class.

If so, set comboKeys to 0 and loop through all keyCodes elements of the current listener. Here we do 2 things - check if the pressed keyCode equals the current keyCode of the listener - set the corresponding element in flags to true; and check if the flag that's corresponding to this key is set to true - if so, increase comboKeys by 1.

After the loop, check if the comboKeys value equals length of keyCodes. Call the handler if so.

private function kDown(evt:KeyboardEvent):void {
var u:int = 0;
var comboKeys:int = 0;
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i] is KeyListener && evt.keyCode == listeners[i].keyCode && evt.altKey == listeners[i].alt && evt.ctrlKey == listeners[i].ctrl && evt.shiftKey == listeners[i].shift) {
if (listeners[i].handlerD) listeners[i].handlerD.call();
}
if (listeners[i] is HoldListener && evt.keyCode == listeners[i].keyCode) {
listeners[i].flag = true;
}
if (listeners[i] is ComboListener) {
comboKeys = 0;
for (u = 0; u < listeners[i].keyCodes.length; u++) {
if (listeners[i].keyCodes[u] == evt.keyCode) {
listeners[i].flags[u] = true;
}
if (listeners[i].flags[u] == true) comboKeys++;
}
if (comboKeys == listeners[i].keyCodes.length) {
if (listeners[i].handler) listeners[i].handler.call();
}
}
}
}

Basically, we set the needed key flag to true, and at the same time check if all the keys in the listener are pressed. We do that by calculating how many flags are set to "true", and if that number equals the number of all elements in the keyCodes array of that listener, that means all keys are currently pressed.

Now, we can add the Up event listener for these keys the same way. It's even simpler, because we don't need to count comboKeys here - just set the needed flag to false and that's it.

private function kUp(evt:KeyboardEvent):void {
var u:int = 0;
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i] is KeyListener && evt.keyCode == listeners[i].keyCode && evt.altKey == listeners[i].alt && evt.ctrlKey == listeners[i].ctrl && evt.shiftKey == listeners[i].shift) {
if (listeners[i].handlerU) listeners[i].handlerU.call();
}
if (listeners[i] is HoldListener && evt.keyCode == listeners[i].keyCode) {
listeners[i].flag = false;
}
if (listeners[i] is ComboListener) {
for (u = 0; u < listeners[i].keyCodes.length; u++) {
if (listeners[i].keyCodes[u] == evt.keyCode) {
listeners[i].flags[u] = false;
}
}
}
}
}

One more thing we'll update our code with today is the line that calls handler in frame() function - add an if...statement, which checks if the handler is not null before calling it:

private function frame(evt:Event):void {
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i] is HoldListener && listeners[i].flag) {
if (listeners[i].handler != null) listeners[i].handler.call();
}
}
}

Full EasyKeyboard.as code:

package com.kircode.EasyKeyboard 
{
import flash.display.Stage;
import flash.events.Event;
import flash.events.KeyboardEvent;

/**
 * Utility for easy keyboard listener management.
 * @author Kirill Poletaev
 */
public class EasyKeyboard 
{
public var listeners:Array = [];
private var keyLabels:Array = ["0","1","2","3","4","5","6","7","Backspace","Tab","10","11","Center","Enter","14","15","Shift","Control","Alt","Pause","Caps Lock","21","22","23","24","25","26","27","28","29","30","31","Space","Page Up","Page Down","End","Home","Left","Up","Right","Down","41","42","43","44","Insert","Delete","47","0","1","2","3","4","5","6","7","8","9","58","59","60","61","62","63","64","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","Windows","Windows","Menu","94","95","Num 0","Num 1","Num 2","Num 3","Num 4","Num 5","Num 6","Num 7","Num 8","Num 9","Num *","Num +","108","Num -","Num .","Num /","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","124","125","126","127","128","129","130","131","132","133","134","135","136","137","138","139","140","141","142","143","Num Lock","Scroll Lock","146","147","148","149","150","151","152","153","154","155","156","157","158","159","160","161","162","163","164","165","166","167","168","169","170","171","172","173","174","175","176","177","178","179","180","181","182","183","184","185",";","+",",","-",".","/","~","193","194","195","196","197","198","199","200","201","202","203","204","205","206","207","208","209","210","211","212","213","214","215","216","217","218","[","\\","]","'","223","224","225","226","227","228","229","230","231","232","233","234","235","236","237","238","239","240","241","242","243","244","245","246","247","248","249","250","251","252","253","254","255"];
private var st:Stage;

public function EasyKeyboard(stage:Stage) 
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, kDown);
stage.addEventListener(KeyboardEvent.KEY_UP, kUp);
stage.addEventListener(Event.ENTER_FRAME, frame);
st = stage;
}

private function frame(evt:Event):void {
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i] is HoldListener && listeners[i].flag) {
if (listeners[i].handler != null) listeners[i].handler.call();
}
}
}

/**
 * Call this before nullifying the class instance to remove all the listeners.
 */

public function kill():void {
st.removeEventListener(KeyboardEvent.KEY_DOWN, kDown);
st.removeEventListener(KeyboardEvent.KEY_UP, kUp);
st.addEventListener(Event.ENTER_FRAME, frame);
}

/**
 * Add event listener for a single key using a keycode.
 * @paramkeyCode Key code of the key.
 * @paramhandlerDown Function to be called when the key is pressed down.
 * @paramhandlerUp Function to be called when the key is released.
 * @paramalt Used in combination with the alt key.
 * @paramctrl Used in combination with the ctrl key.
 * @paramshift Used in combination with the shift key.
 */

public function addListener(keyCode:int, handlerDown:Function = null, handlerUp:Function = null, alt:Boolean = false, ctrl:Boolean = false, shift:Boolean = false):void {
listeners.push(new KeyListener(keyCode, handlerDown, handlerUp, alt, ctrl, shift));
}

/**
 * Add event listener for a single key using a keycode.
 * @paramkeyCode Key code of the key.
 * @paramhandler Function to execute while the key is held every frame.
 */

public function addHoldListener(keyCode:int, handler:Function):void {
listeners.push(new HoldListener(keyCode, handler, false));
}


/**
 * Add event listener for a combination of keys using key codes.
 * @paramkeyCodes Array of key codes for the combination.
 * @paramhandler Function to execute when the combination is held.
 */

public function addComboListener(keyCodes:Array, handler:Function):void {
var flags:Array = [];
for (var i:int = 0; i < keyCodes.length; i++) {
flags.push(false);
}
listeners.push(new ComboListener(keyCodes, handler, flags));
}

/**
 * Add event listener for a single key using key string value.
 * @paramkeyName String name of the key.
 * @paramhandlerDown Function to be called when the key is pressed down.
 * @paramhandlerUp Function to be called when the key is released.
 * @paramalt Used in combination with the alt key.
 * @paramctrl Used in combination with the ctrl key.
 * @paramshift Used in combination with the shift key.
 */

public function addEasyListener(keyName:String, handlerDown:Function = null, handlerUp:Function = null, alt:Boolean = false, ctrl:Boolean = false, shift:Boolean = false):void {
var code:int = -1;
for (var i:int = 0; i < keyLabels.length; i++) {
if (keyLabels[i] == keyName) {
code = i;
break;
}
}
if (code == -1) {
throw new Error('Incorrect key string value specified - no "' + keyName + '" key found.');
return;
}
addListener(code, handlerDown, handlerUp, alt, ctrl, shift);
}

/**
 * Add hold listener for a single key using key string value.
 * @paramkeyName String name of the key.
 * @paramhandler Function to execute while the key is held every frame.
 */

public function addEasyHoldListener(keyName:String, handler:Function):void {
var code:int = -1;
for (var i:int = 0; i < keyLabels.length; i++) {
if (keyLabels[i] == keyName) {
code = i;
break;
}
}
if (code == -1) {
throw new Error('Incorrect key string value specified - no "' + keyName + '" key found.');
return;
}
addHoldListener(code, handler);
}

private function kDown(evt:KeyboardEvent):void {
var u:int = 0;
var comboKeys:int = 0;
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i] is KeyListener && evt.keyCode == listeners[i].keyCode && evt.altKey == listeners[i].alt && evt.ctrlKey == listeners[i].ctrl && evt.shiftKey == listeners[i].shift) {
if (listeners[i].handlerD) listeners[i].handlerD.call();
}
if (listeners[i] is HoldListener && evt.keyCode == listeners[i].keyCode) {
listeners[i].flag = true;
}
if (listeners[i] is ComboListener) {
comboKeys = 0;
for (u = 0; u < listeners[i].keyCodes.length; u++) {
if (listeners[i].keyCodes[u] == evt.keyCode) {
listeners[i].flags[u] = true;
}
if (listeners[i].flags[u] == true) comboKeys++;
}
if (comboKeys == listeners[i].keyCodes.length) {
if (listeners[i].handler) listeners[i].handler.call();
}
}
}
}

private function kUp(evt:KeyboardEvent):void {
var u:int = 0;
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i] is KeyListener && evt.keyCode == listeners[i].keyCode && evt.altKey == listeners[i].alt && evt.ctrlKey == listeners[i].ctrl && evt.shiftKey == listeners[i].shift) {
if (listeners[i].handlerU) listeners[i].handlerU.call();
}
if (listeners[i] is HoldListener && evt.keyCode == listeners[i].keyCode) {
listeners[i].flag = false;
}
if (listeners[i] is ComboListener) {
for (u = 0; u < listeners[i].keyCodes.length; u++) {
if (listeners[i].keyCodes[u] == evt.keyCode) {
listeners[i].flags[u] = false;
}
}
}
}
}

}

}

Now you can test the feature by going to main.as and calling the addComboListener function like this:

keyboard = new EasyKeyboard(stage);
keyboard.addComboListener([65, 83], function() { trace("A+S combo is being held!"); } );

Now if you press the A and S keys at the same time, the handler will be executed. Works just the same way as you would listen to a single key.

Thanks for reading!

No comments:

Post a Comment