tools: visual-review screenshot pipeline

- AGigLineShotActor (dev-only): walks a fixed camera list in -game mode,
  captures via FScreenshotRequest, quits. Test content only.
- ue_build_review_map.py builds /Game/Test/ReviewMap from current content
  (gray floor, key light, sky, MPFB character, 1 m scale cubes).
- render_screenshots.sh: build map -> run game -> collect PNGs into
  gitignored Screenshots/.
- Screenshots/README.md (committed) describes each capture with approval
  state; AGENTS.md gains the visual-review instructions.
This commit is contained in:
zwitschi committed 2026-11-07 09:52:10 +01:00
1 parent 774d810dab
commit bbb3e147b3
12 files changed
+525 -1

No files matched your search

@@ -0,0 +1,78 @@
#include "GigLineShotActor.h"
#include "Camera/CameraActor.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);
if (bWaitingForSettle)
{
SettleCountdown -= DeltaSeconds;
if (SettleCountdown <= 0.f)
{
bWaitingForSettle = false;
const FString FileName = FString::Printf(TEXT("review-%02d-%s"), CurrentShot,
CurrentShot < ShotNames.Num() ? *ShotNames[CurrentShot] : *FString::FromInt(CurrentShot));
// Resolution is the game window's; the runner passes -ResX/-ResY.
FScreenshotRequest::RequestScreenshot(FileName, false, false);
bWaitingForScreenshot = true;
}
return;
}
if (bWaitingForScreenshot && !FScreenshotRequest::IsScreenshotRequested())
{
bWaitingForScreenshot = false;
UE_LOG(LogTemp, Log, TEXT("GigLineShotActor: captured %s"), *FScreenshotRequest::GetFilename());
++CurrentShot;
NextShot();
}
}
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);
PlayerController->SetViewTarget(CurrentCamera);
SettleCountdown = SettleSeconds;
bWaitingForSettle = true;
}
@@ -0,0 +1,60 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GigLineShotActor.generated.h"
/**
* Dev-only helper for the visual-review screenshot pipeline.
*
* Placed in the review map by Tools/ue_build_review_map.py. In -game mode it
* walks a fixed list of camera setups, sets the view target to a spawned
* CameraActor for each, requests a screenshot via FScreenshotRequest, and
* quits the game when all shots are written. Lives in test content only;
* the shipped game never references this map.
*/
UCLASS()
class GIGLINE_API AGigLineShotActor : public AActor
{
GENERATED_BODY()
public:
AGigLineShotActor();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
/** World-space locations for each shot camera. */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<FVector> ShotLocations;
/** Rotations for each shot camera (same index as ShotLocations). */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<FRotator> ShotRotations;
/** File name fragment for each shot (same index as ShotLocations). */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<FString> ShotNames;
/** Seconds to let the view target settle before the capture. Resolution comes from the runner's -ResX/-ResY. */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float SettleSeconds = 0.35f;
/** Quit the game after the last shot (disable to keep the game open). */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bQuitWhenDone = true;
private:
void NextShot();
UPROPERTY()
TObjectPtr<class APlayerController> PlayerController;
UPROPERTY()
TObjectPtr<class ACameraActor> CurrentCamera;
int32 CurrentShot = 0;
float SettleCountdown = 0.f;
bool bWaitingForSettle = false;
bool bWaitingForScreenshot = false;
};