|
|
|
@@ -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)
|
|
|
|
|
{
|
|
|
|
|