diff --git a/src/app/App.tsx b/src/app/App.tsx index d6def651..c04f94ea 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -4899,9 +4899,7 @@ export function App({ store, initialStatusMessage }: AppProps) { return; } - const pointerCaptured = - activeNavigationMode === "firstPerson" && - firstPersonTelemetry?.pointerLocked === true; + const pointerCaptured = firstPersonTelemetry?.pointerLocked === true; if (pointerCaptured) { return; @@ -4916,7 +4914,7 @@ export function App({ store, initialStatusMessage }: AppProps) { return () => { window.removeEventListener("keydown", handleWindowKeyDown); }; - }, [activeNavigationMode, editorState.toolMode, firstPersonTelemetry]); + }, [editorState.toolMode, firstPersonTelemetry]); const applyProjectName = () => { const normalizedName = projectNameDraft.trim() || DEFAULT_PROJECT_NAME; @@ -13359,11 +13357,7 @@ export function App({ store, initialStatusMessage }: AppProps) {
Pointer Lock
- {activeNavigationMode === "firstPerson" - ? firstPersonTelemetry?.pointerLocked - ? "active" - : "idle" - : "not used"} + {firstPersonTelemetry?.pointerLocked ? "active" : "idle"}
diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index 6872891f..4c351967 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -969,13 +969,56 @@ export class RuntimeHost { this.runtimeMessageHandler?.(message); }, setPlayerControllerTelemetry: (telemetry) => { + const pointerLockReleasedWithEscapeTargetClear = + this.currentPlayerControllerTelemetry?.pointerLocked === true && + telemetry !== null && + telemetry.pointerLocked === false && + this.activeController === this.thirdPersonController && + this.activeRuntimeTargetReference !== null && + this.resolveRuntimePlayerInputBindings().keyboard.clearTarget === + "Escape"; + this.currentPlayerControllerTelemetry = telemetry; this.currentPlayerAudioHooks = telemetry?.hooks.audio ?? null; + + if (pointerLockReleasedWithEscapeTargetClear) { + this.clearActiveRuntimeTarget(); + this.requestRuntimePointerLock(); + } + this.playerControllerTelemetryHandler?.(telemetry); } }; } + private requestRuntimePointerLock() { + if ( + document.pointerLockElement === this.domElement || + (this.activeController !== this.firstPersonController && + this.activeController !== this.thirdPersonController) + ) { + return; + } + + const pointerLockCapableElement = this.domElement as HTMLCanvasElement & { + requestPointerLock?: () => void | Promise; + }; + + if (typeof pointerLockCapableElement.requestPointerLock !== "function") { + return; + } + + try { + const pointerLockResult = pointerLockCapableElement.requestPointerLock(); + + if (pointerLockResult instanceof Promise) { + pointerLockResult.catch(() => {}); + } + } catch { + // Browser Escape handling can reject immediate recapture; clearing wins. + } + } + private resolvePlayerVolumeState(feetPosition: { x: number; y: number;