evidently-input
Version:
TypeScript powered for easily handling input in your games.
148 lines • 6.89 kB
JavaScript
"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