Compare commits
13
Commits
v0.1.6
...
dd391ed6ff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd391ed6ff | ||
|
|
cdfb24a2b1 | ||
|
|
7930be38bb | ||
|
|
055944fd33 | ||
|
|
2bb1e74814 | ||
|
|
2bab0fb0cd | ||
|
|
2199a546bd | ||
|
|
ea16818e2a | ||
|
|
eb199ccce6 | ||
|
|
1cc487b916 | ||
|
|
f8c33ae697 | ||
|
|
c09352b4af | ||
|
|
3709530977 |
No files matched your search
+7
-1
@@ -46,9 +46,15 @@ Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \
|
|||||||
-archive -archivedirectory="/run/media/system/DATA/Builds/GigLine" \
|
-archive -archivedirectory="/run/media/system/DATA/Builds/GigLine" \
|
||||||
-unattended
|
-unattended
|
||||||
# Expected: "BUILD SUCCESSFUL" (~1 min for the blank project; archive ~1.1 GiB)
|
# Expected: "BUILD SUCCESSFUL" (~1 min for the blank project; archive ~1.1 GiB)
|
||||||
# Launch test: /run/media/system/DATA/Builds/GigLine/Linux/GigLine.sh
|
# Launch test (plain, no -map): /run/media/system/DATA/Builds/GigLine/Linux/GigLine.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Launch test must open the DemoMap.** A plain `./GigLine.sh` should load
|
||||||
|
`/Game/Hollywood/DemoMap` (set via `DefaultEngine.ini` `GameMapsSettings`). If it
|
||||||
|
instead opens `/Engine/Maps/Templates/OpenWorld`, the game shows an empty
|
||||||
|
non-interactive "stuck" scene — check that `GameDefaultMap` is in
|
||||||
|
`DefaultEngine.ini`, not `DefaultGame.ini`. Confirm the loaded map in the engine
|
||||||
|
log: `.../Linux/GigLine/Saved/Logs/GigLine.log` (`LoadMap: /Game/Hollywood/DemoMap`).
|
||||||
The archive directory is deliberately **outside** the repository; never archive into the checkout.
|
The archive directory is deliberately **outside** the repository; never archive into the checkout.
|
||||||
|
|
||||||
## Headless content checks
|
## Headless content checks
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
[/Script/EngineSettings.GameMapsSettings]
|
[/Script/EngineSettings.GameMapsSettings]
|
||||||
GlobalDefaultGameMode="/Script/GigLine.GigLineGameModeBase"
|
GlobalDefaultGameMode="/Script/GigLine.GigLineGameModeBase"
|
||||||
|
; The runnable map is the walkable demo, so a plain launch opens it (not the
|
||||||
|
; engine template nor the review staging scene).
|
||||||
|
GameDefaultMap=/Game/Hollywood/DemoMap
|
||||||
|
EditorStartupMap=/Game/Hollywood/DemoMap
|
||||||
|
ServerDefaultMap=/Game/Hollywood/DemoMap
|
||||||
|
TransitionMap=/Game/Hollywood/DemoMap
|
||||||
|
|
||||||
[/Script/Engine.RendererSettings]
|
[/Script/Engine.RendererSettings]
|
||||||
; Dynamic-only lighting. With static lighting allowed, the stationary sun and
|
; Dynamic-only lighting. With static lighting allowed, the stationary sun and
|
||||||
|
|||||||
@@ -7,9 +7,14 @@ 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]
|
|
||||||
; Temporary default while the demo map is being built; GameMapsSettings in
|
|
||||||
; DefaultEngine.ini still names the game mode only.
|
|
||||||
GameDefaultMap=/Game/Test/ReviewMap
|
|
||||||
EditorStartupMap=/Game/Test/ReviewMap
|
|
||||||
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.
@@ -0,0 +1,65 @@
|
|||||||
|
#include "GigLineAssetTools.h"
|
||||||
|
|
||||||
|
#include "Engine/StaticMesh.h"
|
||||||
|
#include "PhysicsEngine/BodySetup.h"
|
||||||
|
#include "StaticMeshResources.h"
|
||||||
|
|
||||||
|
void UGigLineAssetTools::SetComplexCollisionAsSimple(UStaticMesh* Mesh, bool bEnable)
|
||||||
|
{
|
||||||
|
if (!Mesh)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UBodySetup* BodySetup = Mesh->GetBodySetup();
|
||||||
|
if (!BodySetup)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BodySetup->CollisionTraceFlag = bEnable ? CTF_UseComplexAsSimple : CTF_UseSimpleAndComplex;
|
||||||
|
BodySetup->InvalidatePhysicsData();
|
||||||
|
BodySetup->CreatePhysicsMeshes();
|
||||||
|
Mesh->MarkPackageDirty();
|
||||||
|
Mesh->PostEditChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGigLineAssetTools::ClearSimpleCollision(UStaticMesh* Mesh)
|
||||||
|
{
|
||||||
|
if (!Mesh)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UBodySetup* BodySetup = Mesh->GetBodySetup();
|
||||||
|
if (!BodySetup)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BodySetup->AggGeom.EmptyElements();
|
||||||
|
BodySetup->InvalidatePhysicsData();
|
||||||
|
BodySetup->CreatePhysicsMeshes();
|
||||||
|
Mesh->MarkPackageDirty();
|
||||||
|
Mesh->PostEditChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UGigLineAssetTools::GetCollisionComplexity(UStaticMesh* Mesh)
|
||||||
|
{
|
||||||
|
if (!Mesh)
|
||||||
|
{
|
||||||
|
return TEXT("no mesh");
|
||||||
|
}
|
||||||
|
|
||||||
|
const UBodySetup* BodySetup = Mesh->GetBodySetup();
|
||||||
|
if (!BodySetup)
|
||||||
|
{
|
||||||
|
return TEXT("no body setup");
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 Flag = static_cast<int32>(BodySetup->CollisionTraceFlag.GetValue());
|
||||||
|
if (Flag == CTF_UseComplexAsSimple) { return TEXT("UseComplexAsSimple"); }
|
||||||
|
if (Flag == CTF_UseSimpleAsComplex) { return TEXT("UseSimpleAsComplex"); }
|
||||||
|
if (Flag == CTF_UseSimpleAndComplex) { return TEXT("UseSimpleAndComplex"); }
|
||||||
|
return TEXT("ProjectDefault");
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||||
|
#include "GigLineAssetTools.generated.h"
|
||||||
|
|
||||||
|
class UStaticMesh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small editor-side helpers for the headless asset pipeline scripts.
|
||||||
|
*
|
||||||
|
* Exposed to Python so tools can fix up imported meshes that the Interchange
|
||||||
|
* importer gives unusable default collision. The terrain is the motivating
|
||||||
|
* case: without complex-as-simple it gets a bounding-box proxy, so the whole
|
||||||
|
* street sits under an invisible 8 m box and the player walks on the box lid.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class GIGLINE_API UGigLineAssetTools : public UBlueprintFunctionLibrary
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Make collision trace against the mesh triangles instead of a proxy hull. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GigLine|Assets")
|
||||||
|
static void SetComplexCollisionAsSimple(UStaticMesh* Mesh, bool bEnable = true);
|
||||||
|
|
||||||
|
/** Drop any generated simple collision (hulls/boxes) from the mesh. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GigLine|Assets")
|
||||||
|
static void ClearSimpleCollision(UStaticMesh* Mesh);
|
||||||
|
|
||||||
|
/** Report the current collision complexity: "SimpleAndComplex", "Simple", ... */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GigLine|Assets")
|
||||||
|
static FString GetCollisionComplexity(UStaticMesh* Mesh);
|
||||||
|
};
|
||||||
@@ -7,8 +7,13 @@
|
|||||||
#include "GameFramework/CharacterMovementComponent.h"
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
#include "InputAction.h"
|
#include "InputAction.h"
|
||||||
#include "GameFramework/PlayerController.h"
|
|
||||||
#include "InputMappingContext.h"
|
#include "InputMappingContext.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "TimerManager.h"
|
||||||
|
#include "Containers/Ticker.h"
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
#include "Misc/CommandLine.h"
|
||||||
|
#include "Misc/Parse.h"
|
||||||
|
|
||||||
AGigLineCharacter::AGigLineCharacter()
|
AGigLineCharacter::AGigLineCharacter()
|
||||||
{
|
{
|
||||||
@@ -39,51 +44,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 pushes
|
||||||
|
// the now-valid mapping context to the local player's subsystem; called from
|
||||||
LoadInputAssets();
|
// SetupPlayerInputComponent at possession, when the controller 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()))
|
||||||
@@ -92,11 +102,250 @@ void AGigLineCharacter::BeginPlay()
|
|||||||
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
|
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
|
||||||
{
|
{
|
||||||
Subsystem->AddMappingContext(MappingContext, 0);
|
Subsystem->AddMappingContext(MappingContext, 0);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine IN: pushed mapping context %s"),
|
||||||
|
*MappingContext->GetName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine IN: no enhanced input subsystem on local player"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine IN: no PlayerController at LoadInputAssets"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AGigLineCharacter::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
StartLocation = GetActorLocation();
|
||||||
|
StartRotation = GetActorRotation();
|
||||||
|
|
||||||
|
// The PlayerStart sits above grade so the capsule cannot spawn embedded, so
|
||||||
|
// the BeginPlay position is mid-air. Re-capture once the pawn has landed,
|
||||||
|
// otherwise Reset drops the player 1.4 m back into the air every press.
|
||||||
|
// Repeat until the first grounded frame. A single late timer is wrong: it can
|
||||||
|
// fire after the player (or the QA self-test) has already started walking,
|
||||||
|
// which recorded a mid-stride position as "start".
|
||||||
|
GetWorldTimerManager().SetTimer(SettleHandle, [this]()
|
||||||
|
{
|
||||||
|
if (bQATestWalking)
|
||||||
|
{
|
||||||
|
return; // keep waiting; the test walks almost immediately
|
||||||
|
}
|
||||||
|
if (const UCharacterMovementComponent* Mov = GetCharacterMovement())
|
||||||
|
{
|
||||||
|
if (Mov->IsMovingOnGround())
|
||||||
|
{
|
||||||
|
StartLocation = GetActorLocation();
|
||||||
|
StartRotation = GetActorRotation();
|
||||||
|
GetWorldTimerManager().ClearTimer(this->SettleHandle);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine: start captured on ground at Z %.0f"),
|
||||||
|
StartLocation.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0.25f, true);
|
||||||
|
|
||||||
|
// Force game input mode + focus + mouse capture so keyboard/mouse reach the
|
||||||
|
// pawn regardless of window focus quirks.
|
||||||
|
if (APlayerController* PC = Cast<APlayerController>(GetController()))
|
||||||
|
{
|
||||||
|
PC->SetInputMode(FInputModeGameOnly());
|
||||||
|
PC->bShowMouseCursor = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEMP QA: one screenshot at +1.5s so the actual frame is reviewable.
|
||||||
|
FTimerHandle ShotHandle;
|
||||||
|
GetWorldTimerManager().SetTimer(ShotHandle, [this]()
|
||||||
|
{
|
||||||
|
if (APlayerController* PC = Cast<APlayerController>(GetController()))
|
||||||
|
{
|
||||||
|
PC->ConsoleCommand(TEXT("HighResShot 1280x720"));
|
||||||
|
}
|
||||||
|
}, 1.5f, false);
|
||||||
|
|
||||||
|
// TEMP QA: every second log world time + capsule Z + gravity + fall state so
|
||||||
|
// we can see whether the world is simulating the pawn at all (symptom:
|
||||||
|
// "spawns above ground but never falls, no input").
|
||||||
|
FTimerHandle SimHandle;
|
||||||
|
GetWorldTimerManager().SetTimer(SimHandle, [this]()
|
||||||
|
{
|
||||||
|
UCharacterMovementComponent* M = GetCharacterMovement();
|
||||||
|
UE_LOG(LogTemp, Log,
|
||||||
|
TEXT("GigLine SIM: t=%.2f XYZ=(%.0f,%.0f,%.0f) grav=%.1f fall=%d local=%d role=%d mode=%d ctrl=%s"),
|
||||||
|
GetWorld() ? GetWorld()->GetTimeSeconds() : -1.f,
|
||||||
|
GetActorLocation().X, GetActorLocation().Y, GetActorLocation().Z,
|
||||||
|
M ? M->GetGravityZ() : 0.f,
|
||||||
|
(M && M->IsFalling()) ? 1 : 0,
|
||||||
|
IsLocallyControlled() ? 1 : 0,
|
||||||
|
(int)GetLocalRole(),
|
||||||
|
M ? (int)M->MovementMode : -1,
|
||||||
|
GetController() ? *GetController()->GetName() : TEXT("NONE"));
|
||||||
|
if (UWorld* W = GetWorld())
|
||||||
|
{
|
||||||
|
const FVector L = GetActorLocation();
|
||||||
|
FHitResult Hit;
|
||||||
|
FCollisionQueryParams Q(TEXT("GigLineGroundProbe"), false, this);
|
||||||
|
const bool bHit = W->LineTraceSingleByChannel(Hit,
|
||||||
|
FVector(L.X, L.Y, 3000.f), FVector(L.X, L.Y, -3000.f), ECC_Visibility, Q);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine GROUND: top=%.0f actor=%s"),
|
||||||
|
bHit ? Hit.ImpactPoint.Z : -9999.f,
|
||||||
|
bHit && Hit.GetActor() ? *Hit.GetActor()->GetActorLabel() : TEXT("none"));
|
||||||
|
}
|
||||||
|
}, 1.0f, true);
|
||||||
|
|
||||||
|
if (FParse::Param(FCommandLine::Get(), TEXT("qaselftest")))
|
||||||
|
{
|
||||||
|
StartQASelfTest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGigLineCharacter::StartQASelfTest()
|
||||||
|
{
|
||||||
|
// Driven by a core ticker rather than a world timer on purpose: pausing the
|
||||||
|
// game stops world timers, so a timer-based test would freeze the moment it
|
||||||
|
// proved pause works.
|
||||||
|
struct FQAState
|
||||||
|
{
|
||||||
|
float Elapsed = 0.0f;
|
||||||
|
FVector Start = FVector::ZeroVector;
|
||||||
|
FVector WalkEnd = FVector::ZeroVector;
|
||||||
|
float Step = 0.0f;
|
||||||
|
bool bWalkMoved = false;
|
||||||
|
bool bResetReturned = false;
|
||||||
|
bool bPausedOn = false;
|
||||||
|
bool bUnpausedBack = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
TSharedPtr<FQAState> State = MakeShared<FQAState>();
|
||||||
|
State->Start = GetActorLocation();
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine QA: start XYZ=(%.0f,%.0f,%.0f)"),
|
||||||
|
State->Start.X, State->Start.Y, State->Start.Z);
|
||||||
|
|
||||||
|
FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateWeakLambda(this,
|
||||||
|
[this, State](float DeltaTime)
|
||||||
|
{
|
||||||
|
State->Elapsed += DeltaTime;
|
||||||
|
const float E = State->Elapsed;
|
||||||
|
|
||||||
|
// 1.0 - 3.0 s: hold forward. Reset sets the Step marker so each phase
|
||||||
|
// runs exactly once without an ever-growing if-chain.
|
||||||
|
if (E >= 1.0f && State->Step < 1.0f)
|
||||||
|
{
|
||||||
|
State->Step = 1.0f;
|
||||||
|
// Measure from the settled position, not the mid-air spawn point.
|
||||||
|
State->Start = GetActorLocation();
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine QA: settled XYZ=(%.0f,%.0f,%.0f)"),
|
||||||
|
State->Start.X, State->Start.Y, State->Start.Z);
|
||||||
|
bQATestWalking = true;
|
||||||
|
}
|
||||||
|
if (bQATestWalking)
|
||||||
|
{
|
||||||
|
AddMovementInput(GetActorForwardVector(), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.0: stop and measure.
|
||||||
|
if (E >= 3.0f && State->Step < 2.0f)
|
||||||
|
{
|
||||||
|
State->Step = 2.0f;
|
||||||
|
bQATestWalking = false;
|
||||||
|
State->WalkEnd = GetActorLocation();
|
||||||
|
const float Walked = FVector::Dist2D(State->Start, State->WalkEnd);
|
||||||
|
State->bWalkMoved = Walked > 200.0f; // >2 m in 2 s at 4 m/s
|
||||||
|
UE_LOG(LogTemp, Log,
|
||||||
|
TEXT("GigLine QA: walk moved %.0f cm in 2 s -> %s (Z %.0f -> %.0f)"),
|
||||||
|
Walked, State->bWalkMoved ? TEXT("PASS") : TEXT("FAIL"),
|
||||||
|
State->Start.Z, State->WalkEnd.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.5: reset should return to the spawn point.
|
||||||
|
if (E >= 3.5f && State->Step < 3.0f)
|
||||||
|
{
|
||||||
|
State->Step = 3.0f;
|
||||||
|
ResetToStart();
|
||||||
|
}
|
||||||
|
if (E >= 4.0f && State->Step < 4.0f)
|
||||||
|
{
|
||||||
|
State->Step = 4.0f;
|
||||||
|
const float Back = FVector::Dist(State->Start, GetActorLocation());
|
||||||
|
State->bResetReturned = Back < 100.0f;
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine QA: reset returned %.0f cm from spawn -> %s"),
|
||||||
|
Back, State->bResetReturned ? TEXT("PASS") : TEXT("FAIL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.5: pause, then confirm the world actually stopped.
|
||||||
|
if (E >= 4.5f && State->Step < 5.0f)
|
||||||
|
{
|
||||||
|
State->Step = 5.0f;
|
||||||
|
TogglePause();
|
||||||
|
State->bPausedOn = UGameplayStatics::IsGamePaused(this);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine QA: pause -> paused=%d %s"),
|
||||||
|
State->bPausedOn ? 1 : 0, State->bPausedOn ? TEXT("PASS") : TEXT("FAIL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5.5: unpause and confirm it comes back.
|
||||||
|
if (E >= 5.5f && State->Step < 6.0f)
|
||||||
|
{
|
||||||
|
State->Step = 6.0f;
|
||||||
|
TogglePause();
|
||||||
|
State->bUnpausedBack = !UGameplayStatics::IsGamePaused(this);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine QA: unpause -> paused=%d %s"),
|
||||||
|
UGameplayStatics::IsGamePaused(this) ? 1 : 0,
|
||||||
|
State->bUnpausedBack ? TEXT("PASS") : TEXT("FAIL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6.0: ground transect - sweep both sidewalks and the carriageway and
|
||||||
|
// count holes, so "can traverse the block without falling through"
|
||||||
|
// is measured rather than assumed.
|
||||||
|
if (E >= 6.0f && State->Step < 7.0f)
|
||||||
|
{
|
||||||
|
State->Step = 7.0f;
|
||||||
|
UWorld* W = GetWorld();
|
||||||
|
int32 Holes = 0, Samples = 0;
|
||||||
|
float Lowest = 1.0e9f, Highest = -1.0e9f;
|
||||||
|
static const float Lanes[] = {-1400.0f, -167.0f, 1000.0f}; // N walk, road, S walk
|
||||||
|
for (float LaneY : Lanes)
|
||||||
|
{
|
||||||
|
for (float X = -14900.0f; X <= -9420.0f; X += 200.0f)
|
||||||
|
{
|
||||||
|
FHitResult Hit;
|
||||||
|
FCollisionQueryParams Q(TEXT("GigLineTransect"), false, this);
|
||||||
|
const bool bHit = W->LineTraceSingleByChannel(Hit,
|
||||||
|
FVector(X, LaneY, 3000.f), FVector(X, LaneY, -3000.f),
|
||||||
|
ECC_Visibility, Q);
|
||||||
|
++Samples;
|
||||||
|
if (!bHit)
|
||||||
|
{
|
||||||
|
++Holes;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Lowest = FMath::Min(Lowest, Hit.ImpactPoint.Z);
|
||||||
|
Highest = FMath::Max(Highest, Hit.ImpactPoint.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UE_LOG(LogTemp, Log,
|
||||||
|
TEXT("GigLine QA: transect %d samples, %d holes, ground Z %.0f..%.0f -> %s"),
|
||||||
|
Samples, Holes, Lowest, Highest, Holes == 0 ? TEXT("PASS") : TEXT("FAIL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7.0: verdict, then stop ticking.
|
||||||
|
if (E >= 7.0f)
|
||||||
|
{
|
||||||
|
const bool bAllPass = State->bWalkMoved && State->bResetReturned
|
||||||
|
&& State->bPausedOn && State->bUnpausedBack;
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine QA: RESULT walk=%d reset=%d pause=%d unpause=%d -> %s"),
|
||||||
|
State->bWalkMoved ? 1 : 0, State->bResetReturned ? 1 : 0,
|
||||||
|
State->bPausedOn ? 1 : 0, State->bUnpausedBack ? 1 : 0,
|
||||||
|
bAllPass ? TEXT("PASS") : TEXT("FAIL"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||||
{
|
{
|
||||||
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
||||||
@@ -108,6 +357,10 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push the mapping context for this controller's local player so the bound
|
||||||
|
// actions receive input (runs at possession, when the controller exists).
|
||||||
|
LoadInputAssets();
|
||||||
|
|
||||||
// One action per direction keeps the sign explicit: W and S differ only by
|
// One action per direction keeps the sign explicit: W and S differ only by
|
||||||
// which handler runs, not by a modifier in the asset.
|
// which handler runs, not by a modifier in the asset.
|
||||||
if (MoveAction)
|
if (MoveAction)
|
||||||
@@ -143,6 +396,12 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
|
|||||||
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
|
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
|
||||||
Input->BindAction(JumpAction, ETriggerEvent::Completed, this, &AGigLineCharacter::StopJump);
|
Input->BindAction(JumpAction, ETriggerEvent::Completed, this, &AGigLineCharacter::StopJump);
|
||||||
}
|
}
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine IN: bound %d actions; ctx=%s move=%s look=%s jmp=%s"),
|
||||||
|
(Input ? 1 : 0),
|
||||||
|
MappingContext ? TEXT("ok") : TEXT("nil"),
|
||||||
|
MoveAction ? TEXT("ok") : TEXT("nil"),
|
||||||
|
LookAction ? TEXT("ok") : TEXT("nil"),
|
||||||
|
JumpAction ? TEXT("ok") : TEXT("nil"));
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -169,6 +428,8 @@ namespace
|
|||||||
|
|
||||||
void AGigLineCharacter::MoveForward(const FInputActionValue& Value)
|
void AGigLineCharacter::MoveForward(const FInputActionValue& Value)
|
||||||
{
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine IN: MoveForward fired, axis=%.3f type=%d"),
|
||||||
|
FMath::Abs(SignedAxis(Value, false)), (int)Value.GetValueType());
|
||||||
ApplyMove(FMath::Abs(SignedAxis(Value, false)), 0.0f);
|
ApplyMove(FMath::Abs(SignedAxis(Value, false)), 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,11 +451,16 @@ void AGigLineCharacter::MoveLeft(const FInputActionValue& Value)
|
|||||||
void AGigLineCharacter::ApplyMove(float Forward, float Strafe)
|
void AGigLineCharacter::ApplyMove(float Forward, float Strafe)
|
||||||
{
|
{
|
||||||
const AController* Controller = GetController();
|
const AController* Controller = GetController();
|
||||||
if (!Controller || (FMath::IsNearlyZero(Forward) && FMath::IsNearlyZero(Strafe)))
|
if (!Controller)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine IN: ApplyMove called but NO controller (fwd %.2f strafe %.2f)"),
|
||||||
|
Forward, Strafe);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (FMath::IsNearlyZero(Forward) && FMath::IsNearlyZero(Strafe))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward/right on the controller yaw keeps the capsule upright on slopes.
|
// Forward/right on the controller yaw keeps the capsule upright on slopes.
|
||||||
const FRotator YawRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f);
|
const FRotator YawRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f);
|
||||||
const FVector ForwardDir = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
const FVector ForwardDir = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
||||||
@@ -202,6 +468,8 @@ void AGigLineCharacter::ApplyMove(float Forward, float Strafe)
|
|||||||
|
|
||||||
AddMovementInput(ForwardDir, Forward);
|
AddMovementInput(ForwardDir, Forward);
|
||||||
AddMovementInput(RightDir, Strafe);
|
AddMovementInput(RightDir, Strafe);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine IN: ApplyMove fwd %.2f strafe %.2f controller=%s yaw=%.0f"),
|
||||||
|
Forward, Strafe, *Controller->GetName(), Controller->GetControlRotation().Yaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AGigLineCharacter::Look(const FInputActionValue& Value)
|
void AGigLineCharacter::Look(const FInputActionValue& Value)
|
||||||
|
|||||||
@@ -38,6 +38,22 @@ protected:
|
|||||||
void ResetToStart();
|
void ResetToStart();
|
||||||
void TogglePause();
|
void TogglePause();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TEMP QA: scripted walk / reset / pause verification.
|
||||||
|
*
|
||||||
|
* Enabled only by the `-qaselftest` command-line switch, so it never runs in
|
||||||
|
* normal play. It exists because those three behaviours were listed as
|
||||||
|
* "needs a human at the keyboard" and a repeatable headless check beats
|
||||||
|
* waiting for one.
|
||||||
|
*/
|
||||||
|
void StartQASelfTest();
|
||||||
|
|
||||||
|
/** Set while the self-test is driving forward input. */
|
||||||
|
bool bQATestWalking = false;
|
||||||
|
|
||||||
|
/** Repeating timer that captures StartLocation on first ground contact. */
|
||||||
|
FTimerHandle SettleHandle;
|
||||||
|
|
||||||
/** Where the player started, so reset returns there rather than the origin. */
|
/** Where the player started, so reset returns there rather than the origin. */
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FVector StartLocation = FVector::ZeroVector;
|
FVector StartLocation = FVector::ZeroVector;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "GigLineGameModeBase.h"
|
#include "GigLineGameModeBase.h"
|
||||||
|
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
#include "GigLineCharacter.h"
|
#include "GigLineCharacter.h"
|
||||||
#include "GigLineHUD.h"
|
#include "GigLineHUD.h"
|
||||||
|
|
||||||
@@ -9,3 +10,12 @@ AGigLineGameModeBase::AGigLineGameModeBase()
|
|||||||
// ODbL attribution must be visible in the running demo, not only in docs.
|
// ODbL attribution must be visible in the running demo, not only in docs.
|
||||||
HUDClass = AGigLineHUD::StaticClass();
|
HUDClass = AGigLineHUD::StaticClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AGigLineGameModeBase::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer)
|
||||||
|
{
|
||||||
|
Super::HandleStartingNewPlayer_Implementation(NewPlayer);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine GM: player %s connected; pawn=%s input=%s"),
|
||||||
|
NewPlayer ? *NewPlayer->GetName() : TEXT("null"),
|
||||||
|
NewPlayer && NewPlayer->GetPawn() ? *NewPlayer->GetPawn()->GetName() : TEXT("NONE"),
|
||||||
|
NewPlayer && NewPlayer->PlayerInput ? TEXT("yes") : TEXT("no"));
|
||||||
|
}
|
||||||
@@ -18,4 +18,8 @@ class GIGLINE_API AGigLineGameModeBase : public AGameModeBase
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
AGigLineGameModeBase();
|
AGigLineGameModeBase();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Logs whether a local player connected to a pawn with input (QA). */
|
||||||
|
virtual void HandleStartingNewPlayer_Implementation(class APlayerController* NewPlayer) override;
|
||||||
};
|
};
|
||||||
@@ -6,6 +6,22 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-11-08 — Reset was returning to mid-air; QA self-test switch
|
||||||
|
|
||||||
|
- **`ResetToStart()` restored a `StartLocation` captured in `BeginPlay`**, which is the PlayerStart position *above* grade (Z 600) so the capsule never spawns embedded. Every reset therefore dropped the player 1.4 m back into the air. Fix: capture `StartLocation` on the first grounded frame (repeating 0.25 s timer, cleared once captured) — not a one-shot timer, which can fire after the player has already started walking and record a mid-stride position.
|
||||||
|
- **`-qaselftest`** drives the checks headlessly and logs `GigLine QA: ... PASS/FAIL`. It runs on a **core ticker, not a world timer**: pausing the game stops world timers, so a timer-driven test freezes exactly when it proves pause works.
|
||||||
|
- Current results: walk 746 cm / 2 s with Z steady, reset 0 cm from spawn, pause `IsGamePaused()` 1, unpause 0, transect 84 samples / 0 holes (ground Z 317..395).
|
||||||
|
|
||||||
|
## 2026-11-08 — The fall-through was an 8 m invisible box (QA branch)
|
||||||
|
|
||||||
|
- **Symptom:** player "spawned above ground but never fell", sat at Z 909, `fall=0`, `mode=1` (Walking), no fall-through, view looked plausible so it was mistaken for working.
|
||||||
|
- **Cause:** `Terrain_DEM` had only a **bounding-box simple collision proxy** (Interchange default). A line trace down at *every* Y through the block hit `Demo_Terrain_DEM` at exactly **819** — the mesh bbox max Z. The whole street was under an invisible lid; the pawn stood on the box, 8 m above the road.
|
||||||
|
- **Proof:** a debug `LineTraceSingleByChannel` profile at Y −1912…+1447 reported `top=819 actor=Demo_Terrain_DEM` for the entire street. After the fix it reports `Sidewalk_North 395`, `Road_Hollywood 366`, `Sidewalk_South 317`.
|
||||||
|
- **Fix:** force complex-as-simple collision on the terrain. Python **cannot** set it (`StaticMesh`/`BodySetup` expose no such property; `dir()` shows only `lod_for_collision`). Added `UGigLineAssetTools::SetComplexCollisionAsSimple` in the C++ module (`BodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple`, UE 5.8 no longer has `bUseComplexAsSimpleCollision`).
|
||||||
|
- **Second gotcha:** the flag lives on the **asset**, not the map. Setting it in a builder session and saving only the `.umap` loses it — `EditorAssetLibrary.save_loaded_asset()` is required, otherwise the next run reloads the bbox proxy.
|
||||||
|
- **Third gotcha:** `HighResShot 1280 720` means **multiplier 1280** (UE warns "the high resolution screenshot multiplier is too large for your system"). The correct form is `HighResShot 1280x720`.
|
||||||
|
- Resulting spawn: `(-13336, -1150, 600)` — inside the carved corridor, lands on the road at Z 456 (feet 368 on road top 366).
|
||||||
|
|
||||||
## 2026-11-07 — Publication audit
|
## 2026-11-07 — Publication audit
|
||||||
|
|
||||||
- MPFB 2.0.18's installed `blender_manifest.toml` identifies its code as GPL-3.0-or-later; its versioned `LICENSE.md` explicitly puts bundled mesh, texture, and rig assets under CC0 1.0. The earlier CC0 claim for the add-on code was wrong.
|
- MPFB 2.0.18's installed `blender_manifest.toml` identifies its code as GPL-3.0-or-later; its versioned `LICENSE.md` explicitly puts bundled mesh, texture, and rig assets under CC0 1.0. The earlier CC0 claim for the add-on code was wrong.
|
||||||
@@ -199,3 +215,24 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
|
|||||||
- The two north-side buildings (TCL, Tussaud's) were similarly on the **south** side: `export_north_buildings.py` mirrored Y, which put them where the frontage belongs.
|
- The two north-side buildings (TCL, Tussaud's) were similarly on the **south** side: `export_north_buildings.py` mirrored Y, which put them where the frontage belongs.
|
||||||
- Fix: hero at X −13336 / Y +1307 (the frontage gap between 6902 and 6838); frontage and north buildings exported **without** a Y mirror (the FBX preset already maps Blender −Y south to UE +Y south).
|
- Fix: hero at X −13336 / Y +1307 (the frontage gap between 6902 and 6838); frontage and north buildings exported **without** a Y mirror (the FBX preset already maps Blender −Y south to UE +Y south).
|
||||||
- Lesson: **place a building only against a neighbour or the street mesh, never in isolation.** A single object in an empty map hides any number of placement errors; adding the adjacent frontage immediately exposed this one.
|
- Lesson: **place a building only against a neighbour or the street mesh, never in isolation.** A single object in an empty map hides any number of placement errors; adding the adjacent frontage immediately exposed this one.
|
||||||
|
## 2026-11-07 — walkable demo QA bugs (fix branch qa-input-collision-fix-0.1.0)
|
||||||
|
|
||||||
|
- **"Stuck" window = wrong default map.** A plain `./GigLine.sh` opened
|
||||||
|
`/Engine/Maps/Templates/OpenWorld` (empty engine template) instead of the demo,
|
||||||
|
so nothing was interactive. `GameDefaultMap` was in `DefaultGame.ini` and not
|
||||||
|
applied; it must live in `DefaultEngine.ini[/Script/EngineSettings.GameMapsSettings]`
|
||||||
|
with `GlobalDefaultGameMode`. Verified: plain launch now loads
|
||||||
|
`/Game/Hollywood/DemoMap`. Evidence: `LogLoad: LoadMap: /Engine/Maps/Templates/OpenWorld`.
|
||||||
|
- **Input assets were pruned from the pak.** The Enhanced Input assets under
|
||||||
|
`/Game/Input` were only soft-referenced at runtime, so the cooker excluded them
|
||||||
|
(cook `ReferencedSet.txt` had zero `/Game/Input` lines -> all inputs nil at
|
||||||
|
runtime). Fix: hard-reference via `ConstructorHelpers::FObjectFinder` in the
|
||||||
|
character ctor (cooker follows hard refs) + `+DirectoriesToAlwaysCook`. Packaged
|
||||||
|
log then showed `ctx=ok move=ok ... jump=ok`.
|
||||||
|
- **Character fell through** the site: site meshes staged MOVABLE + PlayerStart far
|
||||||
|
past the north sidewalk. Fix: STATIC mobility + explicit collision
|
||||||
|
(QUERY_AND_PHYSICS/WorldStatic/BlockAll) and PlayerStart at Y -1200 on the
|
||||||
|
sidewalk. Packaged run: `grounded=1 movement=Walking`.
|
||||||
|
- Diagnose packaged runtime via the engine log
|
||||||
|
(`.../Linux/GigLine/Saved/Logs/GigLine.log`); a headless `-nullrhi` commandlet
|
||||||
|
cannot validate physics traces (even a spawned cube returns NO HIT there).
|
||||||
@@ -86,13 +86,13 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep
|
|||||||
- [x] Model hero façade for the El Capitan Entertainment Centre site. **Second pass built 2026-11-07** (proportions recalibrated from the 2009 photograph: entablature 3.4 m, parapet 2.2 m, architrave and cornice bands, square pedestals — the first pass had 5.7 m of blank wall above the capitals and read as a warehouse). **First pass built 2026-11-07** (`Tools/hero_facade_build.py` → `Art/blender/hero/hero-facade-2026-11-07.blend` + JSON part report with evidence per part → `Tools/ue_hero_facade_import.py` → `/Game/Hollywood/ElCapitan/SM_HeroFacade`, 1892 tris, 4 materials, placed on the measured building line). Six columns at 4.96 m over the 25.75 m frontage, plinth, three portal flights (5 risers, not the old 8), entablature 2.2 m, parapet 1.6 m with motto band and medallions. **Screenshot approved** (`Screenshots/review-hero-portals`, `review-hero-facade-front/oblique`). Next pass: banner and window placement, then materials.
|
- [x] Model hero façade for the El Capitan Entertainment Centre site. **Second pass built 2026-11-07** (proportions recalibrated from the 2009 photograph: entablature 3.4 m, parapet 2.2 m, architrave and cornice bands, square pedestals — the first pass had 5.7 m of blank wall above the capitals and read as a warehouse). **First pass built 2026-11-07** (`Tools/hero_facade_build.py` → `Art/blender/hero/hero-facade-2026-11-07.blend` + JSON part report with evidence per part → `Tools/ue_hero_facade_import.py` → `/Game/Hollywood/ElCapitan/SM_HeroFacade`, 1892 tris, 4 materials, placed on the measured building line). Six columns at 4.96 m over the 25.75 m frontage, plinth, three portal flights (5 risers, not the old 8), entablature 2.2 m, parapet 1.6 m with motto band and medallions. **Screenshot approved** (`Screenshots/review-hero-portals`, `review-hero-facade-front/oblique`). Next pass: banner and window placement, then materials.
|
||||||
- [x] Model surrounding block frontage. **Done 2026-11-07, screenshots approved.** The five remaining south-side buildings verified for the block (`Geo/verification/2026-11-06-el-capitan-block.md`) are exported in place from the OSM blockout by `Tools/export_block_frontage.py` -> `Tools/ue_import_block_frontage.py` -> `/Game/Hollywood/Frontage/`, and staged by the demo builder: 6922, 6902, 6838 El Capitan Theatre, 6822 Budget, 6806-6820, 6800 Lee Drug. **Adding them exposed a pre-existing placement bug**: UE south is +Y (the south sidewalk is Y +718..+1447) but the hero facade sat at Y -1450 (north side) and X -12024..-9420, i.e. 26 m east of its own footprint and on the wrong side of the street; the two north-side buildings were likewise on the south side. The hero is now at X -13336 / Y +1307 (inside the frontage gap between 6902 and 6838) and the north buildings are back on the north side (Y -1802..-9431). The sun was raised to pitch -62 so both frontages read instead of one being silhouetted.
|
- [x] Model surrounding block frontage. **Done 2026-11-07, screenshots approved.** The five remaining south-side buildings verified for the block (`Geo/verification/2026-11-06-el-capitan-block.md`) are exported in place from the OSM blockout by `Tools/export_block_frontage.py` -> `Tools/ue_import_block_frontage.py` -> `/Game/Hollywood/Frontage/`, and staged by the demo builder: 6922, 6902, 6838 El Capitan Theatre, 6822 Budget, 6806-6820, 6800 Lee Drug. **Adding them exposed a pre-existing placement bug**: UE south is +Y (the south sidewalk is Y +718..+1447) but the hero facade sat at Y -1450 (north side) and X -12024..-9420, i.e. 26 m east of its own footprint and on the wrong side of the street; the two north-side buildings were likewise on the south side. The hero is now at X -13336 / Y +1307 (inside the frontage gap between 6902 and 6838) and the north buildings are back on the north side (Y -1802..-9431). The sun was raised to pitch -62 so both frontages read instead of one being silhouetted.
|
||||||
- [x] Model simplified background buildings directly across Hollywood Boulevard. Done 2026-11-07, the two in-scope north-side buildings — TCL Chinese Theatre (SM_TCL_Chinese_Theatre) and Madame Tussaud's (SM_Madame_Tussauds) — taken from the verified OSM blockout volumes and exported with `ue_metric_v1`, with a Y-mirror so they land north of Hollywood Blvd across the street from the hero (TCL spans Unreal Y 1802..9431, Tussaud's 1888..7586; road centreline ≈ 0, hero façade at Y −1450). Staged in the DemoMap as `North_TCL_Chinese` / `North_Madame_Tussauds`; demo shot `review-03-demo-north-buildings` captured. Simple flat facade materials (surfaces are the `0.1.4` patch).
|
- [x] Model simplified background buildings directly across Hollywood Boulevard. Done 2026-11-07, the two in-scope north-side buildings — TCL Chinese Theatre (SM_TCL_Chinese_Theatre) and Madame Tussaud's (SM_Madame_Tussauds) — taken from the verified OSM blockout volumes and exported with `ue_metric_v1`, with a Y-mirror so they land north of Hollywood Blvd across the street from the hero (TCL spans Unreal Y 1802..9431, Tussaud's 1888..7586; road centreline ≈ 0, hero façade at Y −1450). Staged in the DemoMap as `North_TCL_Chinese` / `North_Madame_Tussauds`; demo shot `review-03-demo-north-buildings` captured. Simple flat facade materials (surfaces are the `0.1.4` patch).
|
||||||
- [A] Build sidewalks, roads, and collision meshes. Terrain, road, sidewalks and kerbs exist (`Art/blender/terrain/`, `street/`) and are assembled in `site/site-2026-11-06.blend`; collision and details (crossing markings, kerb radii, aprons) are not built. Ground must follow the DEM: 3.94 m fall along the block, ~2 % crown between the gutters at ±10–11 m from the centreline, north sidewalk higher than south. Kerb height is an unresolved assumption — the DEM does not resolve it (`Geo/verification/2026-11-06-cross-sections.md`).
|
- [A] Build sidewalks, roads, and collision meshes. Terrain, road, sidewalks and kerbs exist (`Art/blender/terrain/`, `street/`) and are assembled in `site/site-2026-11-06.blend`; crossing markings were built 2026-11-07. Ground follows the DEM: 3.94 m fall along the block, north sidewalk higher than south. **Collision done and verified 2026-11-08:** every site mesh is STATIC with `BlockAll`, the terrain is forced complex-as-simple (a bbox proxy had buried the street 8 m deep), and a swept transect of both sidewalks plus the carriageway reports 84 samples, **0 holes**, ground Z 317..395. Remaining: kerb radii at the corners and apron details, and the kerb height is still an assumption — the DEM does not resolve it (`Geo/verification/2026-11-06-cross-sections.md`).
|
||||||
- [x] Materials and look development for all exported assets. **Done 2026-11-07, screenshots approved.** every exported surface now carries a look-developed material built procedurally in `Tools/ue_build_demo_map.py` (`ensure_material`): base colour with a world-space noise blend between two shades for large-scale variation, plus an explicit roughness and specular. Set is `M_Asphalt` 0.92, `M_Concrete` 0.88, `M_KerbConcrete` 0.85, `M_Ground` 0.98, `M_RoadPaint` 0.62, `M_FacadeStone` 0.72 (hero façade) and `M_Stucco` 0.86 (TCL, Tussaud's), assigned by mesh and by actor label. Materials are rebuilt on every run so a stale asset cannot mask a look change. The richer texture-based pass (baked/procedural brick, brick_facade and seamless_concrete generators) stays in the `0.1.4` façade patch.
|
- [x] Materials and look development for all exported assets. **Done 2026-11-07, screenshots approved.** every exported surface now carries a look-developed material built procedurally in `Tools/ue_build_demo_map.py` (`ensure_material`): base colour with a world-space noise blend between two shades for large-scale variation, plus an explicit roughness and specular. Set is `M_Asphalt` 0.92, `M_Concrete` 0.88, `M_KerbConcrete` 0.85, `M_Ground` 0.98, `M_RoadPaint` 0.62, `M_FacadeStone` 0.72 (hero façade) and `M_Stucco` 0.86 (TCL, Tussaud's), assigned by mesh and by actor label. Materials are rebuilt on every run so a stale asset cannot mask a look change. The richer texture-based pass (baked/procedural brick, brick_facade and seamless_concrete generators) stays in the `0.1.4` façade patch.
|
||||||
|
|
||||||
### Gameplay - active - 01a1b26b-3c94-707f-9665-2f6b4a79b564 (branch `gameplay-attribution-0.1.0`)
|
### Gameplay - active - qa-input-collision-fix (branch `qa-input-collision-fix-0.1.0`)
|
||||||
|
|
||||||
- [x] First-person movement, mouse look, and Enhanced Input bindings. Implemented 2026-11-07: `AGigLineCharacter` (C++, 1.75 m capsule, 1.60 m eye camera, 4 m/s walk, mouse look in deg/s, jump) as the default pawn, with five actions and an 11-entry context in `/Game/Input` built by `Tools/ue_create_input_assets.py`. **Not yet confirmed interactively** — launch PIE on `Test/ReviewMap` and walk. Screenshot review pending (`Screenshots/README.md`).
|
- [x] First-person movement, mouse look, and Enhanced Input bindings. Implemented 2026-11-07: `AGigLineCharacter` (C++, 1.75 m capsule, 1.60 m eye camera, 4 m/s walk, mouse look in deg/s, jump) as the default pawn, with five actions and an 11-entry context in `/Game/Input` built by `Tools/ue_create_input_assets.py`. **Not yet confirmed interactively** — launch PIE on `Test/ReviewMap` and walk. Screenshot review pending (`Screenshots/README.md`).
|
||||||
- [D] Collision, reset, and pause functionality. Reset (**R**) and pause (**Escape**, a real game pause that releases the cursor) are implemented in `AGigLineCharacter` with `/Game/Input/IA_Reset` and `IA_Pause`. Collision now exists — terrain, road and sidewalks import as complex-as-simple, so a simple hull cannot ramp over the kerbs. **Not verified by hand**: walk, reset and pause need a human at the keyboard. No pause menu widget yet, only the pause itself.
|
- [x] Collision, reset, and pause functionality. Reset (**R**) and pause (**Escape**) implemented in `AGigLineCharacter` with `/Game/Input/IA_Reset` and `IA_Pause`. **Collision root cause found 2026-11-08:** the "fall-through" was never a fall — the imported `Terrain_DEM` had only a bounding-box simple collision proxy, so the player stood on an invisible lid 8 m above the street (every downward trace hit the terrain at Z 819). Fixed by forcing complex-as-simple collision on the terrain (`UGigLineAssetTools`, C++) and saving the asset; the demo spawns at `(-13336, -1150, 600)` and lands on the road at Z 456. **Verified 2026-11-08** by a scripted check (`-qaselftest`, core ticker so pause cannot freeze it): walk 761 cm in 2 s with Z steady, reset returns 0 cm from spawn, pause reports `IsGamePaused()` 1, unpause 0 — `RESULT ... PASS`. Writing it found a real bug: `ResetToStart()` teleported to the mid-air PlayerStart (Z 600), dropping the player 1.4 m on every reset; `StartLocation` is now captured on the first grounded frame. A manual smoke test is still worth doing before the release tag.
|
||||||
- [x] Build the walkable demo level: terrain, street, and hero facade assembled into one map with a start point on the north sidewalk, so the demo is something a player can walk rather than a review staging. **Built 2026-11-07** by `Tools/ue_build_demo_map.py` → `/Game/Hollywood/DemoMap`: sun/sky, `Hero_Facade`, six site meshes imported with collision (`Terrain_DEM`, `Road_Hollywood`, both sidewalks and both kerbs), and a `Demo_PlayerStart` on the north sidewalk 20 m back from the frontage. Demo views in `Screenshots/demo/` re-rendered and ☑ approved 2026-11-07.
|
- [x] Build the walkable demo level: terrain, street, and hero facade assembled into one map with a start point on the north sidewalk, so the demo is something a player can walk rather than a review staging. **Built 2026-11-07** by `Tools/ue_build_demo_map.py` → `/Game/Hollywood/DemoMap`: sun/sky, `Hero_Facade`, six site meshes imported with collision (`Terrain_DEM`, `Road_Hollywood`, both sidewalks and both kerbs), and a `Demo_PlayerStart` on the north sidewalk 20 m back from the frontage. Demo views in `Screenshots/demo/` re-rendered and ☑ approved 2026-11-07.
|
||||||
- [x] Walkable sidewalks and a designated street crossing. Crossing chosen: mid-block signalised crossing, OSM way 385648319, at 130 m along the block (`Geo/verification/2026-11-06-street-profile.md`). Done 2026-11-07: continental zebra markings (0.5 m bars / 0.5 m gaps, 3 m band) at all three signalised crossings, no collision on the paint; kerbs were already flushed at the crossings. Enablers found on the way: the street surfaces were 100 % down-facing (invisible in engine) — winding flipped; the terrain buried the street — 10.7 m corridor cut around the centreline (sidewalks cover the transition, walk-check 0 holes); LFS-lockable `.blend`/`.umap` check out read-only — builders now chmod before saving and fail loudly. Screenshot ☑ approved 2026-11-07 (`Screenshots/demo/review-demo-crossing`); the corridor renders near-black in shadow — that was the **Fixed daytime lighting** item, now also done.
|
- [x] Walkable sidewalks and a designated street crossing. Crossing chosen: mid-block signalised crossing, OSM way 385648319, at 130 m along the block (`Geo/verification/2026-11-06-street-profile.md`). Done 2026-11-07: continental zebra markings (0.5 m bars / 0.5 m gaps, 3 m band) at all three signalised crossings, no collision on the paint; kerbs were already flushed at the crossings. Enablers found on the way: the street surfaces were 100 % down-facing (invisible in engine) — winding flipped; the terrain buried the street — 10.7 m corridor cut around the centreline (sidewalks cover the transition, walk-check 0 holes); LFS-lockable `.blend`/`.umap` check out read-only — builders now chmod before saving and fail loudly. Screenshot ☑ approved 2026-11-07 (`Screenshots/demo/review-demo-crossing`); the corridor renders near-black in shadow — that was the **Fixed daytime lighting** item, now also done.
|
||||||
- [x] Fixed daytime lighting and basic ambience. **Done 2026-11-07, screenshots approved.** Demo map now has a fixed daytime rig in `Tools/ue_build_demo_map.py`: sun pitch −50 / yaw 120 (intensity 4, warm 255/245/230, atmosphere sun light), sky light 2.6 with real-time capture, sky atmosphere, height fog (density 0.008), and a manual-exposure post-process volume (min = max brightness 1.0) so a shot cannot wash out or crush to black. Two root causes found while doing it: (a) **Blender flat diffuse colours do not survive the FBX→UE import** — every site mesh arrived white and the terrain arrived with `WorldGridMaterial`, which is what actually made the scene look blown out; explicit `M_Asphalt` / `M_Concrete` / `M_Ground` / `M_RoadPaint` materials are now created and assigned in the demo builder. (b) only **one of the three crossings** was reaching the game: each band was its own object and Blender renamed the duplicates `.001`/`.002`, which site assembly skips; all bands are now one `Crossing_Markings` object. Demo cameras also fixed: street-level sat inside the façade body, and the crossing camera aimed at the wrong crossing (s=36.6 instead of the designated s=130.1 at UE X −14956). Lighting is dynamic-only (`r.AllowStaticLighting=False`, all lights Movable) so the editor's "LIGHTING NEEDS TO BE REBUILT" warning no longer fires. All four demo views approved by the owner.
|
- [x] Fixed daytime lighting and basic ambience. **Done 2026-11-07, screenshots approved.** Demo map now has a fixed daytime rig in `Tools/ue_build_demo_map.py`: sun pitch −50 / yaw 120 (intensity 4, warm 255/245/230, atmosphere sun light), sky light 2.6 with real-time capture, sky atmosphere, height fog (density 0.008), and a manual-exposure post-process volume (min = max brightness 1.0) so a shot cannot wash out or crush to black. Two root causes found while doing it: (a) **Blender flat diffuse colours do not survive the FBX→UE import** — every site mesh arrived white and the terrain arrived with `WorldGridMaterial`, which is what actually made the scene look blown out; explicit `M_Asphalt` / `M_Concrete` / `M_Ground` / `M_RoadPaint` materials are now created and assigned in the demo builder. (b) only **one of the three crossings** was reaching the game: each band was its own object and Blender renamed the duplicates `.001`/`.002`, which site assembly skips; all bands are now one `Crossing_Markings` object. Demo cameras also fixed: street-level sat inside the façade body, and the crossing camera aimed at the wrong crossing (s=36.6 instead of the designated s=130.1 at UE X −14956). Lighting is dynamic-only (`r.AllowStaticLighting=False`, all lights Movable) so the editor's "LIGHTING NEEDS TO BE REBUILT" warning no longer fires. All four demo views approved by the owner.
|
||||||
@@ -110,7 +110,7 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep
|
|||||||
|
|
||||||
### Release checks
|
### Release checks
|
||||||
|
|
||||||
- [ ] Player can traverse both sides of the demo without falling through geometry. Blender pre-check passes: 0 raycast holes along both sidewalks and the designated crossing; worst seam step 15.8 cm at the sidewalk's outer edge (`Art/blender/README.md`). Engine traversal still to be tested.
|
- [x] Player can traverse both sides of the demo without falling through geometry. **Verified 2026-11-08:** Blender pre-check plus an in-engine swept transect (`-qaselftest`) over the north sidewalk, carriageway and south sidewalk from X -14900 to -9420 — 84 samples, 0 holes, ground Z 317..395 (sidewalk 395, road 366, south walk 317). The player spawns at Z 456 and stays there for the whole run (`fall=0`, single Z).
|
||||||
- [ ] Building placement, street scale, and site identity checked against references.
|
- [ ] Building placement, street scale, and site identity checked against references.
|
||||||
- [ ] No blocking collision or packaging defects.
|
- [ ] No blocking collision or packaging defects.
|
||||||
- [ ] 60 FPS at 1440p on the dev workstation (Bazzite, Ryzen 7 7700X, 64 GB RAM, RTX 5070 Ti 16 GB) using a documented quality preset; record upscaling state; report performance without frame generation.
|
- [ ] 60 FPS at 1440p on the dev workstation (Bazzite, Ryzen 7 7700X, 64 GB RAM, RTX 5070 Ti 16 GB) using a documented quality preset; record upscaling state; report performance without frame generation.
|
||||||
|
|||||||
@@ -51,3 +51,10 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `render_screenshots.py` / `render_screenshots.sh` | Rebuilds the review map, runs the game, captures the shot list into `Screenshots/` |
|
| `render_screenshots.py` / `render_screenshots.sh` | Rebuilds the review map, runs the game, captures the shot list into `Screenshots/` |
|
||||||
| `judge_screenshot.py` | Vision-model pre-check: judges a screenshot against an expected-scene description (`--image` + `--expect`, or `--shots` for every shot in the README). Reads `OPENROUTER_API_KEY` from `.env` (ignored). Optional helper — a human must still approve. |
|
| `judge_screenshot.py` | Vision-model pre-check: judges a screenshot against an expected-scene description (`--image` + `--expect`, or `--shots` for every shot in the README). Reads `OPENROUTER_API_KEY` from `.env` (ignored). Optional helper — a human must still approve. |
|
||||||
|
|
||||||
|
## Gameplay QA
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `UnrealEditor <uproject> /Game/Hollywood/DemoMap -game -qaselftest` | Headless behaviour check: drives forward input for 2 s, then resets and pauses. Logs `GigLine QA: ... PASS/FAIL` lines and a final `RESULT`. Runs only with `-qaselftest`, so normal play is unaffected. |
|
||||||
|
| `GigLine SIM` / `GigLine GROUND` log lines | Emitted every second by `AGigLineCharacter` (TEMP QA): position, fall state, movement mode, and what the ground probe hits underneath. Remove before release. |
|
||||||
+38
-11
@@ -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")
|
||||||
@@ -301,13 +301,33 @@ def build():
|
|||||||
mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}")
|
mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}")
|
||||||
if mesh is None:
|
if mesh is None:
|
||||||
continue
|
continue
|
||||||
|
# The imported terrain carries a bounding-box simple collision proxy,
|
||||||
|
# which puts an invisible lid ~8 m over the whole block: the player
|
||||||
|
# walks on that box instead of the street. Force trace against the real
|
||||||
|
# triangles so the carved street corridor is actually open.
|
||||||
|
if "Terrain" in name:
|
||||||
|
asset = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}")
|
||||||
|
if asset:
|
||||||
|
unreal.GigLineAssetTools.set_complex_collision_as_simple(asset, True)
|
||||||
|
# The flag lives on the asset, not the map: persist it or the
|
||||||
|
# next session reloads the bbox proxy and the street is buried.
|
||||||
|
saved_asset = unreal.EditorAssetLibrary.save_loaded_asset(asset)
|
||||||
|
log(f"terrain collision: {unreal.GigLineAssetTools.get_collision_complexity(asset)} "
|
||||||
|
f"(saved asset: {saved_asset})")
|
||||||
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,13 +382,20 @@ 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.
|
# CRITICAL: the terrain is only carved inside the street corridor, and
|
||||||
spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 120.0), "Demo_PlayerStart",
|
# terrain_from_dem.py --corridor 10.7 is a HALF-width around the road
|
||||||
|
# centreline (Y ~= -167), so the cut band is Y -1237..+902. A PlayerStart at
|
||||||
|
# Y -1700 sits OUTSIDE it, under ~8 m of uncut terrain: the capsule spawns
|
||||||
|
# embedded, depenetration pops it to Z ~909 (terrain top 819 + half-height
|
||||||
|
# 87.5) and it never touches the sidewalk. Y -1150 is on the north sidewalk
|
||||||
|
# (Y -1912..-1053) AND inside the cut, so the pavement is the ground.
|
||||||
|
# Z 600 is clear air; the capsule drops onto the pavement and settles.
|
||||||
|
spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1150.0, 600.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 cut north sidewalk, facing south (+Y)")
|
||||||
|
|
||||||
# A lockable LFS .umap checks out read-only, which makes save_map silently
|
# A lockable LFS .umap checks out read-only, which makes save_map silently
|
||||||
# return False — chmod u+w first or the staged actors never persist.
|
# return False — chmod u+w first or the staged actors never persist.
|
||||||
|
|||||||
@@ -85,7 +85,11 @@ def build():
|
|||||||
# component and D/A carry strafe, combined by the character. A 2D value would
|
# component and D/A carry strafe, combined by the character. A 2D value would
|
||||||
# need per-key axis modifiers, and modifiers do not survive the save path here.
|
# need per-key axis modifiers, and modifiers do not survive the save path here.
|
||||||
ia_move = fresh("IA_Move", unreal.InputAction, unreal.InputAction_Factory)
|
ia_move = fresh("IA_Move", unreal.InputAction, unreal.InputAction_Factory)
|
||||||
ia_move.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D)
|
# Boolean (pressed = 1), not AXIS1D: the C++ handler reads Get<bool>() and
|
||||||
|
# moves at full walk speed. An axis action from a keyboard key can deliver 0
|
||||||
|
# depending on the trigger, which showed up as "character stands, W does not
|
||||||
|
# move" in QA. WASD are four separate per-direction actions below.
|
||||||
|
ia_move.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||||
save(ia_move)
|
save(ia_move)
|
||||||
|
|
||||||
ia_look = fresh("IA_Look", unreal.InputAction, unreal.InputAction_Factory)
|
ia_look = fresh("IA_Look", unreal.InputAction, unreal.InputAction_Factory)
|
||||||
@@ -107,15 +111,15 @@ def build():
|
|||||||
# Two actions feed the one-dimensional move: IA_Move for forward/back, and a
|
# Two actions feed the one-dimensional move: IA_Move for forward/back, and a
|
||||||
# second action for strafe, both reporting on the value's single component.
|
# second action for strafe, both reporting on the value's single component.
|
||||||
ia_strafe = fresh("IA_Strafe", unreal.InputAction, unreal.InputAction_Factory)
|
ia_strafe = fresh("IA_Strafe", unreal.InputAction, unreal.InputAction_Factory)
|
||||||
ia_strafe.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D)
|
ia_strafe.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||||
save(ia_strafe)
|
save(ia_strafe)
|
||||||
|
|
||||||
ia_back = fresh("IA_MoveBack", unreal.InputAction, unreal.InputAction_Factory)
|
ia_back = fresh("IA_MoveBack", unreal.InputAction, unreal.InputAction_Factory)
|
||||||
ia_back.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D)
|
ia_back.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||||
save(ia_back)
|
save(ia_back)
|
||||||
|
|
||||||
ia_left = fresh("IA_StrafeLeft", unreal.InputAction, unreal.InputAction_Factory)
|
ia_left = fresh("IA_StrafeLeft", unreal.InputAction, unreal.InputAction_Factory)
|
||||||
ia_left.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D)
|
ia_left.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
|
||||||
save(ia_left)
|
save(ia_left)
|
||||||
|
|
||||||
# Reset and pause are boolean: one press, no accumulator.
|
# Reset and pause are boolean: one press, no accumulator.
|
||||||
|
|||||||
+12
-2
@@ -50,6 +50,12 @@ def import_fbx(path, name, collision="complex"):
|
|||||||
smd.convert_scene_unit = False
|
smd.convert_scene_unit = False
|
||||||
smd.force_front_x_axis = False
|
smd.force_front_x_axis = False
|
||||||
smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS
|
smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS
|
||||||
|
# Generate collision at import. A default FBX static-mesh import creates NO
|
||||||
|
# collision primitives, so CTF_USE_COMPLEX_AS_SIMPLE later has nothing to use
|
||||||
|
# and the player falls through the walkable surfaces (observed at runtime:
|
||||||
|
# ground trace under the spawn hits nothing). auto_generate_collision bakes
|
||||||
|
# collision geometry into the mesh so the walkable plane actually blocks.
|
||||||
|
smd.auto_generate_collision = True
|
||||||
task.options = ui
|
task.options = ui
|
||||||
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
||||||
|
|
||||||
@@ -71,8 +77,12 @@ def import_fbx(path, name, collision="complex"):
|
|||||||
log(f"{name:18} import ok size=({mx.x - mn.x:.0f}, {mx.y - mn.y:.0f}, {mx.z - mn.z:.0f}) cm "
|
log(f"{name:18} import ok size=({mx.x - mn.x:.0f}, {mx.y - mn.y:.0f}, {mx.z - mn.z:.0f}) cm "
|
||||||
f"min_z={mn.z:.1f} collision=none")
|
f"min_z={mn.z:.1f} collision=none")
|
||||||
return
|
return
|
||||||
trace = (unreal.CollisionTraceFlag.CTF_USE_COMPLEX_AS_SIMPLE
|
# Walkable surfaces block with SIMPLE + COMPLEX collision so the player's
|
||||||
if collision == "complex" else unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX)
|
# capsule always has ground: CFG_USE_SIMPLE_AND_COMPLEX uses the auto-
|
||||||
|
# generated simple collision AND the mesh triangles. (Complex-as-simple
|
||||||
|
# alone only worked if the mesh carried complex collision, which a default
|
||||||
|
# FBX static-mesh import does not.)
|
||||||
|
trace = unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX
|
||||||
try:
|
try:
|
||||||
body = mesh.get_editor_property("body_setup")
|
body = mesh.get_editor_property("body_setup")
|
||||||
body.set_editor_property("collision_trace_flag", trace)
|
body.set_editor_property("collision_trace_flag", trace)
|
||||||
|
|||||||
Reference in new issue
Block a user