package com.actiontad.gameUtils
{
import flash.display.DisplayObject;
import flash.events.EventDispatcher;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.events.Event;
import flash.ui.Keyboard;
import flash.utils.Timer;
import flash.utils.getTimer;
/**
* The ComboKeys class simplifies handling keyboard input.
* And allows you to also capture combinations of keys.
*
*
* Usage:
*
* var key:ComboKeys = new ComboKeys(stage);
* if (key.pressed(37)) { ... }
*
* if (key.combo([38,38,40,40])) { ... }
* Note: This class does not extend the Proxy Class and does not automatically understand the consts of the Keyboard Class.
* To use those constants instead of numbers (in your class housing the ComboKeys class), one must import the Keyboard class and use it for the consts.
*
*/
public class ComboKeys extends Object
{
public var mySelf:DisplayObject;
//when do combos register, on key 'up' or 'down'
public var combosWhen:String = "up";
public static var comboDelay:Number = 2400; //in epox time
private static var keysInSuccession:Array = [];
private static var theStage:EventDispatcher;
private static var currentKeysPressed:Object;
private static var comboTimer:Number = 0;
/**
* Each const such as this one for 'down' is also in the com.actiontad.basicGameEvents.GameObjectEvents class in captialized standard form.
* They exsist in this class only so that this class does not have to be linked with basicGameObejcts.
* If using basicGameObjects, the consts found in com.actiontad.basicGameEvents.GameObjectEvents (such as GameObjectEvents.DOWN)
* are what should be used outside of this class.
*/
public static const dwn:String = "down";
public static const upstr:String = "up";
public static const keystr:String = "key";
public static const skwu:String = "someKeyWentUp";
/**
*
* String based key (up) named events are dispatched on mySelf.
* So in mySelf you could do, for example: addEventListener(GameObjectEvents.KEY + 32 + GameObjectEvents.DOWN, respondToSpaceUp);
* Also a general key up event is dispatched, when any key goes up, it's called - "someKeyWentUp" or GameObjectEvents.ANY_KEY_UP
* This is a way you could set up a key down event dispatch for when multiple keys are down:
*
* var keysToRespondTo:Array = [32,37,38,39,40];
* for (var kr:int = 0; kr < _keysToRespondTo.length; kr++) {
* var keyis:int= _keysToRespondTo[kr];
* if (key.pressed(keyis)) {
* dispatchEvent(new Event(GameObjectEvents.KEY + keyis + GameObjectEvents.DOWN));
* }
* }
*
* That would need to happen onEnterFrame or some loop in mySelf.
* @param theStageToBe The EventDispatcher that will listen for the KeyboardEvents, normally the stage.
* @param thiser The DisplayObject directly effected by key presses, normally the main character,
* this is the DisplayObject on which you would like to be able to add 'key32down' or 'key32up' style event listeners.
* @param comboD The delay of combos in milliseconds, must press combination faster than this number to register.
* @param comboResponseWhen When do combos register, on key "up" or key "down", "up" or "down" are the only valid values.
* If "up" the keys must each be released first before they get added as part of a successful combo chain.
* If "down" the keys can be pressed in combination, piano style, (note, that means continuously, so use with caution)
* The default is "up".
*/
public function ComboKeys(theStageToBe:EventDispatcher, thiser:DisplayObject = null, comboD:Number = 2400, comboResponseWhen:String = "up") {
ComboKeys.comboDelay = comboD;
mySelf = thiser;
combosWhen = comboResponseWhen;
initialize(theStageToBe);
}
protected function initialize(s:EventDispatcher):void {
ComboKeys.theStage = s;
currentKeysPressed = new Object();
configureKeyListeners();
}
/**
* You may want to call this function after you have called killAllKeyEvents, to re-initialize the class.
* It is an advanced option.
*/
public function configureKeyListeners():void {
theStage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
theStage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
}
public function pressed(keyCodeValue:uint):Boolean {
return Boolean(keyCodeValue in currentKeysPressed);
}
/**
*
* @param nullTheStageRef Also nulls out theStage reference if you pass true here. Default is false.
*/
public function killAllKeyEvents(nullTheStageRef:Boolean = false):void {
theStage.removeEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
theStage.removeEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
currentKeysPressed = new Object();
if (nullTheStageRef == true) ComboKeys.theStage = null;
}
protected function keyDownHandler(e:KeyboardEvent):void {
var theKeyDown:int = e.keyCode;
currentKeysPressed[theKeyDown] = true;
if (combosWhen == dwn) {
var moment:uint = getTimer();
if (comboTimer == 0) { comboTimer = moment; }
if (moment < comboTimer + comboDelay) {
keysInSuccession[keysInSuccession.length] = theKeyDown;
} else {
comboTimer = moment;
keysInSuccession = [theKeyDown];
}
}
//if (mySelf) mySelf.dispatchEvent(new Event(GameObjectEvents.KEY+theKeyDown+GameObjectEvents.DOWN));
//the problem with that is that it will not happen continually
//you have to use a loop to continually check if a key is still down.
//you can listen for this event and switch a boolean on key up,
//but still you would have to use a loop to determine the boolean state for continual key presses.
//A good thing to do is handle such a loop in mySelf as described in the ComboKeys constructor.
//IKeyControlledObject implementors in the basicGameObjects package handle the loop, and also the creation of events,
//in an effort to reduce 'new' calls for key down handling.
//That is why the above is commented out of this class.
//You can uncomment it if you like, it will cause a conflict with those classes if you do however.
}
protected function keyUpHandler(e:KeyboardEvent):void {
var theKeyUp:int = e.keyCode;
delete currentKeysPressed[theKeyUp];
if (combosWhen == upstr) {
var moment:uint = getTimer();
if (comboTimer == 0) { comboTimer = moment; }
if (moment < comboTimer + comboDelay) {
keysInSuccession[keysInSuccession.length] = theKeyUp;
} else {
comboTimer = moment;
keysInSuccession = [theKeyUp];
}
}
if (mySelf) {
//note: The following event strings are also from the com.actiontad.basicGameEvents.GameObjectEvents class
//That class has been left out of this class, so that this class does not have to be used
//with basicGameObejcts.
mySelf.dispatchEvent(new Event(keystr+theKeyUp+upstr));
mySelf.dispatchEvent(new Event(skwu));
}
}
public function combo(checkFor:Array):Boolean {
var status:Boolean = false;
var len:int = checkFor.length-1;
var cmb:int = 0; var i:int; var lastKeyPressed:Number;
if (keysInSuccession.length >= checkFor.length) {
lastKeyPressed = keysInSuccession.length-1;
for (i = len; i > -1; i--) {
if ( keysInSuccession[lastKeyPressed] == checkFor[len] ) {
if (keysInSuccession[lastKeyPressed-i] &&
keysInSuccession[lastKeyPressed-i] == checkFor[len-i]) cmb += 1;
}
}
if (cmb == checkFor.length) {
comboTimer = getTimer();
status = true;ComboKeys.keysInSuccession = [];
}
}
return status;
}
}
}