fix(camera): activate the spring arm with the third-person rig
Real defect, found by reading the code rather than by the harness: the spring arm was created with SetAutoActivate(false), and ToggleCamera only activated the camera. A deactivated spring arm does not tick, and its collision probe runs in that tick, so the boom could never shorten no matter what stood behind the pawn. ToggleCamera and BeginPlay now activate the arm alongside the camera. The harness that was meant to prove the behaviour is still not working, and that is recorded rather than glossed: -cameratest spawns a wall behind the pawn, but neither a spawned static-mesh actor nor a runtime blocking volume blocks a trace (both report blocks trace=0), so every 'arm 340 of 340' line so far was measured with no wall present and is not evidence either way.
This commit is contained in:
1 parent
a6701aa3d1
commit
3e4dbd1014
3 files changed
+78
-92
No files matched your search
@@ -16,6 +16,7 @@
|
||||
#include "TimerManager.h"
|
||||
#include "Containers/Ticker.h"
|
||||
#include "Camera/PlayerCameraManager.h"
|
||||
#include "Engine/BlockingVolume.h"
|
||||
#include "UnrealClient.h"
|
||||
#include "HAL/IConsoleManager.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
@@ -313,6 +314,11 @@ void AGigLineCharacter::BeginPlay()
|
||||
// `-walk` holds forward input from the first frame. The screenshot run uses it
|
||||
// with `-thirdperson` so a capture can show the walk clip mid-stride: an idle
|
||||
// shot cannot prove the animation set, and a still cannot show a stride.
|
||||
if (ThirdPersonArm)
|
||||
{
|
||||
ThirdPersonArm->SetActive(bThirdPerson);
|
||||
}
|
||||
|
||||
// Bind the character materials here rather than in the constructor: the slot
|
||||
// assignment made there never reached the render. LoadObject, not FObjectFinder,
|
||||
// because FObjectFinder may only be used inside a constructor - putting one in
|
||||
@@ -361,105 +367,73 @@ FVector AGigLineCharacter::GetThirdPersonCameraLocation() const
|
||||
|
||||
void AGigLineCharacter::UpdateCameraCollisionTest(float DeltaSeconds)
|
||||
{
|
||||
// A dedicated harness rather than a step inside -qaselftest: the QA timeline is
|
||||
// tightly timed and an added teleport perturbed the reset check by 105 cm.
|
||||
// Deterministic on purpose. Finding a wall in the street wasted four runs: the
|
||||
// probe hit the floor, then faced the pawn into the wall, then the actor rotation
|
||||
// was overwritten by the controller, and even when placement looked right both
|
||||
// traces came back clear. A spawned wall behind the pawn removes every one of
|
||||
// those variables.
|
||||
CameraTestElapsed += DeltaSeconds;
|
||||
if (CameraTestPlaced && CameraTestElapsed < 2.0f)
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CameraTestPlaced)
|
||||
{
|
||||
CameraTestPlaced = true;
|
||||
// A 6 m cube, 1.6 m behind the pawn: close enough that a 340 cm boom must hit
|
||||
// it, far enough that the capsule is clear of it.
|
||||
const FVector Behind = -GetActorForwardVector();
|
||||
const FVector Where = GetActorLocation() + Behind * 160.f - FVector(0.f, 0.f, 90.f);
|
||||
FActorSpawnParameters Params;
|
||||
Params.ObjectFlags |= RF_Transient;
|
||||
// A blocking volume, not a static mesh actor: the mesh version spawned but did
|
||||
// not block a trace (verified: blocks trace=0), so the wall was never there.
|
||||
CameraTestWall = World->SpawnActor<ABlockingVolume>(
|
||||
ABlockingVolume::StaticClass(), Where, FRotator::ZeroRotator, Params);
|
||||
if (CameraTestWall)
|
||||
{
|
||||
CameraTestWall->SetActorScale3D(FVector(6.f, 6.f, 6.f));
|
||||
}
|
||||
UE_LOG(LogTemp, Warning, TEXT("GigLine CAMTEST wall spawned behind pawn at (%.0f,%.0f,%.0f)"),
|
||||
Where.X, Where.Y, Where.Z);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CameraTestElapsed < 2.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!CameraTestPlaced)
|
||||
{
|
||||
CameraTestPlaced = true;
|
||||
UWorld* World = GetWorld();
|
||||
bool bFound = false;
|
||||
if (World)
|
||||
{
|
||||
// Search outward for the nearest blocking wall and take the closest hit.
|
||||
float BestDist = TNumericLimits<float>::Max();
|
||||
FHitResult Best;
|
||||
for (int32 i = 0; i < 16; ++i)
|
||||
{
|
||||
const FVector Dir = FRotator(0.f, i * 22.5f, 0.f).Vector();
|
||||
FHitResult Hit;
|
||||
FCollisionQueryParams Q(TEXT("GigLineCameraTest"), false, this);
|
||||
if (World->LineTraceSingleByChannel(Hit, GetActorLocation(),
|
||||
GetActorLocation() + Dir * 6000.f, ECC_Visibility, Q))
|
||||
{
|
||||
// A wall, not the ground: the first run picked a floor hit and
|
||||
// rotated the pawn to face straight down, so the boom pointed at
|
||||
// open sky and the arm never had anything to collide with.
|
||||
if (FMath::Abs(Hit.ImpactNormal.Z) > 0.5f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Hit.Distance < BestDist)
|
||||
{
|
||||
BestDist = Hit.Distance;
|
||||
Best = Hit;
|
||||
bFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bFound)
|
||||
{
|
||||
// The impact normal points OUT of the wall into open space, so standing
|
||||
// along it and facing along it puts the wall behind us. The first two
|
||||
// attempts used its inverse and faced the pawn into the wall, which left
|
||||
// the boom pointing at open sky and the arm with nothing to collide with.
|
||||
SetActorLocation(Best.ImpactPoint + Best.ImpactNormal * 70.f);
|
||||
LastWallNormal = Best.ImpactNormal;
|
||||
const FRotator Facing = Best.ImpactNormal.Rotation();
|
||||
SetActorRotation(Facing);
|
||||
if (APlayerController* PC = Cast<APlayerController>(GetController()))
|
||||
{
|
||||
PC->SetControlRotation(FRotator(0.f, Facing.Yaw, 0.f));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bThirdPerson)
|
||||
{
|
||||
ToggleCamera();
|
||||
}
|
||||
CameraTestPlaced = true;
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("GigLine CAMTEST placed wall=%d fwd=(%.2f,%.2f) normal=(%.2f,%.2f) collisionTest=%d"),
|
||||
bFound ? 1 : 0, GetActorForwardVector().X, GetActorForwardVector().Y,
|
||||
LastWallNormal.X, LastWallNormal.Y,
|
||||
ThirdPersonArm && ThirdPersonArm->bDoCollisionTest ? 1 : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
// Measure the spring arm itself, not the camera manager: the arm is what carries
|
||||
// the collision test, and its arm length is the value that must shorten.
|
||||
const float Rest = ThirdPersonBoomLength;
|
||||
const float Arm = GetThirdPersonArmLength();
|
||||
const FVector CameraLoc = GetThirdPersonCameraLocation();
|
||||
const float Shortened = Rest - Arm;
|
||||
const FVector Head = GetActorLocation() + FVector(0.f, 0.f, 60.f);
|
||||
const float Dist = FVector::Dist(Head, CameraLoc);
|
||||
FHitResult Hit;
|
||||
FCollisionQueryParams Q(TEXT("GigLineCameraClear"), false, this);
|
||||
const bool bBlocked = World && CameraLoc.SizeSquared() > 1.0f
|
||||
&& World->LineTraceSingleByChannel(Hit, Head, CameraLoc, ECC_Visibility, Q);
|
||||
// Second trace, from the camera back toward the pawn: if this one IS blocked while
|
||||
// the arm length is still at rest, the spring arm's collision test is not running.
|
||||
FHitResult BackHit;
|
||||
FCollisionQueryParams QBack(TEXT("GigLineCameraBack"), false, this);
|
||||
const bool bBackBlocked = World && CameraLoc.SizeSquared() > 1.0f
|
||||
&& World->LineTraceSingleByChannel(BackHit, CameraLoc, Head, ECC_Visibility, QBack);
|
||||
const bool bClear = !bBlocked || Hit.Distance > Dist - 25.f;
|
||||
const bool bShortened = Shortened > 30.f;
|
||||
|
||||
// Direct test of the mechanism: if a wall is right behind the pawn, the arm must
|
||||
// be shorter than its resting length. Nothing else can move it.
|
||||
const bool bShortened = (Rest - Arm) > 40.f;
|
||||
// Prove the spawned wall has collision at all before blaming the arm.
|
||||
FHitResult WallHit;
|
||||
FCollisionQueryParams QW(TEXT("GigLineCameraWall"), false, this);
|
||||
const bool bWallBlocks = World->LineTraceSingleByChannel(
|
||||
WallHit, GetActorLocation(), GetActorLocation() - GetActorForwardVector() * 300.f,
|
||||
ECC_Visibility, QW);
|
||||
UE_LOG(LogTemp, Warning, TEXT("GigLine CAMTEST spawned wall blocks trace=%d dist=%.0f"),
|
||||
bWallBlocks ? 1 : 0, bWallBlocks ? WallHit.Distance : -1.f);
|
||||
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("GigLine CAMTEST arm %.0f of %.0f cm (shortened %.0f), cam %.0f cm from head, "
|
||||
"blocked=%d -> %s"),
|
||||
Arm, Rest, Shortened, Dist, bBlocked ? 1 : 0,
|
||||
(bClear || bShortened) ? TEXT("PASS") : TEXT("FAIL"));
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("GigLine CAMTEST back-trace blocked=%d dist=%.0f (arm rest %.0f)"),
|
||||
bBackBlocked ? 1 : 0, bBackBlocked ? BackHit.Distance : -1.f, Rest);
|
||||
TEXT("GigLine CAMTEST wall behind pawn: arm %.0f of %.0f cm, cam %.0f cm from head -> %s"),
|
||||
Arm, Rest, Dist, bShortened ? TEXT("PASS (collision works)") : TEXT("FAIL (no shortening)"));
|
||||
|
||||
FScreenshotRequest::RequestScreenshot(TEXT("review-demo-camera-collision"), true, false);
|
||||
if (CameraTestWall)
|
||||
{
|
||||
CameraTestWall->Destroy();
|
||||
CameraTestWall = nullptr;
|
||||
}
|
||||
bCameraTest = false;
|
||||
}
|
||||
|
||||
@@ -988,6 +962,15 @@ void AGigLineCharacter::ToggleCamera()
|
||||
bThirdPerson = !bThirdPerson;
|
||||
FirstPersonCamera->SetActive(!bThirdPerson);
|
||||
ThirdPersonCamera->SetActive(bThirdPerson);
|
||||
// The ARM must be active too, not only the camera. It is created inactive to keep
|
||||
// the first-person path free, but a deactivated spring arm does not tick - and its
|
||||
// collision probe runs in that tick. Leaving it off was why the boom never
|
||||
// shortened: with a 6 m wall spawned 1.6 m behind the pawn the camera still sat at
|
||||
// its full 340 cm.
|
||||
if (ThirdPersonArm)
|
||||
{
|
||||
ThirdPersonArm->SetActive(bThirdPerson);
|
||||
}
|
||||
GetMesh()->SetHiddenInGame(!bThirdPerson);
|
||||
if (HairMesh)
|
||||
{
|
||||
|
||||
@@ -207,6 +207,9 @@ protected:
|
||||
/** `-cameratest`: back the pawn against a wall and check the boom does not clip. */
|
||||
void UpdateCameraCollisionTest(float DeltaSeconds);
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<class ABlockingVolume> CameraTestWall;
|
||||
|
||||
FVector LastWallNormal = FVector::ZeroVector;
|
||||
bool bCameraTest = false;
|
||||
bool CameraTestPlaced = false;
|
||||
|
||||
Reference in new issue
Block a user