fix(player): управление камерой 02 как в студии + фикс рандомного меню

Привёл _setupInput/onPointerLockChange к рабочей студийной реализации:
- onCanvasMouseDownGlobal/onWindowMouseUpGlobal — ПКМ-orbit с проверкой
  needPermLock() (как в студии), вместо самодельных onRmbDown/onRmbUp.
- onPointerLockChange: при потере lock выход из Play (меню) ТОЛЬКО если
  needPermLock (first/lockfirst/sideview/shiftLock). В third потеря lock =
  отпустили ПКМ → остаёмся в Play. Это убирает рандомное открытие меню.
- onCanvasClick лочит только в perma-режимах.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
МИН 2026-05-30 12:45:06 +03:00
parent 7b869c83bd
commit 66375e26c8

View File

@ -2212,13 +2212,23 @@ export class PlayerController {
_setupInput() { _setupInput() {
const canvas = this.canvas; const canvas = this.canvas;
// Задача 02: ЛКМ-клик НЕ берёт pointer-lock в third (курсор свободен для // Задача 02 (как в студии): хелпер — режим с ПОСТОЯННЫМ pointer-lock.
// GUI/3D-табличек). Lock берётся только в perma-режимах (first/lock) — const needPermLock = () => (
// но там он уже взят в start(). В third lock включает только зажатая ПКМ. this._cameraMode === 'first' ||
this._cameraMode === 'lockfirst' ||
this._cameraMode === 'sideview' ||
this._shiftLock
);
const onCanvasClick = () => { const onCanvasClick = () => {
// В UI-режиме клик не перехватывает мышь.
if (this._uiCursorMode) return; if (this._uiCursorMode) return;
if (this._active && this._isPermaLockMode() if (!this._active) return;
&& document.pointerLockElement !== canvas) { // Roblox-style: в third-person ЛКМ-клик НЕ лочит курсор (он остаётся
// свободным для GUI/3D-onClick). Lock запрашиваем ТОЛЬКО для режимов
// где курсор постоянно скрыт, и только если lock был снят.
if (!needPermLock()) return;
if (document.pointerLockElement !== canvas) {
try { try {
const p = canvas.requestPointerLock?.(); const p = canvas.requestPointerLock?.();
if (p && typeof p.catch === 'function') p.catch(() => {}); if (p && typeof p.catch === 'function') p.catch(() => {});
@ -2227,31 +2237,33 @@ export class PlayerController {
}; };
canvas.addEventListener('click', onCanvasClick); canvas.addEventListener('click', onCanvasClick);
// Задача 02: ПКМ-orbit — зажал ПКМ в third → lock + камера крутится, // === ПКМ: в third-person удержание ПКМ запускает orbit-камеру ===
// отпустил → курсор вернулся. В perma-режимах ПКМ не нужен. // Зажал ПКМ → курсор скрыт, мышь крутит камеру. Отпустил → курсор вернулся.
const onRmbDown = (e) => { const onCanvasMouseDownGlobal = (e) => {
if (e.button !== 2 || this._uiCursorMode) return; if (!this._active || this._uiCursorMode) return;
if (!this._isPermaLockMode() && document.pointerLockElement !== canvas) { if (e.button !== 2) return; // только ПКМ
this._rmbHeld = true; if (needPermLock()) return; // в perma-режимах ПКМ ничего не делает
this._rmbHeld = true;
if (document.pointerLockElement !== canvas) {
try { try {
const p = canvas.requestPointerLock?.(); const p = canvas.requestPointerLock?.();
if (p && typeof p.catch === 'function') p.catch(() => {}); if (p && typeof p.catch === 'function') p.catch(() => {});
} catch (err) { /* ignore */ } } catch (err) { /* ignore */ }
} }
e.preventDefault();
}; };
const onRmbUp = (e) => { const onWindowMouseUpGlobal = (e) => {
if (e.button !== 2) return; if (e.button !== 2) return;
if (this._rmbHeld) { if (!this._rmbHeld) return;
this._rmbHeld = false; this._rmbHeld = false;
if (!this._isPermaLockMode() && document.pointerLockElement === canvas) { if (needPermLock()) return;
try { document.exitPointerLock(); } catch (err) { /* ignore */ } if (document.pointerLockElement === canvas) {
} try { document.exitPointerLock(); } catch (err) { /* ignore */ }
} }
}; };
const onCtxMenu = (e) => { if (!this._uiCursorMode) e.preventDefault(); }; canvas.addEventListener('mousedown', onCanvasMouseDownGlobal);
canvas.addEventListener('mousedown', onRmbDown); window.addEventListener('mouseup', onWindowMouseUpGlobal);
document.addEventListener('mouseup', onRmbUp); canvas.addEventListener('contextmenu', (e) => { if (this._active) e.preventDefault(); });
canvas.addEventListener('contextmenu', onCtxMenu);
// === UI-режим: mousedown / mouseup → callback (для drag-игр) === // === UI-режим: mousedown / mouseup → callback (для drag-игр) ===
const onCanvasMouseDown = (e) => { const onCanvasMouseDown = (e) => {
@ -2343,18 +2355,26 @@ export class PlayerController {
let wasLocked = false; let wasLocked = false;
const onPointerLockChange = () => { const onPointerLockChange = () => {
const locked = document.pointerLockElement === canvas; const locked = document.pointerLockElement === canvas;
this._applyCursorVisibility(); // задача 02: вернуть/скрыть курсор this._applyCursorVisibility?.(); // задача 02: вернуть/скрыть курсор
if (locked) { if (locked) {
wasLocked = true; wasLocked = true;
this._rmbHeld = true; // если попали в lock — ПКМ удерживается
} else if (wasLocked && this._active) { } else if (wasLocked && this._active) {
// pointer-lock снят. Причин три:
// 1) пользователь в UI-режиме (game.input.setCursorMode('ui'))
// 2) ПКМ отпущена в third-person (orbit-камера завершена)
// 3) Esc → выход из Play (если был в first/lockfirst/sideview/shift)
wasLocked = false; wasLocked = false;
if (this._uiCursorMode) return; this._rmbHeld = false;
// Задача 02: в third потеря lock = отпустили ПКМ (orbit) ИЛИ if (this._uiCursorMode) { this._applyCursorVisibility?.(); return; }
// вышли зумом из first — это НЕ выход из Play. Выход (Esc) только if (needPermLock()) {
// из perma-режимов с постоянным lock (first/lockfirst/shift-lock). // Был режим с постоянным lock'ом и его сняли (Esc) → выход.
if (this._rmbHeld) { this._rmbHeld = false; return; } if (this._onExitRequest) this._onExitRequest();
if (this._cameraMode === 'third' || this._cameraMode === 'front') return; } else {
if (this._onExitRequest) this._onExitRequest(); // Third-person: просто отпустили ПКМ. Остаёмся в Play,
// курсор вернулся — это НЕ повод открывать меню.
this._applyCursorVisibility?.();
}
} }
}; };
document.addEventListener('pointerlockchange', onPointerLockChange); document.addEventListener('pointerlockchange', onPointerLockChange);