This isn't especially clever or tricky code, it's just a bundle of logic that I've found useful in past tinkering and generally doesn't need a lot of customization.
The idea is that this class handles the whole "lock mouse pointer on click"
operation and registers/unregisters event listeners to give an HTML5 canvas
game a nice "click to enter" sort of experience. The getInputFrame
method is then called once per game update tick to get new keyboard/mouse
inputs to respond to.
/** Manages the HTML5 PointerLock API in response to interaction with a
* particular DOM element (usually a canvas) and collects user input for
* processing by game logic.
*/
class InputRelay {
constructor(domElement) {
this.domElement = domElement;
this.pointerLocked = false;
this.keysDown = {};
this.prevKeys = {};
this.mouseX = 0;
this.mouseY = 0;
this.wheelY = 0;
this.handleInput = this.handleInput.bind(this);
this.domElement.addEventListener('click', this.handleInput);
document.addEventListener('pointerlockchange', this.handleInput);
}
handleInput(evt) {
if (evt.type == 'click' && !this.pointerLocked) {
this.domElement.requestPointerLock();
}
if (evt.type == 'pointerlockchange') {
const prevLocked = this.pointerLocked;
this.pointerLocked = (document.pointerLockElement === this.domElement);
if (this.pointerLocked && !prevLocked) {
document.addEventListener('mousemove', this.handleInput);
document.addEventListener('mousedown', this.handleInput);
document.addEventListener('mouseup', this.handleInput);
document.addEventListener('keydown', this.handleInput);
document.addEventListener('keyup', this.handleInput);
document.addEventListener('wheel', this.handleInput);
}
if (!this.pointerLocked && prevLocked) {
document.removeEventListener('mousemove', this.handleInput);
document.removeEventListener('mousedown', this.handleInput);
document.removeEventListener('mouseup', this.handleInput);
document.removeEventListener('keydown', this.handleInput);
document.removeEventListener('keyup', this.handleInput);
document.removeEventListener('wheel', this.handleInput);
}
}
if (evt.type == 'mousemove') {
this.mouseX += evt.movementX;
this.mouseY += evt.movementY;
}
if (evt.type == 'mousedown' || evt.type == 'mouseup') {
this.keysDown['Mouse' + evt.which] = (evt.type == 'mousedown');
}
if (evt.type == 'keydown' || evt.type == 'keyup') {
this.keysDown[evt.code] = (evt.type == 'keydown');
}
if (evt.type == 'wheel') {
this.wheelY += evt.deltaY;
}
}
getInputFrame() {
const input = {
pointerLocked: this.pointerLocked,
mouseX: this.mouseX,
mouseY: this.mouseY,
wheelY: this.wheelY,
keysDown: {},
prevKeys: {},
};
for (const key in this.prevKeys) {
input.prevKeys[key] = this.prevKeys[key];
}
for (const key in this.keysDown) {
input.keysDown[key] = this.keysDown[key];
this.prevKeys[key] = this.keysDown[key];
}
this.mouseX = this.mouseY = 0;
this.wheelY = 0;
return input;
}
}