← Will Donnelly

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;
    }
}