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;