Source: controls/Keys.js

import ScenePlugin from '../core/ScenePlugin.js';

const gamepadMapping = [
    'a', 'b', 'x', 'y',
    false, false, false, false,
    false, false, false, false,
    'up', 'down', 'left', 'right'
];

const gamepads = {
    free: [],
    occupied: []
};

/**
 * Unified keyboard and gamepad input for desktop games. Puts wasd, cursors and gamepad controls together in a single api.
 * All you need is to check the following properties of this plugin in your scenes update function: up, down, left, right, a, b, x and y.
 *
 * @extends module:core~ScenePlugin
 * @memberof module:controls~
 * @fires module:controls~Keys#keyup
 * @fires module:controls~Keys#keydown
 */
class Keys extends ScenePlugin {

    /**
     * @param {object} options - An options object. Can be passed down with the [game config]{@link module:core~VerfGame}.
     * @param {boolean} [options.wasd=true] - Wether or not to support wasd keys.
     * @param {boolean} [options.cursors=true] - Wether or not to support cursor or arrow keys.
     */
    constructor({
        wasd = true,
        cursors = true
    } = {}) {
        super();
        this.wasd = wasd;
        this.cursors = cursors;
    }

    /**
     * Adds the necessary keyboard and gamepad event listeners at the start of the scene.
     */
    init()
    {
        /**
         * Wether the up cursor, W or Z key or dpad up is currently down.
         * @type {boolean}
         */
        this.up = false; // w + z
        /**
         * Wether the left cursor, A or Q key or dpad left is currently down.
         * @type {boolean}
         */
        this.left = false; // a + q
        /**
         * Wether the down cursor, S key or dpad down is currently down.
         * @type {boolean}
         */
        this.down = false; // s
        /**
         * Wether the right cursor, D key or dpad right is currently down.
         * @type {boolean}
         */
        this.right = false; // d
        /**
         * Wether the space bar or gamepad A button is currently down.
         * @type {boolean}
         */
        this.a = false; // space
        /**
         * Wether the C or B key or gamepad B button is currently down.
         * @type {boolean}
         */
        this.b = false; // c + b
        /**
         * Wether the X key or gamepad X button is currently down.
         * @type {boolean}
         */
        this.x = false; // x
        /**
         * Wether the Y or R key or gamepad Y button is currently down.
         * @type {boolean}
         */
        this.y = false; // r + y

        this.keyDownHandler = this.onKeyDown.bind(this);
        window.addEventListener('keydown', this.keyDownHandler);

        this.keyUpHandler = this.onKeyUp.bind(this);
        window.addEventListener('keyup', this.keyUpHandler);

        this.gamepadConnectedHandler = this.onGamepadConnected.bind(this);
        window.addEventListener('gamepadconnected', this.gamepadConnectedHandler);

        this.gamepadDisconnectedHandler = this.onGamepadDisconnected.bind(this);
        window.addEventListener('gamepaddisconnected', this.gamepadDisconnectedHandler);

        this.gamepad = -1;

        if (gamepads.free.length) {
            this.gamepad = gamepads.free.shift();
            gamepads.occupied.push(this.gamepad);
        }

        /**
         * Indicates which gamepad buttons are down
         *
         * @type {boolean[]}
         */
        this.gamepadButtons = [];
        gamepadMapping.forEach(() => {
            this.gamepadButtons.push(false);
        });
    }

    /**
     * Called internally when a key goes down. Override this if you want to support more keys.
     *
     * @param {object} e - The native javascript event object.
     */
    onKeyDown(e)
    {
        if (this.gamepad >= 0) {
            return;
        }
        if (this.cursors) {
            switch (e.keyCode) {
                case 38: // up
                    this.up = true;
                    break;
                case 37: // left
                    this.left = true;
                    break;
                case 40: // down
                    this.down = true;
                    break;
                case 39: // right
                    this.right = true;
                    break;
            }
        }
        if (this.wasd) {
            switch (e.keyCode) {
                case 87: // w
                case 90: // z
                    this.up = true;
                    break;
                case 65: // a
                case 81: // q
                    this.left = true;
                    break;
                case 83: // s
                    this.down = true;
                    break;
                case 68: // d
                    this.right = true;
                    break;
            }
        }
        switch (e.keyCode) {
            case 32: // space
                this.a = true;
                break;
            case 67: // c
            case 66: // b
                this.b = true;
                break;
            case 88: // x
                this.x = true;
                break;
            case 89: // y
            case 82: // r
                this.y = true;
                break;
        }
        /**
         * Any key goes down. Passes the native KeyboardEvent.
         *
         * @event module:controls~Keys#keydown
         * @type {KeyboardEvent}
         */
        this.emit('keydown', e);
    }

