Spaces:
Running
Running
File size: 3,238 Bytes
a1c7c3f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | // Keyboard and touch input, translated into game intents.
const KEY_MAP = {
ArrowLeft: 'left',
KeyA: 'left',
ArrowRight: 'right',
KeyD: 'right',
ArrowUp: 'jump',
KeyW: 'jump',
Space: 'jump',
ShiftLeft: 'boost',
ShiftRight: 'boost',
KeyE: 'edge',
Enter: 'confirm',
NumpadEnter: 'confirm',
KeyP: 'pause',
Escape: 'pause',
KeyM: 'mute',
};
export class Input {
constructor(target = window) {
this.target = target;
this.pressed = new Set();
this.held = new Set();
this.muted = false;
this.onFirstGesture = null;
this.keydown = (e) => {
const action = KEY_MAP[e.code];
if (!action) return;
// Stop the page scrolling under the game.
if (e.code === 'Space' || e.code.startsWith('Arrow')) e.preventDefault();
if (!this.held.has(action)) this.pressed.add(action);
this.held.add(action);
this.#gesture();
};
this.keyup = (e) => {
const action = KEY_MAP[e.code];
if (action) this.held.delete(action);
};
this.blur = () => this.held.clear();
this.touchstart = (e) => {
const t = e.changedTouches[0];
this.touchOrigin = { x: t.clientX, y: t.clientY, time: performance.now() };
this.#gesture();
};
this.touchend = (e) => {
if (!this.touchOrigin) return;
const t = e.changedTouches[0];
const dx = t.clientX - this.touchOrigin.x;
const dy = t.clientY - this.touchOrigin.y;
const dt = performance.now() - this.touchOrigin.time;
const THRESHOLD = 34;
if (Math.abs(dx) < THRESHOLD && Math.abs(dy) < THRESHOLD) {
if (dt < 400) this.pressed.add('jump');
} else if (Math.abs(dx) > Math.abs(dy)) {
this.pressed.add(dx > 0 ? 'right' : 'left');
} else if (dy < 0) {
this.pressed.add('jump');
} else {
this.pressed.add('edge');
}
this.touchOrigin = null;
};
this.pointerdown = () => this.#gesture();
target.addEventListener('keydown', this.keydown);
target.addEventListener('keyup', this.keyup);
target.addEventListener('blur', this.blur);
target.addEventListener('touchstart', this.touchstart, { passive: true });
target.addEventListener('touchend', this.touchend, { passive: true });
target.addEventListener('pointerdown', this.pointerdown);
}
#gesture() {
if (!this.gestured) {
this.gestured = true;
this.onFirstGesture?.();
}
}
// True once per press.
consume(action) {
if (!this.pressed.has(action)) return false;
this.pressed.delete(action);
return true;
}
isHeld(action) {
return this.held.has(action);
}
// Drop queued presses that were not handled this frame so they cannot fire
// late, for example a jump buffered while the game over screen was up.
endFrame() {
this.pressed.clear();
}
destroy() {
this.target.removeEventListener('keydown', this.keydown);
this.target.removeEventListener('keyup', this.keyup);
this.target.removeEventListener('blur', this.blur);
this.target.removeEventListener('touchstart', this.touchstart);
this.target.removeEventListener('touchend', this.touchend);
this.target.removeEventListener('pointerdown', this.pointerdown);
}
}
|