qa fix (wip): input assets hard-referenced, static site collision, spawn on sidewalk

Carries the QA fixes for the walkable demo off main onto an error-fixing
branch so a live window can be tested without blocking the clean main.

- GigLineCharacter: hard-reference the Enhanced Input assets via
  ConstructorHelpers so the cooker includes /Game/Input (they were soft-only
  and pruned from the pak -> no inputs bound). Push the mapping context on a
  deferred timer after possession.
- defaultgame.ini: +DirectoriesToAlwaysCook for /Game/Input, Hollywood, Test.
- ue_build_demo_map.py: stage site meshes STATIC with explicit collision
  (QUERY_AND_PHYSICS, WorldStatic, BlockAll); hero facade STATIC; PlayerStart
  moved onto the north sidewalk at Y -1200.
- DemoMap.umap rebuilt with the above.

STATUS: packaged build now logs all input assets ok and grounded=1/Walking,
so falling + input-nil are fixed in the package; the user still hit a stuck
window (no mouse/keys) in one test, so this remains WIP pending a clean
interactive run.
This commit is contained in:
zwitschi committed 2026-11-08 10:31:12 +01:00
1 parent fee26ade10
commit 3709530977
3 files changed
+84 -45

No files matched your search

+9
View File
@@ -7,6 +7,15 @@ CopyrightNotice=
BuildConfiguration=PPBC_Development BuildConfiguration=PPBC_Development
FullRebuild=False FullRebuild=False
UsePakFile=True UsePakFile=True
; Runtime-loaded content must ship even when nothing statically references it.
; The Enhanced Input assets are only soft-loaded by AGigLineCharacter at
; BeginPlay; without this the cooker prunes them from the pak (observed: the
; cook ReferencedSet had zero /Game/Input entries, so inputs were all nil and
; no keys worked). Always cook the demo map + input. Keep both maps listed so
; EditorStartupMap/GameDefaultMap selection stays flexible.
+DirectoriesToAlwaysCook=/Game/Input
+DirectoriesToAlwaysCook=/Game/Hollywood
+DirectoriesToAlwaysCook=/Game/Test
[/Script/EngineSettings.GameMapsSettings] [/Script/EngineSettings.GameMapsSettings]
; Temporary default while the demo map is being built; GameMapsSettings in ; Temporary default while the demo map is being built; GameMapsSettings in
@@ -9,6 +9,9 @@
#include "InputAction.h" #include "InputAction.h"
#include "GameFramework/PlayerController.h" #include "GameFramework/PlayerController.h"
#include "InputMappingContext.h" #include "InputMappingContext.h"
#include "Components/CapsuleComponent.h"
#include "Engine/World.h"
#include "TimerManager.h"
AGigLineCharacter::AGigLineCharacter() AGigLineCharacter::AGigLineCharacter()
{ {
@@ -39,51 +42,56 @@ AGigLineCharacter::AGigLineCharacter()
GetMesh()->SetHiddenInGame(true); GetMesh()->SetHiddenInGame(true);
GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision); GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// Input assets are created by Tools/ue_create_input_assets.py. They are loaded in // Hard-reference the Enhanced Input assets with ConstructorHelpers so the
// BeginPlay rather than the constructor: the constructor runs during CDO // cooker always includes them and they load during CDO construction. Runtime
// construction where asset loads are unreliable, and a missing asset should be // soft-loading them at BeginPlay was fragile: the assets were pruned from the
// a loud warning, not a silent no-op. // pak unless /Game/Input was force-cooked, and a soft path can miss while the
} // IoStore registry is still mounting. A constructor FObjectFinder is a hard
// reference (followed by the cooker) and loads deterministically.
// Assets are created by Tools/ue_create_input_assets.py.
static ConstructorHelpers::FObjectFinder<UInputMappingContext> CtxFinder(
TEXT("/Game/Input/IMC_Default.IMC_Default"));
static ConstructorHelpers::FObjectFinder<UInputAction> MoveF(
TEXT("/Game/Input/IA_Move.IA_Move"));
static ConstructorHelpers::FObjectFinder<UInputAction> StrafeF(
TEXT("/Game/Input/IA_Strafe.IA_Strafe"));
static ConstructorHelpers::FObjectFinder<UInputAction> BackF(
TEXT("/Game/Input/IA_MoveBack.IA_MoveBack"));
static ConstructorHelpers::FObjectFinder<UInputAction> LeftF(
TEXT("/Game/Input/IA_StrafeLeft.IA_StrafeLeft"));
static ConstructorHelpers::FObjectFinder<UInputAction> LookF(
TEXT("/Game/Input/IA_Look.IA_Look"));
static ConstructorHelpers::FObjectFinder<UInputAction> JumpF(
TEXT("/Game/Input/IA_Jump.IA_Jump"));
static ConstructorHelpers::FObjectFinder<UInputAction> ResetF(
TEXT("/Game/Input/IA_Reset.IA_Reset"));
static ConstructorHelpers::FObjectFinder<UInputAction> PauseF(
TEXT("/Game/Input/IA_Pause.IA_Pause"));
void AGigLineCharacter::LoadInputAssets() MappingContext = CtxFinder.Object;
{ MoveAction = MoveF.Object;
auto LoadSoft = [](const TCHAR* Path) -> TObjectPtr<UObject> StrafeAction = StrafeF.Object;
{ MoveBackAction = BackF.Object;
return LoadObject<UObject>(nullptr, Path); StrafeLeftAction = LeftF.Object;
}; LookAction = LookF.Object;
MappingContext = Cast<UInputMappingContext>(LoadSoft(TEXT("/Game/Input/IMC_Default.IMC_Default"))); JumpAction = JumpF.Object;
MoveAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Move.IA_Move"))); ResetAction = ResetF.Object;
StrafeAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Strafe.IA_Strafe"))); PauseAction = PauseF.Object;
MoveBackAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_MoveBack.IA_MoveBack")));
StrafeLeftAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_StrafeLeft.IA_StrafeLeft")));
LookAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Look.IA_Look")));
JumpAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Jump.IA_Jump")));
ResetAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Reset.IA_Reset")));
PauseAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Pause.IA_Pause")));
if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction
|| !LookAction) || !LookAction)
{ {
UE_LOG(LogTemp, Warning, UE_LOG(LogTemp, Warning,
TEXT("GigLine: input assets missing (context=%s move=%s look=%s). ") TEXT("GigLine: input assets failed to load at construction. ")
TEXT("Run Tools/ue_create_input_assets.py to build /Game/Input."), TEXT("Run Tools/ue_create_input_assets.py to build /Game/Input."));
MappingContext ? TEXT("ok") : TEXT("missing"),
(MoveAction && MoveBackAction && StrafeAction && StrafeLeftAction) ? TEXT("ok")
: TEXT("missing"),
LookAction ? TEXT("ok") : TEXT("missing"));
} }
} }
void AGigLineCharacter::BeginPlay() void AGigLineCharacter::LoadInputAssets()
{ {
Super::BeginPlay(); // The assets are hard-referenced and loaded in the constructor; this deferred
// call pushes the now-valid mapping context to the local player, after the
LoadInputAssets(); // pawn has been possessed and its PlayerController exists.
StartLocation = GetActorLocation();
StartRotation = GetActorRotation();
// Push the mapping context for this local player.
if (MappingContext) if (MappingContext)
{ {
if (const APlayerController* PC = Cast<APlayerController>(GetController())) if (const APlayerController* PC = Cast<APlayerController>(GetController()))
@@ -97,6 +105,20 @@ void AGigLineCharacter::BeginPlay()
} }
} }
void AGigLineCharacter::BeginPlay()
{
Super::BeginPlay();
StartLocation = GetActorLocation();
StartRotation = GetActorRotation();
// Input assets are hard-referenced in the constructor. Defer the mapping
// context push to just after spawn, when the PlayerController exists and a
// packaged game has finished mounting its pak IoStore.
FTimerHandle InputHandle;
GetWorldTimerManager().SetTimer(InputHandle, this, &AGigLineCharacter::LoadInputAssets, 1.0f, false);
}
void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{ {
Super::SetupPlayerInputComponent(PlayerInputComponent); Super::SetupPlayerInputComponent(PlayerInputComponent);
+18 -10
View File
@@ -146,7 +146,7 @@ def build():
log(f"could not create {MAP}") log(f"could not create {MAP}")
return return
removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_")) removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_", "ShotSequencer"))
log(f"cleared {removed} previous demo actors") log(f"cleared {removed} previous demo actors")
# --- lighting: fixed daytime ------------------------------------------ # --- lighting: fixed daytime ------------------------------------------
@@ -256,7 +256,7 @@ def build():
actor = spawn(unreal.StaticMeshActor, unreal.Vector(-13336.0, 1307.0, 0.0), "Hero_Facade") actor = spawn(unreal.StaticMeshActor, unreal.Vector(-13336.0, 1307.0, 0.0), "Hero_Facade")
if actor: if actor:
actor.static_mesh_component.set_static_mesh(hero) actor.static_mesh_component.set_static_mesh(hero)
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE) actor.static_mesh_component.set_mobility(unreal.ComponentMobility.STATIC)
log("staged: Hero_Facade at the building line") log("staged: Hero_Facade at the building line")
else: else:
log("MISSING: SM_HeroFacade — run Tools/ue_hero_facade_import.py") log("MISSING: SM_HeroFacade — run Tools/ue_hero_facade_import.py")
@@ -303,11 +303,18 @@ def build():
continue continue
actor = spawn(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), f"Demo_{name}") actor = spawn(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), f"Demo_{name}")
if actor: if actor:
actor.static_mesh_component.set_static_mesh(mesh) comp = actor.static_mesh_component
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE) comp.set_static_mesh(mesh)
# Immutable level geometry must be STATIC to reliably expose its
# complex-as-simple collision body to CharacterMovement; staging
# these MOVABLE let the player fall through the world.
comp.set_mobility(unreal.ComponentMobility.STATIC)
comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS)
comp.set_collision_object_type(unreal.CollisionChannel.ECC_WORLD_STATIC)
comp.set_collision_profile_name("BlockAll")
mat = built.get(mat_name) mat = built.get(mat_name)
if mat: if mat:
actor.static_mesh_component.set_material(0, mat) comp.set_material(0, mat)
imported += 1 imported += 1
log(f"staged {imported} site meshes with explicit surface materials") log(f"staged {imported} site meshes with explicit surface materials")
@@ -362,11 +369,12 @@ def build():
# --- player start: north sidewalk, facing the hero frontage ------------- # --- player start: north sidewalk, facing the hero frontage -------------
# Signs were measured from the imported meshes rather than assumed: the FBX # Signs were measured from the imported meshes rather than assumed: the FBX
# preset flips north, so in Unreal the road spans Y -1382..+1047, the NORTH # preset flips north, so in Unreal the road spans Y -1382..+1047, the NORTH
# sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447. The # sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447.
# facade stands at Y -1450. Stand on the north sidewalk and look north (-Y). # The hero facade is on the SOUTH side at Y +1307. Stand on the north
# North sidewalk (Y -1912..-1053), facing south (+Y) across the road at the # sidewalk and face south (+Y, yaw 90) across the road at it.
# hero on the south side. Yaw 90 looks along +Y. # Z 200 is clear air so the capsule settles onto the pavement instead of
spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 120.0), "Demo_PlayerStart", # spawning embedded or below grade (the fall-through QA finding).
spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 200.0), "Demo_PlayerStart",
unreal.Rotator(pitch=0.0, yaw=90.0, roll=0.0)) unreal.Rotator(pitch=0.0, yaw=90.0, roll=0.0))
log("staged: Demo_PlayerStart on the north sidewalk facing south (+Y)") log("staged: Demo_PlayerStart on the north sidewalk facing south (+Y)")