    /**
     * Called internally when a key goes up. Override this if you want to support more keys.
     *
     * @param {object} e - The native javascript event object.
     */
    onKeyUp(e)
    {
        if (this.cursors) {
            switch (e.keyCode) {
                case 38: // up
                    this.up = false;
                    break;
                case 37: // left
                    this.left = false;
                    break;
                case 40: // down
                    this.down = false;
                    break;
                case 39: // right
                    this.right = false;
                    break;
            }
        }
        if (this.wasd) {
            switch (e.keyCode) {
                case 87: // w
                case 90: // z
                    this.up = false;
                    break;
                case 65: // a
                case 81: // q
                    this.left = false;
                    break;
                case 83: // s
                    this.down = false;
                    break;
                case 68: // d
                    this.right = false;
                    break;
            }
        }
        switch (e.keyCode) {
            case 32: // space
                this.a = false;
                break;
            case 67: // c
            case 66: // b
                this.b = false;
                break;
            case 88: // x
                this.x = false;
                break;
            case 89: // y
            case 82: // r
                this.y = false;
                break;
        }
        /**
         * Any key goes up. Passes the native KeyboardEvent.
         *
         * @event module:controls~Keys#keyup
         * @type {KeyboardEvent}
         */
        this.emit('keyup', e);
    }

    /**
     * Called internally when a gamepad is connected.
     *
     * @param {object} e - The native javascript event object.
     */
    onGamepadConnected(e)
    {
        if (this.gamepad >= 0) {
            return;
        }
        let gamepad = e.gamepad.index;
        if (gamepads.occupied.indexOf(gamepad) === -1) {
            this.gamepad = gamepad;
            gamepads.occupied.push(this.gamepad);
        }
    }

    /**
     * Called internally when a gamepad is disconnected.
     *
     * @param {object} e - The native javascript event object.
     */
    onGamepadDisconnected(e)
    {
        let gamepad = e.gamepad.index;
        if (gamepad == this.gamepad) {
            this.gamepad = -1;
        }
        let occupiedIndex = gamepads.occupied.indexOf(gamepad);
        if (occupiedIndex > -1) {
            gamepads.occupied.splice(occupiedIndex, 1);
        }
        let freeIndex = gamepads.free.indexOf(gamepad);
        if (freeIndex > -1) {
            gamepads.free.splice(freeIndex, 1);
        }
    }

    /**
     * Called internally to update the gamepad mappings. Override this if you want to support more buttons of the gamepad.
     *
     * @param {number} time - The total time (in milliesconds) since the start of the game.
     * @param {number} delta - The time elapsed (in milliseconds) since the last frame.
     */
    update(time, delta)
    {
        if (this.gamepad < 0) {
            return;
        }
        let navGamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []);
        let gamepad = navGamepads[this.gamepad];
        gamepad.buttons.forEach((button, index) => {
            if (gamepadMapping[index]) {
                this[gamepadMapping[index]] = button.pressed;
            }
            if (!this.gamepadButtons[index] && button.pressed) {
                /**
                 * Gamepad button goes down. Passes {id, name} of the button.
                 *
                 * @event module:controls~Keys#paddown
                 * @type {object}
                 */
                this.emit('paddown', {id: index, name: gamepadMapping[index]});
            }
            if (this.gamepadButtons[index] && !button.pressed) {
                /**
                 * Gamepad button goes up. Passes {id, name} of the button.
                 *
                 * @event module:controls~Keys#padup
                 * @type {object}
                 */
                this.emit('padup', {id: index, name: gamepadMapping[index]});
            }
            this.gamepadButtons[index] = button.pressed;
        });
    }

    /**
     * Removes all the keyboard and gamepad event listeners when the scene shuts down.
     */
    shutdown()
    {
        super.shutdown();

        window.removeEventListener('keydown', this.keyDownHandler);
        window.removeEventListener('keyup', this.keyUpHandler);

        window.removeEventListener('gamepadconnected', this.gamepadConnectedHandler);
        window.removeEventListener('gamepaddisconnected', this.gamepadDisconnectedHandler);

        let occupiedIndex = gamepads.occupied.indexOf(this.gamepad);
        if (occupiedIndex > -1) {
            gamepads.occupied.splice(occupiedIndex, 1);
            gamepads.free.push(this.gamepad);
        }
        this.gamepad = -1;

        this.up = false;
        this.left = false;
        this.down = false;
        this.right = false;
        this.a = false;
        this.b = false;
        this.x = false;
        this.y = false;
    }
}
export default Keys;