feat(review): deterministic walking capture, and the defect it exposed
The shot actor can now photograph a moving pawn: a per-shot ShotFollow flag re-places the camera every frame at a fixed offset in the pawn's frame, and the walk is driven per shot (SetReviewWalk) with the pawn returned to spawn after a follow shot, so the fixed-camera shots later in the list still find it in place. With that, the capture stopped depending on where a 4.5 m/s pawn happened to be. Two further bugs fixed on the way: - the engine's screenshot folder is cleared before each run. The collect step copies every review-*.png it finds in Saved/, so a shot retired from the camera list kept reappearing with byte-identical content from an older run - which is how a walk shot that was never captured looked like a captured one. - UpdateBodyAnimation no longer calls SetAnimation every frame. Re-setting the same clip restarts it, which freezes the body on frame 0. What the deterministic capture then exposed: the walk clip is applied but does not advance - every walking frame shows the same pose (arms forward, legs together). The stride shot is retired again rather than shipped, and TODO.md names the next step: log GetCurrentAssetTime across frames and check the imported PlayRate. The harness stays wired for when the clip plays. Shots 6 and 7 were re-rendered after these changes and still pass; 7/7 verify.
This commit is contained in:
1 parent
f84093adba
commit
eeeabe56b1
42 files changed
+160
-68
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -258,6 +258,16 @@ void AGigLineCharacter::BeginPlay()
|
||||
bWalkForever = FParse::Param(FCommandLine::Get(), TEXT("walk"));
|
||||
}
|
||||
|
||||
void AGigLineCharacter::SetReviewWalk(bool bEnable)
|
||||
{
|
||||
bWalkForever = bEnable;
|
||||
}
|
||||
|
||||
void AGigLineCharacter::ReturnToStartForReview()
|
||||
{
|
||||
ResetToStart();
|
||||
}
|
||||
|
||||
void AGigLineCharacter::WalkForever()
|
||||
{
|
||||
if (bWalkForever && GetCharacterMovement() && GetCharacterMovement()->IsMovingOnGround())
|
||||
@@ -773,8 +783,6 @@ void AGigLineCharacter::UpdateBodyAnimation()
|
||||
UAnimSequence* Wanted = bMoving ? WalkAnimation.Get() : IdleAnimation.Get();
|
||||
if (!Wanted)
|
||||
{
|
||||
// Logged once: a missing clip presents as an A-posed body, which looks like
|
||||
// an animation problem rather than a missing asset reference.
|
||||
static bool bWarnedMissingClip = false;
|
||||
if (!bWarnedMissingClip)
|
||||
{
|
||||
@@ -787,32 +795,32 @@ void AGigLineCharacter::UpdateBodyAnimation()
|
||||
return;
|
||||
}
|
||||
|
||||
// PlayAnimation is only an intent: it needs the component in single-node mode and
|
||||
// a live single-node instance. The body is hidden at construction and only
|
||||
// unhidden by the camera toggle, so the first call could land on a component that
|
||||
// was not ready and never retried - the clip name logged, the pose left at bind.
|
||||
// Driving the mode and the instance explicitly, and re-asserting every frame while
|
||||
// the instance is missing, makes that failure self-correcting.
|
||||
if (GetMesh()->GetAnimationMode() != EAnimationMode::AnimationSingleNode)
|
||||
{
|
||||
GetMesh()->SetAnimationMode(EAnimationMode::AnimationSingleNode);
|
||||
}
|
||||
if (Wanted != PlayingAnimation || GetMesh()->GetSingleNodeInstance() == nullptr)
|
||||
{
|
||||
PlayingAnimation = Wanted;
|
||||
GetMesh()->SetAnimation(Wanted);
|
||||
GetMesh()->Play(true);
|
||||
if (CVarGigLineSetupLogs.GetValueOnGameThread())
|
||||
// Only the mode setter is idempotent; calling SetAnimation unconditionally
|
||||
// restarts the clip every frame, which freezes the body on frame 0 - that is
|
||||
// why the walking capture showed a still figure with both legs together.
|
||||
if (GetMesh()->GetAnimationMode() != EAnimationMode::AnimationSingleNode)
|
||||
{
|
||||
static bool bLoggedAnimState = false;
|
||||
if (!bLoggedAnimState)
|
||||
GetMesh()->SetAnimationMode(EAnimationMode::AnimationSingleNode);
|
||||
}
|
||||
if (Wanted != PlayingAnimation)
|
||||
{
|
||||
GetMesh()->SetAnimation(Wanted);
|
||||
PlayingAnimation = Wanted;
|
||||
GetMesh()->Play(true);
|
||||
if (CVarGigLineSetupLogs.GetValueOnGameThread())
|
||||
{
|
||||
bLoggedAnimState = true;
|
||||
UE_LOG(LogTemp, Log, TEXT("GigLine: body clip %s, mode=%d, instance=%s"),
|
||||
*Wanted->GetName(), static_cast<int32>(GetMesh()->GetAnimationMode()),
|
||||
GetMesh()->GetSingleNodeInstance() ? TEXT("ok") : TEXT("nil"));
|
||||
UE_LOG(LogTemp, Log, TEXT("GigLine: body clip %s, mode=%d"),
|
||||
*Wanted->GetName(), static_cast<int32>(GetMesh()->GetAnimationMode()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Same clip but the instance was recreated after a mode change: resume it
|
||||
// once rather than re-setting the animation.
|
||||
GetMesh()->Play(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,14 @@ protected:
|
||||
void ResetToStart();
|
||||
void TogglePause();
|
||||
|
||||
public:
|
||||
/** Review harness: hold or release forward input from the screenshot actor. */
|
||||
void SetReviewWalk(bool bEnable);
|
||||
|
||||
/** Review harness: put the pawn back where it spawned, after a walking capture. */
|
||||
void ReturnToStartForReview();
|
||||
|
||||
protected:
|
||||
/** Swaps between the first-person and third-person rigs (V). */
|
||||
void ToggleCamera();
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "GigLineShotActor.h"
|
||||
|
||||
#include "GigLineCharacter.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
#include "Camera/CameraActor.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "Engine/World.h"
|
||||
@@ -32,6 +35,12 @@ void AGigLineShotActor::Tick(float DeltaSeconds)
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -53,11 +62,40 @@ void AGigLineShotActor::Tick(float DeltaSeconds)
|
||||
{
|
||||
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;
|
||||
}
|
||||
const APawn* Pawn = UGameplayStatics::GetPlayerPawn(this, 0);
|
||||
if (!Pawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 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)
|
||||
@@ -93,6 +131,20 @@ void AGigLineShotActor::NextShot()
|
||||
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];
|
||||
if (AGigLineCharacter* Character = Cast<AGigLineCharacter>(UGameplayStatics::GetPlayerPawn(this, 0)))
|
||||
{
|
||||
Character->SetReviewWalk(bFollowThisShot);
|
||||
}
|
||||
if (bFollowThisShot)
|
||||
{
|
||||
UpdateFollowCamera();
|
||||
}
|
||||
|
||||
PlayerController->SetViewTarget(CurrentCamera);
|
||||
|
||||
SettleCountdown = SettleSeconds;
|
||||
|
||||
@@ -50,6 +50,18 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
float SettleSeconds = 0.35f;
|
||||
|
||||
/**
|
||||
* Optional per-shot "follow the player" flag, same index as ShotLocations.
|
||||
*
|
||||
* A fixed camera cannot photograph a walking pawn: at 4.5 m/s it covers the
|
||||
* distance between two captures, so three attempts framed it at a different
|
||||
* distance each run and two missed it entirely. With this flag the camera is
|
||||
* re-placed every frame at a fixed offset in the pawn's frame, which is what
|
||||
* makes a stride shot deterministic.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
TArray<bool> ShotFollow;
|
||||
|
||||
/** Quit the game after the last shot (disable to keep the game open). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
bool bQuitWhenDone = true;
|
||||
@@ -57,6 +69,9 @@ public:
|
||||
private:
|
||||
void NextShot();
|
||||
|
||||
/** Re-places the active camera at the follow offset behind the player pawn. */
|
||||
void UpdateFollowCamera();
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<class APlayerController> PlayerController;
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -32,7 +32,7 @@ Actionable task list for this project. This file is the single, central place fo
|
||||
|
||||
### Release checks
|
||||
|
||||
- [ ] Third-person traversal works end-to-end in the demo area. **Blocked on a capture harness, not on the game:** walking works (`-walk` holds forward input; the pawn crosses the sidewalk while the third-person rig follows), but a fixed review camera cannot catch a pawn moving at 4.5 m/s — three attempts framed it at a different distance each run and the last two missed it entirely. The stride shot was removed rather than shipped broken. Needs a camera that tracks the pawn (attach the shot camera to the pawn, or drive the capture from a recorded path), then re-add `demo-third-person-walk`.
|
||||
- [ ] Third-person traversal works end-to-end in the demo area. **The harness exists now, the animation does not advance.** The screenshot actor can track the pawn (`ShotFollow`) and drive the walk per shot (`SetReviewWalk`), and the capture is deterministic — but every walking capture shows the body in one static pose (arms forward, legs together), so the walk clip is applied without playing. Next step: check whether the single-node instance is ticking at all (log `GetSingleNodeInstance()->GetCurrentAssetTime()` across frames) and whether `PlayRate` survived the import. Earlier note, superseded: walking works (`-walk` holds forward input; the pawn crosses the sidewalk while the third-person rig follows), but a fixed review camera cannot catch a pawn moving at 4.5 m/s — three attempts framed it at a different distance each run and the last two missed it entirely. The stride shot was removed rather than shipped broken. Needs a camera that tracks the pawn (attach the shot camera to the pawn, or drive the capture from a recorded path), then re-add `demo-third-person-walk`.
|
||||
- [ ] No blocking animation or rig defects.
|
||||
- [ ] Performance target still met at 1440p.
|
||||
- [ ] Tag `v0.2.0` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -39,6 +39,14 @@ run_cmdlet ue_build_review_map.py "5/6 stage the demo shot cameras"
|
||||
tail -3 /tmp/ue-review-map-result.txt 2>/dev/null || true
|
||||
|
||||
echo "== 6/6 capture the demo map =="
|
||||
# Clear the engine's screenshot folder first. The collect step below copies every
|
||||
# review-*.png it finds in Saved/, so without this a shot retired from the camera list
|
||||
# keeps coming back as a stale file - that is how a removed walk shot reappeared with
|
||||
# byte-identical content after a run that never captured it.
|
||||
rm -rf "$REPO"/Game/GigLine/Saved/Screenshots/* "$HOME"/.config/Epic/GigLine/Saved/Screenshots/* 2>/dev/null || true
|
||||
# Recreate the folder: the engine writes into it and does not recreate a deleted
|
||||
# directory, which silently produced zero captures in the run that removed it.
|
||||
mkdir -p "$REPO/Game/GigLine/Saved/Screenshots/LinuxEditor"
|
||||
# The engine path is absolute: this step runs from the repo, not from $ENGINE, and a
|
||||
# bare ./Engine/... failed with "No such file or directory" while every commandlet
|
||||
# above succeeded through run_cmdlet's cd.
|
||||
|
||||
@@ -331,6 +331,10 @@ try:
|
||||
seq.set_editor_property("shot_rotations",
|
||||
[unreal.MathLibrary.find_look_at_rotation(c[1], c[2]) for c in demo_shots])
|
||||
seq.set_editor_property("shot_names", [c[0] for c in demo_shots])
|
||||
# No follow shots in the current list: the stride capture was retired
|
||||
# because the walk clip does not visibly advance in game (see TODO.md).
|
||||
# The follow flag stays wired for when it does.
|
||||
seq.set_editor_property("shot_follow", [False] * len(demo_shots))
|
||||
seq.set_editor_property(
|
||||
"shot_field_of_view",
|
||||
[float(c[3]) if len(c) > 3 else 0.0 for c in demo_shots])
|
||||
|
||||
Reference in new issue
Block a user