From 45895e53387c7fbf9ffc4cbc7e9d19e72b95e2da Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Mon, 27 Apr 2026 19:29:49 +0200 Subject: [PATCH] Improve pointer lock handling and dedicated escape key release logic --- src/runtime-three/runtime-host.ts | 62 +++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index 435b1e68..0b065167 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -971,6 +971,14 @@ export class RuntimeHost { this.runtimeMessageHandler?.(message); }, setPlayerControllerTelemetry: (telemetry) => { + const previousPointerLocked = + this.currentPlayerControllerTelemetry?.pointerLocked === true; + const nextPointerLocked = telemetry?.pointerLocked === true; + + if (previousPointerLocked && !nextPointerLocked) { + this.lastPointerLockReleaseAt = performance.now(); + } + this.currentPlayerControllerTelemetry = telemetry; this.currentPlayerAudioHooks = telemetry?.hooks.audio ?? null; this.playerControllerTelemetryHandler?.(telemetry); @@ -6725,12 +6733,57 @@ export class RuntimeHost { event.stopImmediatePropagation(); }; + private shouldReserveEscapeForPointerLockRelease(): boolean { + if (document.pointerLockElement !== null) { + return true; + } + + if (this.currentPlayerControllerTelemetry?.pointerLocked === true) { + return true; + } + + return ( + performance.now() - this.lastPointerLockReleaseAt <= + POINTER_LOCK_ESCAPE_RELEASE_GRACE_MS + ); + } + + private releasePointerLockForEscape(event: KeyboardEvent): boolean { + if ( + event.code !== "Escape" || + !this.shouldReserveEscapeForPointerLockRelease() + ) { + return false; + } + + this.pressedKeys.delete(event.code); + this.previousClearTargetInputActive = false; + this.previousPauseInputActive = false; + + if ( + document.pointerLockElement !== null && + typeof document.exitPointerLock === "function" + ) { + document.exitPointerLock(); + this.lastPointerLockReleaseAt = performance.now(); + } + + event.stopImmediatePropagation(); + return true; + } + + private handlePointerLockEscapeKeyDown = (event: KeyboardEvent) => { + this.releasePointerLockForEscape(event); + }; + private handleRuntimeKeyDown = (event: KeyboardEvent) => { if (this.runtimeScene === null || !this.sceneReady) { return; } - this.pressedKeys.add(event.code); + if (this.releasePointerLockForEscape(event)) { + return; + } if ( event.defaultPrevented || @@ -6743,12 +6796,7 @@ export class RuntimeHost { return; } - if ( - document.pointerLockElement === this.domElement && - event.code === "Escape" - ) { - return; - } + this.pressedKeys.add(event.code); const playerInputBindings = this.resolveRuntimePlayerInputBindings(); const interactKeyboardBinding = playerInputBindings.keyboard.interact;