import { System } from '@serpentity/serpentity'; import ControllableNode from '../nodes/controllable'; const internals = { gamepadState: { }, keyboardState: { } }; /** * Updates control status based on the controller map * * @extends {external:Serpentity.System} * @class ControlMapperSystem * @param {object} config a configuration object to extend. */ export default class ControlMapperSystem extends System { constructor(config = {}) { super(); /** * The node collection of controllable entities * * @property {external:Serpentity.NodeCollection} controllables * @instance * @memberof RenderSystem */ this.controllables = null; this._initializeKeyboard(); this._initializeGamepad(); } /** * Initializes system when added. Requests controllable nodes. * * @function added * @memberof RenderSystem * @instance * @param {external:Serpentity.Engine} engine the serpentity engine to * which we are getting added */ added(engine) { this.controllables = engine.getNodes(ControllableNode); } /** * Clears system resources when removed. * * @function removed * @instance * @memberof RenderSystem */ removed() { this.controllables = null; } /** * Runs on every update of the loop. Maps the actions given the current state of the inputs * * @function update * @instance * @param {Number} currentFrameDuration the duration of the current * frame * @memberof RenderSystem */ update(currentFrameDuration) { this._updateGamepads(); for (const controllable of this.controllables) { for (const map of controllable.controlMap.map) { if (map.source.type === 'keyboard') { this._setValue(controllable.entity, map.target, !!internals.keyboardState[map.source.index]); } if (map.source.type === 'gamepad') { const gamepad = internals.gamepadState[map.source.gamepadNumber]; if (gamepad) { const source = gamepad[map.source.gamepadInputSource]; source && this._setValue(controllable.entity, map.target, source[map.source.index]); } } } } } // Listens to keyboard to update internal map _initializeKeyboard() { window.addEventListener('keydown', (event) => { internals.keyboardState[event.keyCode] = true; }); window.addEventListener('keyup', (event) => { internals.keyboardState[event.keyCode] = false; }); } // Requests gamepad access and binds to the events _initializeGamepad() { window.addEventListener('gamepadconnected', (event) => { internals.gamepadState[event.gamepad.index] = event.gamepad; window.gamepad = event.gamepad; }); window.addEventListener('gamepaddisconnected', (event) => { delete internals.gamepadState[event.gamepad.index]; }); } // Update Gamepad _updateGamepads() { const gamepads = navigator.getGamepads(); for (const index of Object.keys(internals.gamepadState)) { internals.gamepadState[index] = gamepads[index]; } } // Sets the value to a target _setValue(entity, target, value) { const component = entity.getComponent(target.component); if (component) { const keyFragments = target.property.split('.'); let currentObject = component; for (const keyFragment of keyFragments.slice(0, keyFragments.length - 1)) { currentObject = currentObject[keyFragment] = currentObject[keyFragment] || {}; } const finalValue = !!target.value ? target.value(value) : value; const finalProperty = keyFragments.pop(); currentObject[finalProperty] += finalValue; } } }