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.interactionSystem.updatePlayerPosition(
this.currentPlayerControllerTelemetry.feetPosition,
{
feetPosition: this.currentPlayerControllerTelemetry.feetPosition,
eyePosition: this.currentPlayerControllerTelemetry.eyePosition
},
this.runtimeScene,
this.createInteractionDispatcher()
);

View File

@@ -255,23 +255,6 @@ function getInteractableTargetRadius(
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(
npc: RuntimeNpc,
hasClickLinks: boolean
@@ -413,13 +396,21 @@ export class RuntimeInteractionSystem {
}
updatePlayerPosition(
feetPosition: Vec3,
playerProbe: Vec3 | RuntimePlayerTriggerProbe,
runtimeScene: RuntimeSceneDefinition,
dispatcher: RuntimeInteractionDispatcher
) {
const feetPosition = isVec3(playerProbe)
? playerProbe
: playerProbe.feetPosition;
const eyePosition = isVec3(playerProbe)
? playerProbe
: playerProbe.eyePosition;
for (const triggerVolume of runtimeScene.entities.triggerVolumes) {
const containsPlayer = isPointInsideTriggerVolume(
const containsPlayer = isPlayerInsideTriggerVolume(
feetPosition,
eyePosition,
triggerVolume
);
const wasOccupied = this.occupiedTriggerVolumes.has(
@@ -562,18 +553,17 @@ export class RuntimeInteractionSystem {
continue;
}
const radius = getNpcDialogueTargetRadius(npc);
const distance = distanceBetweenVec3(interactionOrigin, npc.position);
const bounds = getNpcDialogueTargetBounds(npc);
const distance = distanceBetweenVec3(interactionOrigin, bounds.center);
if (distance > radius) {
if (distance > bounds.range) {
continue;
}
const hitDistance = raySphereHitDistance(
const hitDistance = rayAxisAlignedBoxHitDistance(
rayOrigin,
normalizedViewDirection,
npc.position,
radius
bounds
);
if (hitDistance === null) {
@@ -586,7 +576,7 @@ export class RuntimeInteractionSystem {
npc.entityId,
getNpcDialoguePrompt(npc, hasClickLinks),
distance,
radius,
bounds.range,
hitDistance
);
bestPrompt = next.prompt;

View File

@@ -679,6 +679,61 @@ describe("RuntimeInteractionSystem", () => {
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", () => {
const runtimeScene = createRuntimeSceneFixture();
runtimeScene.entities.interactables = [];