UNPKG

evidently-input

Version:

TypeScript powered for easily handling input in your games.

148 lines 6.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * Handles the keyboard events and provides an API for interacting with it easily. The simplest way to use it is: * * 1. Create a new instance of `KeyboardInput`. * 2. Call `registerListeners()` passing the `document`. * 3. Call `update` **at the end** of every update frame of your game. * 4. Use the functions of this library in your code. * * ### How does it work? * * You need to call the `update()` method at the **end** of every frame to properly transition internal state. * The workflow for this class is as follows: * 1. Browser receives events from the user and fires the corresponding events. * 2. `KeyboardInput` receives those events and updates its internal state. * 3. Update loop for your game runs and you can use the methods in this class to interact. * 4. At the end of the your game's update loop `update()` is called on this class. (or at any point past when you know you'll interact with this class) * 5. Repeat * * ### Understanding key states * * There are three states recognized by this class for a key: down, pressed and released: * * * Use `pressed` if you want to know if a key was pressed at least once this frame. * * Use `released` if you want to knof iw a key was releast at least once this frame. * * Use `down` if you want to know if a key was either pressed this frame or pressed in any previous frame and not released so far. * * * You may notice that in a situation where during the course of a single frame a key is both pressed and released, all three states * will report true. That's an intentional design decision to avoid a very uncommon but still possible bug. Imagine this code: * * ``` * if (keyboardInput.isKeyDown("a")) { * this.move({x: -1}); * } * ``` * * If for some reason your game runs slow (maybe it naturally has low FPS or it's lagging at the moment), if you pressed and released the `a` key * during the span of a single frame as a player you'd still expect the move to trigger - sure, you released the key but you also pressed it. The * code can't use `isKeyPressed` instead, because you want the player to keep moving as long as the key is down. You could work around this by * expanding the condition to `i.isKeyDown || i.isKeyPressed`, but the issues are: * * 1. You probably didn't think about this bug in the first place. Which is fine, it's extremely unlikely to ever be an issue for the vast majority * of games. * 2. The expanded condition is less readable. * 3. The change on its own doesn't break anything, so you lose nothing by having it baked in the system. */ class KeyboardInput { constructor() { this.isKeyDown = (key) => this._keysDown.indexOf(key) !== -1 || this._keysPressed.indexOf(key) !== -1; this.isKeyPressed = (key) => this._keysPressed.indexOf(key) !== -1; this.isKeyReleased = (key) => this._keysReleased.indexOf(key) !== -1; /** * Updates the internal state, should be called at the end of each frame. */ this.update = () => { this._wasAltDownThisFrame = false; this._wasCtrlDownThisFrame = false; this._wasShiftDownThisFrame = false; this._keysReleased = []; this._keysPressed = []; }; /** * Resets the internal state of this class to what it is during creation (nothing pressed, released or held down) */ this.flush = () => { this._keysPressed.length = 0; this._keysReleased.length = 0; this._keysDown.length = 0; this._lastKeyPressed = ''; this._wasAltDownThisFrame = false; this._wasShiftDownThisFrame = false; this._wasCtrlDownThisFrame = false; this._isAltDown = false; this._isShiftDown = false; this._isCtrlDown = false; }; /** * Handles a keyboard event updating the internal status. This will be called automatically if you register the listeners * via `registerListeners`. If you have listeners being registered somewhere else entirely and would like to keep it * that way, you can manually call this method passing the KeyboardEvent. * * @warning This method will call `preventDefault` on the event passed. * * @param {KeyboardEvent} event */ this.handleEvent = (event) => { this._wasAltDownThisFrame = this._wasAltDownThisFrame || event.altKey; this._wasCtrlDownThisFrame = this._wasCtrlDownThisFrame || event.ctrlKey; this._wasShiftDownThisFrame = this._wasShiftDownThisFrame || event.shiftKey; this._isAltDown = event.altKey; this._isCtrlDown = event.ctrlKey; this._isShiftDown = event.shiftKey; if (event.type === 'keydown') { this._lastKeyPressed = event.key; this._keysPressed.push(event.key); this._keysDown.push(event.key); this._lastKeyPressed = event.key; } else if (event.type === 'keyup') { this._keysReleased.push(event.key); this._keysDown = this._keysDown.filter(key => key !== event.key); } event.preventDefault(); }; this._isCtrlDown = false; this._isAltDown = false; this._isShiftDown = false; this._keysReleased = []; this._keysPressed = []; this._keysDown = []; this._lastKeyPressed = ""; } get isCtrlDown() { return this._wasCtrlDownThisFrame || this._isCtrlDown; } get isShiftDown() { return this._wasShiftDownThisFrame || this._isShiftDown; } get isAltDown() { return this._wasAltDownThisFrame || this._isAltDown; } get isAnyKeyDown() { return this._keysDown.length > 0 || this._keysPressed.length > 0; } get isAnyKeyPressed() { return this._keysPressed.length > 0; } get isAnyKeyReleased() { return this._keysReleased.length > 0; } get lastKeyPressed() { return this._lastKeyPressed; } /** * Registers the `keydown` and `keyup` listeners on the passed element so that the class * can handle the input. * * @param {GlobalEventHandlers} element To avoid issues with focus it's best to pass `document` here */ registerListeners(element) { element.addEventListener("keydown", this.handleEvent); element.addEventListener("keyup", this.handleEvent); } } exports.KeyboardInput = KeyboardInput; //# sourceMappingURL=KeyboardInput.js.map