Merge pull request 'fix(player): ���� ESC toggle + ����� orbit-������ �� ���' (#12) from fix/player-menu-toggle-camera into main
This commit is contained in:
commit
64de6c3683
@ -517,13 +517,20 @@ const KubikonPlayer = () => {
|
|||||||
s?.player?.setUiCursorMode?.(true);
|
s?.player?.setUiCursorMode?.(true);
|
||||||
setChatOpen(false);
|
setChatOpen(false);
|
||||||
setTopMenuOpen(true);
|
setTopMenuOpen(true);
|
||||||
|
try { if (s) s._playerMenuOpen = true; } catch (e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// ESC в Play → меню-оверлей поверх ЖИВОЙ игры (Roblox-style). Play не
|
// ESC в Play → TOGGLE меню-оверлея поверх ЖИВОЙ игры (Roblox-style).
|
||||||
// прерывается, скрипты продолжают идти, игрок не респавнится.
|
// Движок сам решает open/close (единый источник истины _playerMenuOpen)
|
||||||
scene.setOnEscMenu?.(() => {
|
// и передаёт сюда. Это убирает гонку двух ESC-обработчиков, из-за которой
|
||||||
|
// меню открывалось поверх меню, а orbit-камера по ПКМ зависала.
|
||||||
|
scene.setOnEscMenu?.((open) => {
|
||||||
|
if (open) {
|
||||||
setChatOpen(false);
|
setChatOpen(false);
|
||||||
setTopMenuOpen(true);
|
setTopMenuOpen(true);
|
||||||
|
} else {
|
||||||
|
setTopMenuOpen(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Загружаем проект.
|
// Загружаем проект.
|
||||||
@ -734,26 +741,20 @@ const KubikonPlayer = () => {
|
|||||||
p._uiCursorMode = true;
|
p._uiCursorMode = true;
|
||||||
setChatOpen(false);
|
setChatOpen(false);
|
||||||
setTopMenuOpen(true);
|
setTopMenuOpen(true);
|
||||||
|
// Синхронизируем единый флаг меню в движке, чтобы следующий ESC
|
||||||
|
// сработал как toggle-закрытие (а не открыл второе меню).
|
||||||
|
try { s._playerMenuOpen = true; } catch (e) { /* ignore */ }
|
||||||
};
|
};
|
||||||
// capture-фаза, чтобы успеть раньше PlayerController
|
// capture-фаза, чтобы успеть раньше PlayerController
|
||||||
document.addEventListener('pointerlockchange', onLockChange, true);
|
document.addEventListener('pointerlockchange', onLockChange, true);
|
||||||
return () => document.removeEventListener('pointerlockchange', onLockChange, true);
|
return () => document.removeEventListener('pointerlockchange', onLockChange, true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Повторный ESC (когда меню уже открыто) — закрыть меню и вернуть
|
// Повторный ESC (toggle закрытие) теперь обрабатывает движок через
|
||||||
// мышь в игру.
|
// setOnExitRequest → _onEscMenu(false). Отдельный React-обработчик ESC
|
||||||
useEffect(() => {
|
// УБРАН — он слушал тот же ESC, что и движок, и создавал гонку:
|
||||||
if (!topMenuOpen) return;
|
// меню открывалось поверх себя, а _uiCursorMode застревал в true
|
||||||
const onEsc = (e) => {
|
// (orbit-камера по ПКМ переставала работать после закрытия меню).
|
||||||
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]);
|
|
||||||
|
|
||||||
// Горячая клавиша T — открыть/закрыть чат. Игнорируем когда:
|
// Горячая клавиша T — открыть/закрыть чат. Игнорируем когда:
|
||||||
// • уже введён текст в <input>/<textarea>/contenteditable (юзер печатает)
|
// • уже введён текст в <input>/<textarea>/contenteditable (юзер печатает)
|
||||||
@ -1619,9 +1620,10 @@ const KubikonPlayer = () => {
|
|||||||
visible={topMenuOpen}
|
visible={topMenuOpen}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setTopMenuOpen(false);
|
setTopMenuOpen(false);
|
||||||
// Возвращаем мышь в pointer-lock игры (как делал
|
// Синхронизируем движок (_playerMenuOpen) И возвращаем мышь
|
||||||
// старый ESC-handler выше).
|
// в игру одним вызовом. Без этого следующий ESC решит, что
|
||||||
try { sceneRef.current?.player?.setUiCursorMode?.(false); } catch {}
|
// меню «ещё открыто», и не откроет его.
|
||||||
|
try { sceneRef.current?.setPlayerMenuOpen?.(false); } catch {}
|
||||||
}}
|
}}
|
||||||
onExit={() => exitPlayer(id)}
|
onExit={() => exitPlayer(id)}
|
||||||
onRespawn={() => respawnPlayer()}
|
onRespawn={() => respawnPlayer()}
|
||||||
|
|||||||
@ -5332,6 +5332,7 @@ export class BabylonScene {
|
|||||||
this._isPlaying = true;
|
this._isPlaying = true;
|
||||||
// Сброс состояния касаний — каждый прогон начинается «не касаясь».
|
// Сброс состояния касаний — каждый прогон начинается «не касаясь».
|
||||||
if (this._touchState) this._touchState.clear();
|
if (this._touchState) this._touchState.clear();
|
||||||
|
this._playerMenuOpen = false; // меню-оверлей закрыт на старте Play
|
||||||
// По умолчанию стандартный HUD видим в Play.
|
// По умолчанию стандартный HUD видим в Play.
|
||||||
// Скрипт может скрыть через game.hud.setVisible(false).
|
// Скрипт может скрыть через game.hud.setVisible(false).
|
||||||
this._setStdHudVisible(true);
|
this._setStdHudVisible(true);
|
||||||
@ -5392,14 +5393,24 @@ export class BabylonScene {
|
|||||||
this.modalManager.close();
|
this.modalManager.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// ESC в плеере = открыть меню-оверлей поверх ЖИВОЙ игры (как в Roblox).
|
// ESC в плеере = TOGGLE меню-оверлея поверх ЖИВОЙ игры (как в Roblox).
|
||||||
// Раньше тут был exitPlayMode() + _onPlayChange(false), из-за чего
|
// Единый источник истины — _playerMenuOpen в движке. Раньше состояние
|
||||||
// KubikonPlayer заново звал enterPlayMode → игра перезапускалась
|
// меню держал React, а ESC слушали ДВА обработчика (движок + React) →
|
||||||
// (респавн + перезапуск скриптов). Теперь только UI-курсор + сигнал
|
// гонка: меню открывалось поверх меню, а _uiCursorMode застревал в true
|
||||||
// открыть меню. Play продолжает идти под меню.
|
// → orbit-камера по ПКМ переставала работать после закрытия меню.
|
||||||
|
// Теперь движок сам решает open/close и шлёт это в _onEscMenu(open).
|
||||||
if (typeof this._onEscMenu === 'function') {
|
if (typeof this._onEscMenu === 'function') {
|
||||||
|
if (this._playerMenuOpen) {
|
||||||
|
// Меню открыто → ESC закрывает: вернуть мышь в игру.
|
||||||
|
this._playerMenuOpen = false;
|
||||||
|
this.player?.setUiCursorMode?.(false);
|
||||||
|
this._onEscMenu(false);
|
||||||
|
} else {
|
||||||
|
// Меню закрыто → ESC открывает: освободить курсор.
|
||||||
|
this._playerMenuOpen = true;
|
||||||
this.player?.setUiCursorMode?.(true);
|
this.player?.setUiCursorMode?.(true);
|
||||||
this._onEscMenu();
|
this._onEscMenu(true);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Фолбэк (если меню не подписано, напр. в студии) — старое поведение.
|
// Фолбэк (если меню не подписано, напр. в студии) — старое поведение.
|
||||||
@ -5996,6 +6007,22 @@ export class BabylonScene {
|
|||||||
this._onEscMenu = cb;
|
this._onEscMenu = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Синхронизация состояния меню-оверлея из UI (React). Когда меню закрывают
|
||||||
|
* НЕ через ESC (кнопка «Продолжить»/крестик/клик по фону), UI обязан сообщить
|
||||||
|
* движку — иначе _playerMenuOpen рассинхронизируется и следующий ESC решит,
|
||||||
|
* что меню «открыто», и не откроет его. open=false также возвращает мышь в игру.
|
||||||
|
*/
|
||||||
|
setPlayerMenuOpen(open) {
|
||||||
|
const v = !!open;
|
||||||
|
if (this._playerMenuOpen === v) return;
|
||||||
|
this._playerMenuOpen = v;
|
||||||
|
if (!v) {
|
||||||
|
// меню закрыли из UI → вернуть управление камерой/мышью
|
||||||
|
try { this.player?.setUiCursorMode?.(false); } catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Колбэк изменения сцены (любая модификация блоков/моделей).
|
* Колбэк изменения сцены (любая модификация блоков/моделей).
|
||||||
* Используется KubikonEditor для dirty-tracking → auto-save.
|
* Используется KubikonEditor для dirty-tracking → auto-save.
|
||||||
|
|||||||
@ -580,6 +580,10 @@ export class PlayerController {
|
|||||||
setUiCursorMode(enabled) {
|
setUiCursorMode(enabled) {
|
||||||
this._uiCursorMode = !!enabled;
|
this._uiCursorMode = !!enabled;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
// Открываем UI (меню/курсор) → сбрасываем удержание ПКМ. Иначе если
|
||||||
|
// меню открыли при зажатой ПКМ, _rmbHeld застревает в true и orbit-
|
||||||
|
// камера после закрытия меню «думает», что ПКМ всё ещё активна.
|
||||||
|
this._rmbHeld = false;
|
||||||
// Освобождаем мышь
|
// Освобождаем мышь
|
||||||
if (document.pointerLockElement === this.canvas) {
|
if (document.pointerLockElement === this.canvas) {
|
||||||
try { document.exitPointerLock(); } catch (e) { /* ignore */ }
|
try { document.exitPointerLock(); } catch (e) { /* ignore */ }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user