Two defects from the 0.2.0 review, both fixed and both measured. - Fingers splayed because the bind is an A-pose hand. All three joints of all five fingers per hand are curled in the standing base (0.28/0.34/0.22 rad), with the curl axis probed on the rig first. The front shot now shows relaxed hands. - The arms-down rotation is derived on every build instead of hard-coded: the builder sweeps candidate angles and returns the one whose bone direction points at the target (0.60 rad, direction dot 1.000 this build). The old 0.55 was a hand-swept constant. - Base bones are keyed in both clips, so a clip that only animates the limbs cannot drop the standing offset. Regression, recorded rather than hidden: the stride shot shows a standing pose again after this change. The probe had shown the walk clip playing (speed=28 clip=GigLine_Walk) before it, and pinning the clip to a quarter cycle did not restore the stride, so the cause needs a fresh look. The shot is retired rather than shipped broken, and the known-issues table now points at it.
172 lines
5.4 KiB
C++
172 lines
5.4 KiB
C++
#include "GigLineShotActor.h"
|
|
|
|
#include "GigLineCharacter.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
|
|
#include "Camera/CameraActor.h"
|
|
#include "Camera/CameraComponent.h"
|
|
#include "Engine/World.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "UnrealClient.h"
|
|
|
|
AGigLineShotActor::AGigLineShotActor()
|
|
{
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
PrimaryActorTick.bStartWithTickEnabled = true;
|
|
}
|
|
|
|
void AGigLineShotActor::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
NextShot();
|
|
}
|
|
|
|
void AGigLineShotActor::Tick(float DeltaSeconds)
|
|
{
|
|
Super::Tick(DeltaSeconds);
|
|
|
|
// QA mode: the -qaselftest run owns the process (it walks, resets, pauses,
|
|
// transects, then quits on its own). Skip the screenshot rounds so the game
|
|
// does not exit mid-test via bQuitWhenDone.
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("qaselftest")))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bWaitingForSettle)
|
|
{
|
|
// A follow shot tracks the pawn while it settles, otherwise the subject
|
|
// walks out of the frame between placement and capture.
|
|
if (CurrentShot < ShotFollow.Num() && ShotFollow[CurrentShot])
|
|
{
|
|
UpdateFollowCamera();
|
|
}
|
|
SettleCountdown -= DeltaSeconds;
|
|
if (SettleCountdown <= 0.f)
|
|
{
|
|
bWaitingForSettle = false;
|
|
// Use the stable custom name only (no list-index prefix): inserting or
|
|
// reordering cameras must not renumber files and orphan the old ones.
|
|
const FString Name = CurrentShot < ShotNames.Num()
|
|
? ShotNames[CurrentShot]
|
|
: FString::FromInt(CurrentShot);
|
|
const FString FileName = FString::Printf(TEXT("review-%s"), *Name);
|
|
// Resolution is the game window's; the runner passes -ResX/-ResY.
|
|
FScreenshotRequest::RequestScreenshot(FileName, /*bInShowUI=*/true, false);
|
|
bWaitingForScreenshot = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (bWaitingForScreenshot && !FScreenshotRequest::IsScreenshotRequested())
|
|
{
|
|
bWaitingForScreenshot = false;
|
|
UE_LOG(LogTemp, Log, TEXT("GigLineShotActor: captured %s"), *FScreenshotRequest::GetFilename());
|
|
// Walking shots move the pawn; put it back before the fixed-camera shots run.
|
|
if (ShotFollow.IsValidIndex(CurrentShot) && ShotFollow[CurrentShot])
|
|
{
|
|
if (AGigLineCharacter* Character = Cast<AGigLineCharacter>(UGameplayStatics::GetPlayerPawn(this, 0)))
|
|
{
|
|
Character->ReturnToStartForReview();
|
|
}
|
|
}
|
|
++CurrentShot;
|
|
NextShot();
|
|
}
|
|
}
|
|
|
|
void AGigLineShotActor::UpdateFollowCamera()
|
|
{
|
|
if (!CurrentCamera)
|
|
{
|
|
return;
|
|
}
|
|
APawn* Pawn = UGameplayStatics::GetPlayerPawn(this, 0);
|
|
if (!Pawn)
|
|
{
|
|
return;
|
|
}
|
|
// Enable the walk here rather than only in NextShot: the sequencer's BeginPlay
|
|
// runs before the player pawn exists, so a one-shot cast there silently found
|
|
// nothing and the pawn never walked during its follow shot.
|
|
if (AGigLineCharacter* Character = Cast<AGigLineCharacter>(Pawn))
|
|
{
|
|
Character->SetReviewWalk(true);
|
|
// Park the walk clip at a quarter cycle the first frame of the shot, so the
|
|
// capture lands mid-stride. Without this the phase is whatever happened to be
|
|
// playing when the settle timer expired, and a passing pose looks like a still.
|
|
if (!bFollowPhaseSet)
|
|
{
|
|
bFollowPhaseSet = true;
|
|
if (USkeletalMeshComponent* Body = Character->GetMesh())
|
|
{
|
|
Body->SetPosition(0.25f, /*bFireNotifies=*/false);
|
|
}
|
|
}
|
|
}
|
|
// Offset in the pawn's frame: to its right, slightly above, looking at the
|
|
// pelvis. Right-of-pawn gives a profile view, which is where a stride reads.
|
|
const FVector Right = Pawn->GetActorRightVector();
|
|
const FVector Origin = Pawn->GetActorLocation();
|
|
const FVector CameraLocation = Origin + Right * 420.f + FVector(0.f, 0.f, 90.f);
|
|
const FVector Target = Origin + FVector(0.f, 0.f, 40.f);
|
|
CurrentCamera->SetActorLocation(CameraLocation);
|
|
CurrentCamera->SetActorRotation((Target - CameraLocation).Rotation());
|
|
}
|
|
|
|
void AGigLineShotActor::NextShot()
|
|
{
|
|
if (!PlayerController)
|
|
{
|
|
PlayerController = GetWorld()->GetFirstPlayerController();
|
|
}
|
|
|
|
if (CurrentShot >= ShotLocations.Num() || !PlayerController)
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("GigLineShotActor: done (%d shots)"), CurrentShot);
|
|
if (bQuitWhenDone)
|
|
{
|
|
PlayerController->ConsoleCommand(TEXT("quit"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (CurrentCamera)
|
|
{
|
|
CurrentCamera->Destroy();
|
|
}
|
|
|
|
FActorSpawnParameters Params;
|
|
Params.ObjectFlags |= RF_Transient;
|
|
CurrentCamera = GetWorld()->SpawnActor<ACameraActor>(
|
|
ACameraActor::StaticClass(), ShotLocations[CurrentShot], ShotRotations[CurrentShot], Params);
|
|
if (ShotFieldOfView.IsValidIndex(CurrentShot) && ShotFieldOfView[CurrentShot] > 0.0f)
|
|
{
|
|
if (UCameraComponent* Cam = CurrentCamera->GetCameraComponent())
|
|
{
|
|
// Long lens for elevations: fills the frame from across the street
|
|
// without pitching the camera, so verticals stay parallel.
|
|
Cam->SetFieldOfView(ShotFieldOfView[CurrentShot]);
|
|
}
|
|
}
|
|
// Follow shots ignore their recorded transform after this point: the camera is
|
|
// rebuilt from the pawn every frame until the capture. They also own the walking:
|
|
// the pawn only holds forward input while a follow shot is being taken, so the
|
|
// fixed-camera shots later in the list still find it at the spawn point.
|
|
const bool bFollowThisShot = ShotFollow.IsValidIndex(CurrentShot) && ShotFollow[CurrentShot];
|
|
bFollowPhaseSet = false;
|
|
if (AGigLineCharacter* Character = Cast<AGigLineCharacter>(UGameplayStatics::GetPlayerPawn(this, 0)))
|
|
{
|
|
Character->SetReviewWalk(bFollowThisShot);
|
|
}
|
|
if (bFollowThisShot)
|
|
{
|
|
UpdateFollowCamera();
|
|
}
|
|
|
|
PlayerController->SetViewTarget(CurrentCamera);
|
|
|
|
SettleCountdown = SettleSeconds;
|
|
bWaitingForSettle = true;
|
|
}
|