From 6033089ad81d64a8bb22c6bc616a09cef87343cc Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Mon, 27 Apr 2026 18:15:39 +0200 Subject: [PATCH] Refactor: Simplify third-person pointer lock and escape key handling --- src/app/App.tsx | 5 - src/runtime-three/navigation-controller.ts | 1 - src/runtime-three/runtime-host.ts | 34 +----- .../third-person-navigation-controller.ts | 115 +++--------------- 4 files changed, 19 insertions(+), 136 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index f28df4b9..d6def651 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -298,7 +298,6 @@ import { DEFAULT_PLAYER_START_CAPSULE_RADIUS, DEFAULT_PLAYER_START_EYE_HEIGHT, DEFAULT_PLAYER_START_ALLOW_LOOK_INPUT_TARGET_SWITCH as DEFAULT_PLAYER_START_ALLOW_LOOK_INPUT_TARGET_SWITCH_VALUE, - DEFAULT_PLAYER_START_INVERT_MOUSE_CAMERA_HORIZONTAL as DEFAULT_PLAYER_START_INVERT_MOUSE_CAMERA_HORIZONTAL_VALUE, DEFAULT_PLAYER_START_INTERACTION_ANGLE_DEGREES, DEFAULT_PLAYER_START_INTERACTION_REACH_METERS, DEFAULT_PLAYER_START_NAVIGATION_MODE, @@ -2769,10 +2768,6 @@ export function App({ store, initialStatusMessage }: AppProps) { playerStartTargetButtonCyclesActiveTargetDraft, setPlayerStartTargetButtonCyclesActiveTargetDraft ] = useState(DEFAULT_PLAYER_START_TARGET_BUTTON_CYCLES_ACTIVE_TARGET_VALUE); - const [ - playerStartInvertMouseCameraHorizontalDraft, - setPlayerStartInvertMouseCameraHorizontalDraft - ] = useState(DEFAULT_PLAYER_START_INVERT_MOUSE_CAMERA_HORIZONTAL_VALUE); const [ playerStartMovementTemplateDraft, setPlayerStartMovementTemplateDraft diff --git a/src/runtime-three/navigation-controller.ts b/src/runtime-three/navigation-controller.ts index 12aa33e4..6aa52334 100644 --- a/src/runtime-three/navigation-controller.ts +++ b/src/runtime-three/navigation-controller.ts @@ -166,7 +166,6 @@ export interface RuntimeControllerContext { input: RuntimeTargetLookInput ): RuntimeTargetLookInputResult; handleRuntimeTargetLookBoundaryReached?(): boolean; - handleThirdPersonPointerLockReleased?(): boolean; isCameraDrivenExternally(): boolean; getCameraYawRadians(): number; isInputSuspended(): boolean; diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index 87957643..6872891f 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -952,28 +952,6 @@ export class RuntimeHost { this.clearActiveRuntimeTarget(); return false; }, - handleThirdPersonPointerLockReleased: () => { - if ( - this.activeController !== this.thirdPersonController || - this.activeRuntimeTargetReference === null - ) { - return false; - } - - const clearTargetKeyboardBinding = - this.resolveRuntimePlayerInputBindings().keyboard.clearTarget; - - if ( - isPlayerStartMouseBindingCode(clearTargetKeyboardBinding) || - clearTargetKeyboardBinding !== "Escape" - ) { - return false; - } - - this.clearActiveRuntimeTarget(); - this.previousClearTargetInputActive = true; - return true; - }, isCameraDrivenExternally: () => this.resolveActiveRuntimeCameraRig() !== null || this.resolveDialogueAttentionNpc() !== null, @@ -1093,8 +1071,8 @@ export class RuntimeHost { loadScene(runtimeScene: RuntimeSceneDefinition) { const requestId = ++this.collisionWorldRequestId; const preservePointerLockDuringLoad = - this.activeController !== null && - this.activeController.id === this.desiredNavigationMode && + this.activeController === this.firstPersonController && + this.desiredNavigationMode === "firstPerson" && document.pointerLockElement === this.domElement; this.sceneReady = false; @@ -1548,13 +1526,7 @@ export class RuntimeHost { return; } - const preservePointerLockDuringControllerSwitch = - this.activeController !== null && - document.pointerLockElement === this.domElement; - - this.activeController?.deactivate(this.controllerContext, { - releasePointerLock: !preservePointerLockDuringControllerSwitch - }); + this.activeController?.deactivate(this.controllerContext); this.interactionSystem.reset(); this.setInteractionPrompt(null); if (nextController === this.firstPersonController) { diff --git a/src/runtime-three/third-person-navigation-controller.ts b/src/runtime-three/third-person-navigation-controller.ts index a7ebb4e5..2b4a3780 100644 --- a/src/runtime-three/third-person-navigation-controller.ts +++ b/src/runtime-three/third-person-navigation-controller.ts @@ -181,7 +181,6 @@ export class ThirdPersonNavigationController implements NavigationController { private inFogVolume = false; private pointerLocked = false; private suppressNextPointerLockError = false; - private escapeLockedForTargeting = false; private dragging = false; private pointerLookInputPending = false; private lastPointerClientX = 0; @@ -308,7 +307,6 @@ export class ThirdPersonNavigationController implements NavigationController { this.pointerLocked = false; this.suppressNextPointerLockError = false; - this.releaseEscapeKeyLock(); this.dragging = false; this.jumpPressed = false; this.latestJumpStarted = false; @@ -359,7 +357,6 @@ export class ThirdPersonNavigationController implements NavigationController { this.inFogVolume = false; this.pointerLocked = false; this.suppressNextPointerLockError = false; - this.escapeLockedForTargeting = false; this.dragging = false; this.pointerLookInputPending = false; this.lastPointerClientX = 0; @@ -513,8 +510,6 @@ export class ThirdPersonNavigationController implements NavigationController { ); } - this.syncEscapeKeyLock(); - const movementYawRadians = cameraDrivenExternally ? this.context.getCameraYawRadians() @@ -795,11 +790,6 @@ export class ThirdPersonNavigationController implements NavigationController { document.pointerLockElement === this.context.domElement; this.pointerLocked = pointerLocked; this.dragging = false; - - if (!pointerLocked) { - this.releaseEscapeKeyLock(); - } - this.context.setRuntimeMessage( pointerLocked ? "Third Person mouse look active. Scroll to zoom, use the right stick for gamepad camera look, and press Escape to release the cursor." @@ -808,85 +798,6 @@ export class ThirdPersonNavigationController implements NavigationController { this.publishTelemetry(); } - private syncEscapeKeyLock() { - if (typeof navigator === "undefined") { - return; - } - - const keyboard = ( - navigator as Navigator & { - keyboard?: { - lock?: (keys?: string[]) => Promise; - unlock?: () => void; - }; - } - ).keyboard; - - const shouldLockEscape = - this.pointerLocked && - this.context?.resolveThirdPersonTargetAssist?.() !== null && - typeof keyboard?.lock === "function"; - - if (!shouldLockEscape) { - this.releaseEscapeKeyLock(); - return; - } - - if (this.escapeLockedForTargeting) { - return; - } - - void keyboard - .lock?.(["Escape"]) - .then(() => { - this.escapeLockedForTargeting = true; - }) - .catch(() => {}); - } - - private releaseEscapeKeyLock() { - if (!this.escapeLockedForTargeting || typeof navigator === "undefined") { - this.escapeLockedForTargeting = false; - return; - } - - const keyboard = ( - navigator as Navigator & { - keyboard?: { - unlock?: () => void; - }; - } - ).keyboard; - - keyboard?.unlock?.(); - this.escapeLockedForTargeting = false; - } - - private requestPointerLock() { - if (this.context === null) { - return; - } - - const pointerLockCapableElement = this.context - .domElement as HTMLCanvasElement & { - requestPointerLock?: () => void | Promise; - }; - - if (typeof pointerLockCapableElement.requestPointerLock !== "function") { - return; - } - - const pointerLockResult = pointerLockCapableElement.requestPointerLock(); - - if (pointerLockResult instanceof Promise) { - pointerLockResult.catch(() => { - this.context?.setRuntimeMessage( - "Pointer lock request was denied. Drag orbit remains available in Third Person." - ); - }); - } - } - private resolveHorizontalMouseLookSign() { return this.context?.getRuntimeScene().playerStart ?.invertMouseCameraHorizontal === true @@ -966,7 +877,22 @@ export class ThirdPersonNavigationController implements NavigationController { if (document.pointerLockElement !== this.context.domElement) { this.suppressNextPointerLockError = false; - this.requestPointerLock(); + const pointerLockCapableElement = this.context + .domElement as HTMLCanvasElement & { + requestPointerLock?: () => void | Promise; + }; + + if (typeof pointerLockCapableElement.requestPointerLock === "function") { + const pointerLockResult = pointerLockCapableElement.requestPointerLock(); + + if (pointerLockResult instanceof Promise) { + pointerLockResult.catch(() => { + this.context?.setRuntimeMessage( + "Pointer lock request was denied. Drag orbit remains available in Third Person." + ); + }); + } + } } if ( @@ -1098,17 +1024,8 @@ export class ThirdPersonNavigationController implements NavigationController { }; private handlePointerLockChange = () => { - const wasPointerLocked = this.pointerLocked; this.suppressNextPointerLockError = false; this.syncPointerLockState(); - - if ( - wasPointerLocked && - !this.pointerLocked && - this.context?.handleThirdPersonPointerLockReleased?.() === true - ) { - this.requestPointerLock(); - } }; private handlePointerLockError = () => {