type state = 'down' | 'up'; // - TODO - Pull this from the actual game creating the controls const GameControls = { down: 'down', up: 'up', left: 'left', right: 'right', shift: 'shift', ' ': ' ', } as const; /** * Abstraction for game controls, the game should say what controls it expects and then we can remap the actual controls separately. * i.e. Both "ArrowDown" and "Down" from the keyboard listener should be the games "down" event. * Also handles when the keys are pressed or not. */ class Control { readonly gameControls = GameControls; private _controlMap: { [key: string]: keyof typeof GameControls } = { ArrowDown: GameControls.down, Down: GameControls.down, ArrowUp: GameControls.up, Up: GameControls.up, ArrowLeft: GameControls.left, Left: GameControls.left, ArrowRight: GameControls.right, Right: GameControls.right, Shift: GameControls.shift, ' ': GameControls[' '], }; private _controlState: Map = new Map(); constructor() { this._setKeys(); window.addEventListener('keydown', (event) => { if (this._controlState.get(this._controlMap[event.key])?.state !== 'down') { this._controlState.set(this._controlMap[event.key], { state: 'down', consumed: false }); } }); window.addEventListener('keyup', (event) => { if (this._controlState.get(this._controlMap[event.key])?.state !== 'up') { this._controlState.set(this._controlMap[event.key], { state: 'up', consumed: false }); } }); } /** * - TODO */ private _setKeys(): void { Object.keys(GameControls).forEach((key) => { this._controlState.set(key as keyof typeof GameControls, { state: 'up', consumed: false }); }); } /** * - TODO * * @param key - TODO */ isDown(key: keyof typeof GameControls): boolean { return this._controlState.get(key)?.state === 'down'; } /** * Consume a down key event once * * @param key - TODO */ onceDown(key: keyof typeof GameControls): boolean { const keyState = this._controlState.get(key); if (keyState && keyState.state === 'down' && !keyState.consumed) { keyState.consumed = true; return true; } return false; } } export default Control;