npc: AGigLineNPCPerson prefab + stable-named screenshots

- New C++ AGigLineNPCPerson: reusable NPC/pedestrian prefab built on the MPFB
  human. ACharacter capsule half-height 83 cm (total 1.66 m, head top ~1.66 m
  so a 1.60 m camera doesn't clip), the character skeletal mesh on the built-in
  mesh as the visible body (feet on Z 0), AutoPossessAI for 0.5.0 pedestrians.
  Staged at Z 0 in the review scene; captured as review-npc-prefab.png.
- AGigLineShotActor now names screenshots by stable camera name (no list-index
  prefix): inserting or reordering cameras no longer renumbers files or orphans
  the old ones. Re-rendered all review/demo shots with stable names.
- ue_build_review_map.py: chmod the target .umap before save_map (git marks maps
  444 on checkout, silently making save_map return False and dropping stages).
- Screenshots/README.md, ROADMAP, TODO updated for the prefab + stable names.
- DemoMap.umap rebuilt by the render pipeline (north buildings + demo cameras).
This commit is contained in:
zwitschi committed 2026-11-07 15:28:42 +01:00
1 parent cef87b3d31
commit 985dfcbac7
36 files changed
+178 -68

No files matched your search

@@ -0,0 +1,34 @@
#include "GigLineNPCPerson.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/SkeletalMesh.h"
AGigLineNPCPerson::AGigLineNPCPerson()
{
// The MPFB human is ~1.66 m tall, ~0.43 m wide. Half-height 83 cm leaves
// the head top at ~1.66 m, so a 1.60 m camera does not clip it. Radius
// ~35 cm accommodates the relaxed idle pose (arms at the sides).
GetCapsuleComponent()->InitCapsuleSize(35.0f, 83.0f);
GetCapsuleComponent()->SetRelativeLocation(FVector(0.0f, 0.0f, 83.0f));
// Visible body: the imported MPFB human on ACharacter's built-in mesh
// component, feet at Z 0 (capsule base lowered to the floor).
USkeletalMeshComponent* Mesh = GetMesh();
Mesh->SetRelativeLocation(FVector(0.0f, 0.0f, -83.0f));
Mesh->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f));
Mesh->SetCastShadow(true);
static ConstructorHelpers::FObjectFinder<USkeletalMesh> MeshAsset(
TEXT("/Game/Test/Character/character"));
if (MeshAsset.Succeeded())
{
Mesh->SetSkeletalMeshAsset(MeshAsset.Object);
Mesh->SetSkeletalMesh(MeshAsset.Object);
}
// An NPC pawn has no player input; hand it to AI when it is spawned into
// the world (the 0.5.0 pedestrian navigation will drive it).
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
bUseControllerRotationYaw = false;
}
@@ -0,0 +1,32 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "GigLineNPCPerson.generated.h"
/**
* Reusable NPC/pedestrian prefab built on the MPFB human asset.
*
* Deliberately NOT the 0.1.0 player (which is AGigLineCharacter, a capsule +
* camera). This is the visible-body template for future NPCs/pedestrians
* (0.5.0): ACharacter's collision capsule sized to the ~1.66 m MPFB human,
* with the character skeletal mesh on the built-in mesh component as the
* visible body, feet at Z 0.
*
* Head-height guarantee: capsule half-height 83 cm (total 1.66 m), so the head
* top sits at ~1.66 m; a camera/view at 1.60 m sits just below eye level and
* does not clip the head or neck. Set HeadHeightCm after observing the posed
* mesh if the eye socket lands elsewhere.
*/
UCLASS()
class GIGLINE_API AGigLineNPCPerson : public ACharacter
{
GENERATED_BODY()
public:
AGigLineNPCPerson();
/** Observed head height (cm above Z 0), used to verify camera clearance. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "NPC")
float HeadHeightCm = 155.f;
};
@@ -27,8 +27,12 @@ void AGigLineShotActor::Tick(float 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));
// 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, false, false);
bWaitingForScreenshot = true;