Refactor: Simplify third-person pointer lock and escape key handling

This commit is contained in:
2026-04-27 18:15:39 +02:00
parent 927482a15c
commit 6033089ad8
4 changed files with 19 additions and 136 deletions

View File

@@ -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

View File

@@ -166,7 +166,6 @@ export interface RuntimeControllerContext {
input: RuntimeTargetLookInput
): RuntimeTargetLookInputResult;
handleRuntimeTargetLookBoundaryReached?(): boolean;
handleThirdPersonPointerLockReleased?(): boolean;
isCameraDrivenExternally(): boolean;
getCameraYawRadians(): number;
isInputSuspended(): boolean;

View File

@@ -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) {

View File

@@ -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<void>;
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<void>;
};
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<void>;
};
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 = () => {