From 8ad66ae4d50f47a499f7b6681f2919a613a2af25 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Thu, 5 Mar 2026 14:13:09 +0100 Subject: [PATCH] Use CameraComponent for view point and gaze target (VR/FPS compatibility) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InteractionComponent: GetPawnViewPoint() now prefers UCameraComponent over PlayerController::GetPlayerViewPoint() — fixes agent selection in VR where the pawn root stays at spawn while the HMD moves freely - GazeComponent: ResolveTargetPosition() uses camera first for locally controlled pawns (VR HMD / FPS eye position), falls back to bone chain for NPCs and remote players in multiplayer Co-Authored-By: Claude Opus 4.6 --- .../Private/PS_AI_ConvAgent_GazeComponent.cpp | 89 +++++++++++-------- .../PS_AI_ConvAgent_InteractionComponent.cpp | 15 +++- 2 files changed, 63 insertions(+), 41 deletions(-) diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_GazeComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_GazeComponent.cpp index 7993aea..9b74a24 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_GazeComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_GazeComponent.cpp @@ -58,48 +58,59 @@ static FVector ResolveTargetPosition(const AActor* Target, bool bAutoEyes, { if (!Target) return FVector::ZeroVector; - if (bAutoEyes) + if (!bAutoEyes) { - // Try to find a Face mesh with eye bones on the target - TArray SkelMeshes; - const_cast(Target)->GetComponents(SkelMeshes); - - for (const USkeletalMeshComponent* SMC : SkelMeshes) - { - if (!SMC) continue; - - if (SMC->DoesSocketExist(TargetEyeBoneL) && SMC->DoesSocketExist(TargetEyeBoneR)) - { - // Midpoint between both eye bones - return (SMC->GetSocketLocation(TargetEyeBoneL) - + SMC->GetSocketLocation(TargetEyeBoneR)) * 0.5f; - } - } - - // Fallback: head bone on any mesh - for (const USkeletalMeshComponent* SMC : SkelMeshes) - { - if (!SMC) continue; - if (SMC->DoesSocketExist(TargetHeadBone)) - { - return SMC->GetSocketLocation(TargetHeadBone); - } - } - - // Fallback: CameraComponent — the canonical eye position for - // first-person pawns and any actor with an active camera. - if (const UCameraComponent* Cam = - const_cast(Target)->FindComponentByClass()) - { - return Cam->GetComponentLocation(); - } - - // Final fallback: actor origin + height offset - return Target->GetActorLocation() + FVector(0.0f, 0.0f, FallbackHeight); + // Manual mode: actor origin + user-defined offset + return Target->GetActorLocation() + ManualOffset; } - // Manual mode: actor origin + user-defined offset - return Target->GetActorLocation() + ManualOffset; + // For locally controlled pawns (VR/FPS player), prefer the CameraComponent. + // In VR the pawn's skeletal mesh may stay at the spawn point while the HMD + // moves freely — using eye bones would make the agent look at the floor. + if (const APawn* Pawn = Cast(Target)) + { + if (Pawn->IsLocallyControlled()) + { + if (const UCameraComponent* Cam = + const_cast(Target)->FindComponentByClass()) + { + return Cam->GetComponentLocation(); + } + } + } + + // For other targets (NPCs, remote players): bone chain. + TArray SkelMeshes; + const_cast(Target)->GetComponents(SkelMeshes); + + for (const USkeletalMeshComponent* SMC : SkelMeshes) + { + if (!SMC) continue; + if (SMC->DoesSocketExist(TargetEyeBoneL) && SMC->DoesSocketExist(TargetEyeBoneR)) + { + return (SMC->GetSocketLocation(TargetEyeBoneL) + + SMC->GetSocketLocation(TargetEyeBoneR)) * 0.5f; + } + } + + for (const USkeletalMeshComponent* SMC : SkelMeshes) + { + if (!SMC) continue; + if (SMC->DoesSocketExist(TargetHeadBone)) + { + return SMC->GetSocketLocation(TargetHeadBone); + } + } + + // CameraComponent fallback for non-locally-controlled targets that have one. + if (const UCameraComponent* Cam = + const_cast(Target)->FindComponentByClass()) + { + return Cam->GetComponentLocation(); + } + + // Final fallback: actor origin + height offset. + return Target->GetActorLocation() + FVector(0.0f, 0.0f, FallbackHeight); } // ───────────────────────────────────────────────────────────────────────────── diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp index 2f121ec..2fe3cff 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp @@ -9,6 +9,7 @@ #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "Camera/PlayerCameraManager.h" +#include "Camera/CameraComponent.h" #include "TimerManager.h" #include "Net/UnrealNetwork.h" @@ -434,7 +435,17 @@ void UPS_AI_ConvAgent_InteractionComponent::GetPawnViewPoint(FVector& OutLocatio return; } - // Try to get the player controller's camera view (most accurate for first/third person). + // Prefer the CameraComponent directly — most reliable for VR (HMD) and FPS (eye level). + // In VR the pawn's actor location stays at the spawn point while the HMD moves freely, + // so GetPlayerViewPoint() may not reflect the actual head position. + if (UCameraComponent* Cam = Owner->FindComponentByClass()) + { + OutLocation = Cam->GetComponentLocation(); + OutDirection = Cam->GetForwardVector(); + return; + } + + // Fallback: PlayerController view point (camera manager pipeline). if (APawn* Pawn = Cast(Owner)) { if (APlayerController* PC = Cast(Pawn->GetController())) @@ -446,7 +457,7 @@ void UPS_AI_ConvAgent_InteractionComponent::GetPawnViewPoint(FVector& OutLocatio } } - // Fallback: use actor location and forward. + // Final fallback: actor transform. OutLocation = Owner->GetActorLocation(); OutDirection = Owner->GetActorForwardVector(); }