UNPKG

@theatrejs/theatrejs

Version:

🎮 A JavaScript 2D Game Engine focused on creating pixel art games.

403 lines (298 loc) • 12.1 kB
import {EVENT_CODES, EVENT_TYPES, EventGamepadAnalog, EventGamepadDigital, EventGravityAnalog, EventGravityDigital, EventGyroscopeAnalog, EventGyroscopeDigital, EventPointerAnalog, EventPointerDigital, Stage, System} from '../index.js'; /** * Creates input systems. * * @example * * const system = new SystemInput({$container}); * system.initiate(); * system.tick(); */ class SystemInput extends System { /** * @typedef {object} TypeStateInput Statuses of an accepted input. * @property {number} $analog The analog value of the input. * @property {boolean} $initiate The initiated status of the input. * @property {boolean} $persist The persisted status of the input. * @property {boolean} $terminate The terminated status of the input. * @private * * @memberof SystemInput */ /** * Stores the container. * @type {HTMLElement} * @private */ $container; /** * Stores the input events. * @type {Array<Event>} * @private */ $events; /** * Stores the statuses of the accepted inputs. * @type {Map<string, TypeStateInput>} * @private */ $inputs; /** * Creates a new input system. * @param {object} $parameters The given parameters. * @param {HTMLElement} $parameters.$container The container on which to attach input events. */ constructor({$container}) { super(); this.$container = $container; } /** * Handles an input being initiated. * @param {string} $input The event code of the input. * @private */ $handleInputDown($input) { const initiate = this.$inputs.has($input) === false; /** * @type {TypeStateInput} */ const state = { $analog: 0, $initiate: initiate, $persist: true, $terminate: false }; this.$inputs.set($input, state); } /** * Handles an input being terminated. * @param {string} $input The event code of the input. * @private */ $handleInputUp($input) { /** * @type {TypeStateInput} */ const state = { $analog: 0, $initiate: false, $persist: false, $terminate: true }; this.$inputs.set($input, state); } /** * Sets the analog value of the given input. * @param {string} $input The event code of the input. * @param {number} $value The analog value of the input to set. * @private */ $setInputAnalog($input, $value) { this.$inputs.get($input).$analog = $value; } /** * Stacks the input events for the next tick. * @param {Event} $event The input event to stack. * @private */ $stack($event) { $event.preventDefault(); this.$events.push($event); } /** * Gets the persisted status of the given input. * @param {string} $input The event code of the input. * @returns {boolean} * @public */ getInput($input) { if (this.$inputs.has($input) === false) { return false; } return this.$inputs.get($input).$persist; } /** * Gets the analog value of the given input. * @param {string} $input The event code of the input. * @returns {number} * @public */ getInputAnalog($input) { if (this.$inputs.has($input) === false) { return 0; } return this.$inputs.get($input).$analog; } /** * Gets the initiated status of the given input. * @param {string} $input The event code of the input. * @returns {boolean} * @public */ getInputDown($input) { if (this.$inputs.has($input) === false) { return false; } return this.$inputs.get($input).$initiate; } /** * Gets the terminated status of the given input. * @param {string} $input The event code of the input. * @returns {boolean} * @public */ getInputUp($input) { if (this.$inputs.has($input) === false) { return false; } return this.$inputs.get($input).$terminate; } /** * Called when the system is being initiated. * @public */ onInitiate() { this.$events = []; this.$inputs = new Map(); window.addEventListener(EVENT_TYPES.NATIVE.BLUR, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.NATIVE.CONTEXT_MENU, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_ANALOG, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_CONNECT, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_DOWN, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_UP, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GRAVITY.GRAVITY_ANALOG, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GRAVITY.GRAVITY_DOWN, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GRAVITY.GRAVITY_UP, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_ANALOG, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_DOWN, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_UP, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.POINTER.POINTER_ANALOG, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.POINTER.POINTER_DOWN, this.$stack.bind(this)); window.addEventListener(EVENT_TYPES.POINTER.POINTER_UP, this.$stack.bind(this)); this.$container.addEventListener(EVENT_TYPES.KEYBOARD.KEY_DOWN, this.$stack.bind(this)); this.$container.addEventListener(EVENT_TYPES.KEYBOARD.KEY_UP, this.$stack.bind(this)); } /** * Called when the system is being terminated. * @returns {(undefined | Promise<void>)} * @public */ onTerminate() { window.removeEventListener(EVENT_TYPES.NATIVE.BLUR, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.NATIVE.CONTEXT_MENU, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_ANALOG, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_CONNECT, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_DOWN, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GAMEPAD.GAMEPAD_UP, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GRAVITY.GRAVITY_ANALOG, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GRAVITY.GRAVITY_DOWN, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GRAVITY.GRAVITY_UP, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_ANALOG, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_DOWN, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.GYROSCOPE.GYROSCOPE_UP, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.POINTER.POINTER_ANALOG, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.POINTER.POINTER_DOWN, this.$stack.bind(this)); window.removeEventListener(EVENT_TYPES.POINTER.POINTER_UP, this.$stack.bind(this)); this.$container.removeEventListener(EVENT_TYPES.KEYBOARD.KEY_DOWN, this.$stack.bind(this)); this.$container.removeEventListener(EVENT_TYPES.KEYBOARD.KEY_UP, this.$stack.bind(this)); return undefined; } /** * Called when the system is being updated by one tick update. * @param {object} $parameters The given parameters. * @param {Stage} $parameters.$stage The stage on which to execute the system. * @param {number} $parameters.$timetick The tick duration (in ms). * @public */ onTick({$stage, $timetick}) { void $stage; void $timetick; Array.from(this.$inputs.entries()).forEach(([$code, $state]) => { if ($state.$terminate === true) { this.$inputs.delete($code); return; } if ($code === EVENT_CODES.GAMEPAD_STANDARD.CONNECTED || $code === EVENT_CODES.GAMEPAD_STANDARD.DISCONNECTED) { this.$handleInputUp($code); return; } if ($state.$initiate === true) { $state.$initiate = false; return; } }); while (this.$events.length > 0) { const $event = this.$events.shift(); if ($event.type === EVENT_TYPES.NATIVE.BLUR) { Array.from(this.$inputs.keys()).forEach(($code) => { this.$handleInputUp($code); }); } else if ($event instanceof EventGamepadDigital && $event.type === EVENT_TYPES.GAMEPAD.GAMEPAD_CONNECT) { this.$handleInputDown($event.code); } else if ($event instanceof EventGamepadDigital && $event.type === EVENT_TYPES.GAMEPAD.GAMEPAD_DOWN) { this.$handleInputDown($event.code); } else if ($event instanceof EventGamepadDigital && $event.type === EVENT_TYPES.GAMEPAD.GAMEPAD_UP) { this.$handleInputUp($event.code); } else if ($event instanceof EventGamepadAnalog && $event.type === EVENT_TYPES.GAMEPAD.GAMEPAD_ANALOG) { this.$setInputAnalog($event.code, $event.value); } else if ($event instanceof EventGravityDigital && $event.type === EVENT_TYPES.GRAVITY.GRAVITY_DOWN) { this.$handleInputDown($event.code); } else if ($event instanceof EventGravityDigital && $event.type === EVENT_TYPES.GRAVITY.GRAVITY_UP) { this.$handleInputUp($event.code); } else if ($event instanceof EventGravityAnalog && $event.type === EVENT_TYPES.GRAVITY.GRAVITY_ANALOG) { this.$setInputAnalog($event.code, $event.value); } else if ($event instanceof EventGyroscopeDigital && $event.type === EVENT_TYPES.GYROSCOPE.GYROSCOPE_DOWN) { this.$handleInputDown($event.code); } else if ($event instanceof EventGyroscopeDigital && $event.type === EVENT_TYPES.GYROSCOPE.GYROSCOPE_UP) { this.$handleInputUp($event.code); } else if ($event instanceof EventGyroscopeAnalog && $event.type === EVENT_TYPES.GYROSCOPE.GYROSCOPE_ANALOG) { this.$setInputAnalog($event.code, $event.value); } else if ($event instanceof KeyboardEvent && $event.type === EVENT_TYPES.KEYBOARD.KEY_DOWN) { this.$handleInputDown($event.code); } else if ($event instanceof KeyboardEvent && $event.type === EVENT_TYPES.KEYBOARD.KEY_UP) { this.$handleInputUp($event.code); } else if ($event instanceof EventPointerDigital && $event.type === EVENT_TYPES.POINTER.POINTER_DOWN) { this.$handleInputDown($event.code); } else if ($event instanceof EventPointerDigital && $event.type === EVENT_TYPES.POINTER.POINTER_UP) { this.$handleInputUp($event.code); } else if ($event instanceof EventPointerAnalog && $event.type === EVENT_TYPES.POINTER.POINTER_ANALOG) { this.$setInputAnalog($event.code, $event.value); } } } } export { SystemInput }; export default SystemInput;