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

+31
View File
@@ -25,6 +25,20 @@ def log(msg):
lines.append(str(msg))
def ensure_writable(path):
# A packed .umap left read-only (git sets 644->444 on checkout, or the
# editor re-flags it) makes save_map silently return False with no error.
# chmod u+w it so the staged camera list and actors actually persist.
p = os.path.join(unreal.Paths.project_dir(), path.replace("/Game/", "Content/"))
try:
mode = os.stat(p).st_mode & 0o777
if mode & 0o200 == 0:
os.chmod(p, 0o644)
log(f"map writable: chmod u+w {p} (was {oct(mode)})")
except FileNotFoundError:
pass
def spawn(cls, loc, label=None, scale=None, rot=None):
a = unreal.EditorLevelLibrary.spawn_actor_from_class(cls, loc)
if a is None:
@@ -149,6 +163,18 @@ try:
else:
log("MISSING: /Script/GigLine.GigLineCharacter")
# --- NPC prefab -----------------------------------------------------------
# The reusable NPC person: a collision capsule + visible MPFB body, standing
# feet on Z 0, placed clear of the pawn so the prefab reads on its own.
npc = None
npc_class = unreal.load_class(None, "/Script/GigLine.GigLineNPCPerson")
if npc_class:
npc = spawn(npc_class, unreal.Vector(-4500, 300, 0), "NPC_Person")
if npc:
log("staged: NPC_Person (AGigLineNPCPerson) at Z 0")
else:
log("MISSING: /Script/GigLine.GigLineNPCPerson")
# --- camera list ---------------------------------------------------------
# Every camera is a (name, location, target) triple; the look-at rotation is
# computed from the pair so a camera cannot drift out of aim when one number
@@ -162,6 +188,9 @@ try:
("scale-cubes", unreal.Vector(0, -900, 200), unreal.Vector(0, 600, 80)),
# the pawn the game spawns, with the grounded cube beside it
("player-placement", unreal.Vector(320, -520, 140), unreal.Vector(0, 0, 80)),
# the NPC prefab: capsule + visible body, feet on Z 0, at double scale
# distance because it stands off to the west of the main staging
("npc-prefab", unreal.Vector(-4200, 600, 160), unreal.Vector(-4500, 300, 90)),
# low, close on feet and cube base: ground contact must be obvious
("ground-contact", unreal.Vector(180, -260, 35), unreal.Vector(0, 40, 20)),
# shadow comparison, low angle so both shadows read
@@ -214,6 +243,7 @@ try:
# quit after the demo shots so a dedicated DemoMap capture run
# terminates cleanly (render_screenshots.sh runs both maps).
seq.set_editor_property("quit_when_done", True)
ensure_writable("/Game/Hollywood/DemoMap")
saved = unreal.EditorLoadingAndSavingUtils.save_map(
unreal.EditorLevelLibrary.get_editor_world(), "/Game/Hollywood/DemoMap")
log(f"demo map staged: {len(demo_shots)} cameras, save {saved}")
@@ -239,6 +269,7 @@ except Exception as exc: # noqa
# --- save --------------------------------------------------------------------
try:
ensure_writable(MAP_PATH)
world = unreal.EditorLevelLibrary.get_editor_world()
saved = unreal.EditorLoadingAndSavingUtils.save_map(world, MAP_PATH)
log(f"save_map {MAP_PATH}: {saved}")