Делаем правильный обработчик нажатия клавиатуры

Для начала посмотрим на самый простой способ реализации взаимодействия с клавиатурой:

var key:Array = [];

stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
stage.addEventListener(Event.ENTER_FRAME, update);
        
function keyDownHandler(event:KeyboardEvent):void {
    key[event.keyCode] = true;
}

function keyUpHandler(event:KeyboardEvent):void {
    key[event.keyCode] = false;
}
       
function update(event:Event):void {
    if (key[Keyboard.LEFT]) {
        // Move the player to the left
        trace("walk left");
    }
    if (key[Keyboard.RIGHT]) {
        // Move the player to the right
        trace("walk right");
    }
}

Все прекрасно работает - у нас есть два слушателя событий клавиатуры, которые создают новый элемент массива key или обращаются к существующему элементу массива key по индексу кода нажатой или отпущенной клавиши, и передают ему значение в виде булевого TRUE/FALSE в зависимости от того, была нажата клавиша или отпущена.

Такой способ подходит для большинства случаев, но иногда нужно получить событие нажатой клавиши только один раз, в самом начале нажатия. К примеру мы делаем платформер и при нажатии клавиши прыжка персонаж должен прыгнуть один раз, а не прыгать постоянно, пока клавиша прыжка нажата. Или нам нужно при нажатии определенной клавиши показывать экран паузы и прятать его при повторном нажатии. Также можно вспомнить стандартное press space в экране пройденного уровня.

Для того чтобы реализовать подобный функционал нам нужно знать текущее и прошлое состояние нужной нам клавиши.

Сначала вынесем весь код по обработке событий клавиатуры в отдельный класс Input с глобальным доступом и реализуем старый функционал. Также мы поменяем массив на фиксированый вектор и добавим новый метод isDown для получения значения нажатой клавиши.

package {
    import flash.display.*;
    import flash.events.*;

    public class Input {
        private static var pressed:Vector. = new Vector.(256, true);
       
        public static function init(stage:Stage) {
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
            stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
        }
        
        private static function keyDownHandler(event:KeyboardEvent):void {
            pressed[event.keyCode] = true;
        }
       
        private static function keyUpHandler(event:KeyboardEvent):void {
            pressed[event.keyCode] = false;
        }

        public static function isDown(key:uint):Boolean {
            return pressed[key];
        }
    }
}

В самом начале Input нужно инициализировать с ссылкой на stage и затем его можно использовать в любом месте вашего приложения.

Input.init(stage);

stage.addEventListener(Event.ENTER_FRAME, update);
           
function update(event:Event):void {
    if (Input.isDown(Keyboard.LEFT)) {
        // Move the player to the left
        trace("walk left");
    }
    if (Input.isDown(Keyboard.RIGHT)) {
        // Move the player to the right
        trace("walk right");
    }
}

Теперь можно внедрить новый функционал. Нам понадобится еще один вектор и два новых метода. Метод isPressed будет проверять текущее и прошлое состояние нужной нам клавиши. Метод update будет записывать прошлое состояние нажатых клавиш в вектор released.

package {
    import flash.display.*;
    import flash.events.*;


    public class Input {
        private static var pressed:Vector. = new Vector.(256, true);
        private static var released:Vector. = new Vector.(256, true);
       
       
        public static function init(stage:Stage) {
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
            stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
        }
        
        private static function keyDownHandler(event:KeyboardEvent):void {
            pressed[event.keyCode] = true;
        }
       
        private static function keyUpHandler(event:KeyboardEvent):void {
            pressed[event.keyCode] = false;
        }

        public static function isDown(key:uint):Boolean {
            return pressed[key];
        }
       
        public static function isPressed(key:uint):Boolean {
            return released[key] && pressed[key];
        }
        
        public static function update():void {
            for (var i:int = 0; i < 256; i++) {
                released[i] = !pressed[i];
            }
        }
        
        public static function dispose():void {
            for (var i:int = 0; i < 256; i++) {
                pressed[i] = false;
            }
        }
    }
}

Теперь можно протестировать результат. Главное отличие это то что нужно не забыть использовать метод update класса Input, чтобы записать прошлое состояние нажатых клавиш.

Input.init(stage);

stage.addEventListener(Event.ENTER_FRAME, update);
           
function update(event:Event):void {
    if (Input.isPressed(Keyboard.UP)) {
        // Player jump
        trace("jump");
    }
    if (Input.isDown(Keyboard.LEFT)) {
        // Move the player to the left
        trace("walk left");
    }
    if (Input.isDown(Keyboard.RIGHT)) {
        // Move the player to the right
        trace("walk right");
    }
    Input.update();
}

Также, чтобы не импортировать flash.ui.Keyboard, можно добавить константы представляющие клавиши. Сами константы можно взять, например, из пакета net.flashpunk.utils.Key движка FlashPunk.