edge-runner / src /input.js
jedisct1's picture
Deploy Edge Runner
a1c7c3f verified
Raw History Blame Contribute Delete
3.24 kB
// 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);
}
}