From acb5b0b133b8c411e8e2281633d65bb166c49799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=98=D0=9D?= Date: Sun, 31 May 2026 09:55:58 +0300 Subject: [PATCH] =?UTF-8?q?fix(player):=20=D0=BC=D0=B5=D0=BD=D1=8E=20ESC?= =?UTF-8?q?=20=E2=80=94=20toggle=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=85=20+=20=D1=87=D0=B8=D0=BD=D0=B8=D1=82?= =?UTF-8?q?=20orbit-=D0=BA=D0=B0=D0=BC=D0=B5=D1=80=D1=83=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=9F=D0=9A=D0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Два бага меню в плеере: 1. Повторный ESC открывал меню ПОВЕРХ первого (не закрывал). 2. После открытия/закрытия меню переставала работать orbit-камера по зажатой ПКМ (игры задачи 2 camera_mouse_controls). Первопричина: ESC слушали ДВА обработчика — движок (setOnExitRequest → _onEscMenu) и React (отдельный keydown при topMenuOpen). На одно нажатие срабатывали оба → гонка: меню дублировалось, а _uiCursorMode застревал в true, из-за чего onCanvasMouseDownGlobal (if _uiCursorMode return) игнорировал ПКМ → orbit-камера не включалась. Фикс — единый источник истины в движке: - BabylonScene: флаг _playerMenuOpen + toggle в setOnExitRequest (открыто→ закрыть+setUiCursorMode(false), закрыто→открыть). _onEscMenu(open) передаёт состояние в UI. setPlayerMenuOpen(open) — синхронизация при закрытии из UI (кнопка «Продолжить»). Сброс флага в enterPlayMode. - KubikonPlayer: setOnEscMenu((open)=>setTopMenuOpen(open)); УБРАН дублирующий React ESC-обработчик; onClose меню → setPlayerMenuOpen(false); синхронизация _playerMenuOpen=true в onLockChange (perma) и setOnPlayChange. - PlayerController.setUiCursorMode(true): сброс _rmbHeld=false (иначе если меню открыли при зажатой ПКМ, флаг застревал → orbit «думал» что ПКМ активна). Проверено: ESC открыл→ESC закрыл (1 меню в DOM), ПКМ-orbit работает после меню. Co-Authored-By: Claude Opus 4.8 --- src/KubikonPlayer/KubikonPlayer.jsx | 46 +++++++++++++++-------------- src/engine/BabylonScene.js | 41 ++++++++++++++++++++----- src/engine/PlayerController.js | 4 +++ 3 files changed, 62 insertions(+), 29 deletions(-) diff --git a/src/KubikonPlayer/KubikonPlayer.jsx b/src/KubikonPlayer/KubikonPlayer.jsx index 7ac0043..9d62987 100644 --- a/src/KubikonPlayer/KubikonPlayer.jsx +++ b/src/KubikonPlayer/KubikonPlayer.jsx @@ -517,13 +517,20 @@ const KubikonPlayer = () => { s?.player?.setUiCursorMode?.(true); setChatOpen(false); setTopMenuOpen(true); + try { if (s) s._playerMenuOpen = true; } catch (e) { /* ignore */ } } }); - // ESC в Play → меню-оверлей поверх ЖИВОЙ игры (Roblox-style). Play не - // прерывается, скрипты продолжают идти, игрок не респавнится. - scene.setOnEscMenu?.(() => { - setChatOpen(false); - setTopMenuOpen(true); + // ESC в Play → TOGGLE меню-оверлея поверх ЖИВОЙ игры (Roblox-style). + // Движок сам решает open/close (единый источник истины _playerMenuOpen) + // и передаёт сюда. Это убирает гонку двух ESC-обработчиков, из-за которой + // меню открывалось поверх меню, а orbit-камера по ПКМ зависала. + scene.setOnEscMenu?.((open) => { + if (open) { + setChatOpen(false); + setTopMenuOpen(true); + } else { + setTopMenuOpen(false); + } }); // Загружаем проект. @@ -734,26 +741,20 @@ const KubikonPlayer = () => { p._uiCursorMode = true; setChatOpen(false); setTopMenuOpen(true); + // Синхронизируем единый флаг меню в движке, чтобы следующий ESC + // сработал как toggle-закрытие (а не открыл второе меню). + try { s._playerMenuOpen = true; } catch (e) { /* ignore */ } }; // capture-фаза, чтобы успеть раньше PlayerController document.addEventListener('pointerlockchange', onLockChange, true); return () => document.removeEventListener('pointerlockchange', onLockChange, true); }, []); - // Повторный ESC (когда меню уже открыто) — закрыть меню и вернуть - // мышь в игру. - useEffect(() => { - if (!topMenuOpen) return; - const onEsc = (e) => { - if (e.key !== 'Escape') return; - const s = sceneRef.current; - if (!s || !s._isPlaying) return; - setTopMenuOpen(false); - s.player?.setUiCursorMode?.(false); - }; - window.addEventListener('keydown', onEsc, true); - return () => window.removeEventListener('keydown', onEsc, true); - }, [topMenuOpen]); + // Повторный ESC (toggle закрытие) теперь обрабатывает движок через + // setOnExitRequest → _onEscMenu(false). Отдельный React-обработчик ESC + // УБРАН — он слушал тот же ESC, что и движок, и создавал гонку: + // меню открывалось поверх себя, а _uiCursorMode застревал в true + // (orbit-камера по ПКМ переставала работать после закрытия меню). // Горячая клавиша T — открыть/закрыть чат. Игнорируем когда: // • уже введён текст в /