From 370953097745b0e5d841bab3ebf63b664c8b9aa6 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sat, 7 Nov 2026 20:12:19 +0100 Subject: [PATCH] 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. --- Game/GigLine/Config/DefaultGame.ini | 9 ++ .../Source/GigLine/GigLineCharacter.cpp | 92 ++++++++++++------- Tools/ue_build_demo_map.py | 28 ++++-- 3 files changed, 84 insertions(+), 45 deletions(-) diff --git a/Game/GigLine/Config/DefaultGame.ini b/Game/GigLine/Config/DefaultGame.ini index 8f9867d..6924d0a 100644 --- a/Game/GigLine/Config/DefaultGame.ini +++ b/Game/GigLine/Config/DefaultGame.ini @@ -7,6 +7,15 @@ CopyrightNotice= BuildConfiguration=PPBC_Development FullRebuild=False 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] ; Temporary default while the demo map is being built; GameMapsSettings in diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index d294545..ee85116 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -9,6 +9,9 @@ #include "InputAction.h" #include "GameFramework/PlayerController.h" #include "InputMappingContext.h" +#include "Components/CapsuleComponent.h" +#include "Engine/World.h" +#include "TimerManager.h" AGigLineCharacter::AGigLineCharacter() { @@ -39,51 +42,56 @@ AGigLineCharacter::AGigLineCharacter() GetMesh()->SetHiddenInGame(true); GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision); - // Input assets are created by Tools/ue_create_input_assets.py. They are loaded in - // BeginPlay rather than the constructor: the constructor runs during CDO - // construction where asset loads are unreliable, and a missing asset should be - // a loud warning, not a silent no-op. -} + // Hard-reference the Enhanced Input assets with ConstructorHelpers so the + // cooker always includes them and they load during CDO construction. Runtime + // soft-loading them at BeginPlay was fragile: the assets were pruned from the + // 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 CtxFinder( + TEXT("/Game/Input/IMC_Default.IMC_Default")); + static ConstructorHelpers::FObjectFinder MoveF( + TEXT("/Game/Input/IA_Move.IA_Move")); + static ConstructorHelpers::FObjectFinder StrafeF( + TEXT("/Game/Input/IA_Strafe.IA_Strafe")); + static ConstructorHelpers::FObjectFinder BackF( + TEXT("/Game/Input/IA_MoveBack.IA_MoveBack")); + static ConstructorHelpers::FObjectFinder LeftF( + TEXT("/Game/Input/IA_StrafeLeft.IA_StrafeLeft")); + static ConstructorHelpers::FObjectFinder LookF( + TEXT("/Game/Input/IA_Look.IA_Look")); + static ConstructorHelpers::FObjectFinder JumpF( + TEXT("/Game/Input/IA_Jump.IA_Jump")); + static ConstructorHelpers::FObjectFinder ResetF( + TEXT("/Game/Input/IA_Reset.IA_Reset")); + static ConstructorHelpers::FObjectFinder PauseF( + TEXT("/Game/Input/IA_Pause.IA_Pause")); -void AGigLineCharacter::LoadInputAssets() -{ - auto LoadSoft = [](const TCHAR* Path) -> TObjectPtr - { - return LoadObject(nullptr, Path); - }; - MappingContext = Cast(LoadSoft(TEXT("/Game/Input/IMC_Default.IMC_Default"))); - MoveAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Move.IA_Move"))); - StrafeAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Strafe.IA_Strafe"))); - MoveBackAction = Cast(LoadSoft(TEXT("/Game/Input/IA_MoveBack.IA_MoveBack"))); - StrafeLeftAction = Cast(LoadSoft(TEXT("/Game/Input/IA_StrafeLeft.IA_StrafeLeft"))); - LookAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Look.IA_Look"))); - JumpAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Jump.IA_Jump"))); - ResetAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Reset.IA_Reset"))); - PauseAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Pause.IA_Pause"))); + MappingContext = CtxFinder.Object; + MoveAction = MoveF.Object; + StrafeAction = StrafeF.Object; + MoveBackAction = BackF.Object; + StrafeLeftAction = LeftF.Object; + LookAction = LookF.Object; + JumpAction = JumpF.Object; + ResetAction = ResetF.Object; + PauseAction = PauseF.Object; if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction || !LookAction) { UE_LOG(LogTemp, Warning, - TEXT("GigLine: input assets missing (context=%s move=%s look=%s). ") - 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")); + TEXT("GigLine: input assets failed to load at construction. ") + TEXT("Run Tools/ue_create_input_assets.py to build /Game/Input.")); } } -void AGigLineCharacter::BeginPlay() +void AGigLineCharacter::LoadInputAssets() { - Super::BeginPlay(); - - LoadInputAssets(); - - StartLocation = GetActorLocation(); - StartRotation = GetActorRotation(); - - // Push the mapping context for this local player. + // 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 + // pawn has been possessed and its PlayerController exists. if (MappingContext) { if (const APlayerController* PC = Cast(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) { Super::SetupPlayerInputComponent(PlayerInputComponent); diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 5a9b9d8..0bda350 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -146,7 +146,7 @@ def build(): log(f"could not create {MAP}") return - removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_")) + removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_", "ShotSequencer")) log(f"cleared {removed} previous demo actors") # --- lighting: fixed daytime ------------------------------------------ @@ -256,7 +256,7 @@ def build(): actor = spawn(unreal.StaticMeshActor, unreal.Vector(-13336.0, 1307.0, 0.0), "Hero_Facade") if actor: 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") else: log("MISSING: SM_HeroFacade — run Tools/ue_hero_facade_import.py") @@ -303,11 +303,18 @@ def build(): continue actor = spawn(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), f"Demo_{name}") if actor: - actor.static_mesh_component.set_static_mesh(mesh) - actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE) + comp = actor.static_mesh_component + 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) if mat: - actor.static_mesh_component.set_material(0, mat) + comp.set_material(0, mat) imported += 1 log(f"staged {imported} site meshes with explicit surface materials") @@ -362,11 +369,12 @@ def build(): # --- player start: north sidewalk, facing the hero frontage ------------- # 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 - # sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447. The - # facade stands at Y -1450. Stand on the north sidewalk and look north (-Y). - # North sidewalk (Y -1912..-1053), facing south (+Y) across the road at the - # hero on the south side. Yaw 90 looks along +Y. - spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 120.0), "Demo_PlayerStart", + # sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447. + # The hero facade is on the SOUTH side at Y +1307. Stand on the north + # sidewalk and face south (+Y, yaw 90) across the road at it. + # Z 200 is clear air so the capsule settles onto the pavement instead of + # 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)) log("staged: Demo_PlayerStart on the north sidewalk facing south (+Y)")