#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(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(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::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(UGameplayStatics::GetPlayerPawn(this, 0))) { Character->SetReviewWalk(bFollowThisShot); } if (bFollowThisShot) { UpdateFollowCamera(); } PlayerController->SetViewTarget(CurrentCamera); SettleCountdown = SettleSeconds; bWaitingForSettle = true; }