/* ========================================================= input.js — ввод: клавиатура + сенсорные кнопки. Нормализует всё до трёх сигналов: jump (нажатие), slideHeld (удержание), pause (нажатие). ========================================================= */ 'use strict'; class InputManager { constructor() { // Мгновенные события (сбрасываются после чтения игрой) this.jumpPressed = false; this.pausePressed = false; // Состояние удержания this.slideHeld = false; this._callbacks = { jump: [], pause: [], slideStart: [], slideEnd: [] }; this._bindKeyboard(); this._bindTouch(); } on(event, cb) { if (this._callbacks[event]) this._callbacks[event].push(cb); } _emit(event) { (this._callbacks[event] || []).forEach(cb => cb()); } /* -------- Клавиатура -------- */ _bindKeyboard() { window.addEventListener('keydown', (e) => { switch (e.code) { case 'ArrowUp': case 'Space': case 'KeyW': if (!e.repeat) { this.jumpPressed = true; this._emit('jump'); } e.preventDefault(); break; case 'ArrowDown': case 'KeyS': if (!this.slideHeld) { this.slideHeld = true; this._emit('slideStart'); } e.preventDefault(); break; case 'KeyP': case 'Escape': if (!e.repeat) { this.pausePressed = true; this._emit('pause'); } e.preventDefault(); break; } }); window.addEventListener('keyup', (e) => { if (e.code === 'ArrowDown' || e.code === 'KeyS') { if (this.slideHeld) { this.slideHeld = false; this._emit('slideEnd'); } } }); } /* -------- Сенсор -------- */ _bindTouch() { const jumpBtn = document.getElementById('touch-jump'); const slideBtn = document.getElementById('touch-slide'); if (!jumpBtn || !slideBtn) return; const press = (el, downFn, upFn) => { let activePointer = null; el.addEventListener('pointerdown', (e) => { if (activePointer !== null || (e.button !== undefined && e.button !== 0)) return; e.preventDefault(); activePointer = e.pointerId; el.classList.add('is-pressed'); downFn(); // Захват указателя не критичен: если браузер его не даёт, кнопка // всё равно должна работать (иначе она «залипает» навсегда). try { if (el.setPointerCapture) el.setPointerCapture(e.pointerId); } catch (err) { /* не поддержано */ } }); const release = (e) => { if (activePointer === null || (e.pointerId !== undefined && e.pointerId !== activePointer)) return; if (e.cancelable) e.preventDefault(); activePointer = null; el.classList.remove('is-pressed'); if (upFn) upFn(); }; el.addEventListener('pointerup', release); el.addEventListener('pointercancel', release); el.addEventListener('lostpointercapture', release); // Подстраховка: палец отпущен за пределами кнопки — подкат не должен зависнуть. window.addEventListener('pointerup', release); window.addEventListener('pointercancel', release); }; press(jumpBtn, () => { this.jumpPressed = true; this._emit('jump'); }); press(slideBtn, () => { if (!this.slideHeld) { this.slideHeld = true; this._emit('slideStart'); } }, () => { if (this.slideHeld) { this.slideHeld = false; this._emit('slideEnd'); } }); } /** Считать и сбросить одноразовый прыжок. */ consumeJump() { const v = this.jumpPressed; this.jumpPressed = false; return v; } /** Считать и сбросить паузу. */ consumePause() { const v = this.pausePressed; this.pausePressed = false; return v; } /** Сбросить все состояния (при смене экрана). */ reset() { this.jumpPressed = false; this.pausePressed = false; this.slideHeld = false; } }