Update player position handling in interaction system and tests

This commit is contained in:
2026-04-14 21:12:42 +02:00
parent 7449dccd61
commit 7198b43241
3 changed files with 75 additions and 27 deletions

View File

@@ -2734,7 +2734,10 @@ export class RuntimeHost {
this.currentPlayerControllerTelemetry !== null this.currentPlayerControllerTelemetry !== null
) { ) {
this.interactionSystem.updatePlayerPosition( this.interactionSystem.updatePlayerPosition(
this.currentPlayerControllerTelemetry.feetPosition, {
feetPosition: this.currentPlayerControllerTelemetry.feetPosition,
eyePosition: this.currentPlayerControllerTelemetry.eyePosition
},
this.runtimeScene, this.runtimeScene,
this.createInteractionDispatcher() this.createInteractionDispatcher()
); );

View File

@@ -255,23 +255,6 @@ function getInteractableTargetRadius(
return Math.min(DEFAULT_INTERACTABLE_TARGET_RADIUS, interactable.radius); return Math.min(DEFAULT_INTERACTABLE_TARGET_RADIUS, interactable.radius);
} }
function getNpcDialogueTargetRadius(npc: RuntimeNpc): number {
switch (npc.collider.mode) {
case "capsule":
return Math.max(
DEFAULT_NPC_DIALOGUE_TARGET_RADIUS,
npc.collider.radius * 2
);
case "box":
return Math.max(
DEFAULT_NPC_DIALOGUE_TARGET_RADIUS,
Math.max(npc.collider.size.x, npc.collider.size.z) * 0.75
);
case "none":
return DEFAULT_NPC_DIALOGUE_TARGET_RADIUS;
}
}
function getNpcDialoguePrompt( function getNpcDialoguePrompt(
npc: RuntimeNpc, npc: RuntimeNpc,
hasClickLinks: boolean hasClickLinks: boolean
@@ -413,13 +396,21 @@ export class RuntimeInteractionSystem {
} }
updatePlayerPosition( updatePlayerPosition(
feetPosition: Vec3, playerProbe: Vec3 | RuntimePlayerTriggerProbe,
runtimeScene: RuntimeSceneDefinition, runtimeScene: RuntimeSceneDefinition,
dispatcher: RuntimeInteractionDispatcher dispatcher: RuntimeInteractionDispatcher
) { ) {
const feetPosition = isVec3(playerProbe)
? playerProbe
: playerProbe.feetPosition;
const eyePosition = isVec3(playerProbe)
? playerProbe
: playerProbe.eyePosition;
for (const triggerVolume of runtimeScene.entities.triggerVolumes) { for (const triggerVolume of runtimeScene.entities.triggerVolumes) {
const containsPlayer = isPointInsideTriggerVolume( const containsPlayer = isPlayerInsideTriggerVolume(
feetPosition, feetPosition,
eyePosition,
triggerVolume triggerVolume
); );
const wasOccupied = this.occupiedTriggerVolumes.has( const wasOccupied = this.occupiedTriggerVolumes.has(
@@ -562,18 +553,17 @@ export class RuntimeInteractionSystem {
continue; continue;
} }
const radius = getNpcDialogueTargetRadius(npc); const bounds = getNpcDialogueTargetBounds(npc);
const distance = distanceBetweenVec3(interactionOrigin, npc.position); const distance = distanceBetweenVec3(interactionOrigin, bounds.center);
if (distance > radius) { if (distance > bounds.range) {
continue; continue;
} }
const hitDistance = raySphereHitDistance( const hitDistance = rayAxisAlignedBoxHitDistance(
rayOrigin, rayOrigin,
normalizedViewDirection, normalizedViewDirection,
npc.position, bounds
radius
); );
if (hitDistance === null) { if (hitDistance === null) {
@@ -586,7 +576,7 @@ export class RuntimeInteractionSystem {
npc.entityId, npc.entityId,
getNpcDialoguePrompt(npc, hasClickLinks), getNpcDialoguePrompt(npc, hasClickLinks),
distance, distance,
radius, bounds.range,
hitDistance hitDistance
); );
bestPrompt = next.prompt; bestPrompt = next.prompt;

View File

@@ -679,6 +679,61 @@ describe("RuntimeInteractionSystem", () => {
expect(dispatches).toEqual(["link-trigger-dialogue:dialogue-threshold"]); expect(dispatches).toEqual(["link-trigger-dialogue:dialogue-threshold"]);
}); });
it("treats the player body segment as entering a trigger volume, not just the feet point", () => {
const runtimeScene = createRuntimeSceneFixture();
runtimeScene.entities.triggerVolumes = [
{
entityId: "entity-trigger-chest-height",
position: {
x: 0,
y: 1.2,
z: 0
},
size: {
x: 2,
y: 1,
z: 2
},
triggerOnEnter: true,
triggerOnExit: false
}
];
runtimeScene.interactionLinks = [
createStartDialogueInteractionLink({
id: "link-body-trigger-dialogue",
sourceEntityId: "entity-trigger-chest-height",
trigger: "enter",
dialogueId: "dialogue-threshold"
})
];
const dispatches: string[] = [];
const interactionSystem = new RuntimeInteractionSystem();
interactionSystem.updatePlayerPosition(
{
feetPosition: {
x: 0,
y: 0,
z: 0
},
eyePosition: {
x: 0,
y: 1.6,
z: 0
}
},
runtimeScene,
createDispatcher({
startDialogue: (dialogueId, source) => {
dispatches.push(`${source?.linkId}:${dialogueId}`);
}
})
);
expect(dispatches).toEqual(["link-body-trigger-dialogue:dialogue-threshold"]);
});
it("resolves direct NPC dialogue prompts and dispatches them through the shared start path", () => { it("resolves direct NPC dialogue prompts and dispatches them through the shared start path", () => {
const runtimeScene = createRuntimeSceneFixture(); const runtimeScene = createRuntimeSceneFixture();
runtimeScene.entities.interactables = []; runtimeScene.entities.interactables = [];