Implement manual pointer look input handling and update target assist logic

This commit is contained in:
2026-04-26 21:42:57 +02:00
parent c550ca7634
commit c9327d6239
2 changed files with 69 additions and 3 deletions

View File

@@ -179,6 +179,7 @@ export class ThirdPersonNavigationController implements NavigationController {
private inWaterVolume = false;
private inFogVolume = false;
private dragging = false;
private pointerLookInputPending = false;
private lastPointerClientX = 0;
private lastPointerClientY = 0;
private initializedFromSpawn = false;
@@ -276,6 +277,7 @@ export class ThirdPersonNavigationController implements NavigationController {
this.jumpHoldRemainingMs = 0;
this.previousTelemetry = null;
this.smoothedCameraCollisionDistance = null;
this.pointerLookInputPending = false;
ctx.setRuntimeMessage(null);
ctx.setPlayerControllerTelemetry(null);
this.context = null;
@@ -310,6 +312,7 @@ export class ThirdPersonNavigationController implements NavigationController {
this.inWaterVolume = false;
this.inFogVolume = false;
this.dragging = false;
this.pointerLookInputPending = false;
this.lastPointerClientX = 0;
this.lastPointerClientY = 0;
this.initializedFromSpawn = false;
@@ -346,6 +349,9 @@ export class ThirdPersonNavigationController implements NavigationController {
const cameraDrivenExternally = this.context.isCameraDrivenExternally() === true;
const lookInputActive = lookInput.horizontal !== 0 || lookInput.vertical !== 0;
const manualLookInputActive =
lookInputActive || this.pointerLookInputPending;
this.pointerLookInputPending = false;
let targetLookResult: RuntimeTargetLookInputResult | null = null;
if (!cameraDrivenExternally && lookInputActive) {
@@ -385,7 +391,7 @@ export class ThirdPersonNavigationController implements NavigationController {
if (
cameraDrivenExternally ||
!lookInputActive ||
!manualLookInputActive ||
targetLookResult?.activeTargetLocked !== true ||
targetLookResult.switchedTarget === true ||
targetLookResult.switchInputHeld === true
@@ -404,7 +410,7 @@ export class ThirdPersonNavigationController implements NavigationController {
);
}
if (!cameraDrivenExternally) {
if (!cameraDrivenExternally && !manualLookInputActive) {
const targetAssist =
this.context.resolveThirdPersonTargetAssist?.() ?? null;
@@ -449,7 +455,7 @@ export class ThirdPersonNavigationController implements NavigationController {
dt
);
}
} else {
} else if (cameraDrivenExternally) {
this.targetAssistLookOffsetY = dampScalar(
this.targetAssistLookOffsetY,
0,
@@ -817,6 +823,12 @@ export class ThirdPersonNavigationController implements NavigationController {
this.lastPointerClientX = event.clientX;
this.lastPointerClientY = event.clientY;
if (deltaX === 0 && deltaY === 0) {
return;
}
this.pointerLookInputPending = true;
const targetLookResult =
this.context?.handleRuntimeTargetLookInput?.({
horizontal: deltaX * POINTER_TARGET_LOOK_INPUT_SCALE,

View File

@@ -454,6 +454,60 @@ describe("ThirdPersonNavigationController", () => {
controller.deactivate(targetContext);
});
it("pauses third-person target assist while the camera is actively moved with pointer drag", () => {
const { context } = createRuntimeControllerContext();
const controller = new ThirdPersonNavigationController();
const controllerInternals = controller as unknown as {
cameraYawRadians: number;
pitchRadians: number;
targetAssistLookOffsetY: number;
};
const targetContext = {
...context,
resolveThirdPersonTargetAssist: () => ({
targetPosition: {
x: 0,
y: 4,
z: 5
},
strength: 1
}),
handleRuntimeTargetLookInput: vi.fn(() => ({
activeTargetLocked: true,
switchedTarget: false,
switchInputHeld: false
}))
};
controller.activate(targetContext);
controllerInternals.cameraYawRadians = 1.1;
controllerInternals.pitchRadians = 1.1;
controllerInternals.targetAssistLookOffsetY = 0;
targetContext.domElement.dispatchEvent(
new PointerEvent("pointerdown", {
button: 0,
clientX: 0,
clientY: 0
})
);
window.dispatchEvent(
new PointerEvent("pointermove", {
clientX: 30,
clientY: 12
})
);
controller.update(0.016);
expect(controllerInternals.cameraYawRadians).toBeCloseTo(1.1, 5);
expect(controllerInternals.pitchRadians).toBeCloseTo(1.1, 5);
expect(controllerInternals.targetAssistLookOffsetY).toBeCloseTo(0, 5);
window.dispatchEvent(new PointerEvent("pointerup"));
controller.deactivate(targetContext);
});
it("fades vertical target assist when camera collision pushes the camera close", () => {
const { context } = createRuntimeControllerContext();
const controller = new ThirdPersonNavigationController();