Compare commits
10
Commits
v0.2.0
...
055944fd33
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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" \
|
||||
-unattended
|
||||
# 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.
|
||||
|
||||
## Headless content checks
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
[/Script/EngineSettings.GameMapsSettings]
|
||||
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]
|
||||
; Dynamic-only lighting. With static lighting allowed, the stationary sun and
|
||||
|
||||
@@ -7,9 +7,14 @@ 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
|
||||
; 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,9 @@
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "InputAction.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "InputMappingContext.h"
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
AGigLineCharacter::AGigLineCharacter()
|
||||
{
|
||||
@@ -39,51 +40,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<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()
|
||||
{
|
||||
auto LoadSoft = [](const TCHAR* Path) -> TObjectPtr<UObject>
|
||||
{
|
||||
return LoadObject<UObject>(nullptr, Path);
|
||||
};
|
||||
MappingContext = Cast<UInputMappingContext>(LoadSoft(TEXT("/Game/Input/IMC_Default.IMC_Default")));
|
||||
MoveAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Move.IA_Move")));
|
||||
StrafeAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Strafe.IA_Strafe")));
|
||||
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")));
|
||||
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 pushes
|
||||
// the now-valid mapping context to the local player's subsystem; called from
|
||||
// SetupPlayerInputComponent at possession, when the controller exists.
|
||||
if (MappingContext)
|
||||
{
|
||||
if (const APlayerController* PC = Cast<APlayerController>(GetController()))
|
||||
@@ -92,11 +98,77 @@ void AGigLineCharacter::BeginPlay()
|
||||
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
|
||||
{
|
||||
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();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||
{
|
||||
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
||||
@@ -108,6 +180,10 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
|
||||
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
|
||||
// which handler runs, not by a modifier in the asset.
|
||||
if (MoveAction)
|
||||
@@ -143,6 +219,12 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
|
||||
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
|
||||
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
|
||||
@@ -169,6 +251,8 @@ namespace
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -190,11 +274,16 @@ void AGigLineCharacter::MoveLeft(const FInputActionValue& Value)
|
||||
void AGigLineCharacter::ApplyMove(float Forward, float Strafe)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// Forward/right on the controller yaw keeps the capsule upright on slopes.
|
||||
const FRotator YawRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f);
|
||||
const FVector ForwardDir = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
||||
@@ -202,6 +291,8 @@ void AGigLineCharacter::ApplyMove(float Forward, float Strafe)
|
||||
|
||||
AddMovementInput(ForwardDir, Forward);
|
||||
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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "GigLineGameModeBase.h"
|
||||
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GigLineCharacter.h"
|
||||
#include "GigLineHUD.h"
|
||||
|
||||
@@ -9,3 +10,12 @@ AGigLineGameModeBase::AGigLineGameModeBase()
|
||||
// ODbL attribution must be visible in the running demo, not only in docs.
|
||||
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:
|
||||
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,16 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
- 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 +209,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.
|
||||
- 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.
|
||||
## 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).
|
||||
@@ -89,10 +89,10 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep
|
||||
- [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`).
|
||||
- [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`).
|
||||
- [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.
|
||||
- [-] Collision, reset, and pause functionality. Reset (**R**) and pause (**Escape**) are 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 now spawns at `(-13336, -1150, 600)`, lands on the road at Z 456 with the ground probe reporting `Road_Hollywood` at 366. Walking the block, reset and pause still need a human at the keyboard.
|
||||
- [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] 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.
|
||||
|
||||
+38
-11
@@ -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")
|
||||
@@ -301,13 +301,33 @@ def build():
|
||||
mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}")
|
||||
if mesh is None:
|
||||
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}")
|
||||
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,13 +382,20 @@ 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.
|
||||
# CRITICAL: the terrain is only carved inside the street corridor, and
|
||||
# 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))
|
||||
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
|
||||
# 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
|
||||
# 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.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)
|
||||
|
||||
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
|
||||
# second action for strafe, both reporting on the value's single component.
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
# 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.force_front_x_axis = False
|
||||
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
|
||||
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 "
|
||||
f"min_z={mn.z:.1f} collision=none")
|
||||
return
|
||||
trace = (unreal.CollisionTraceFlag.CTF_USE_COMPLEX_AS_SIMPLE
|
||||
if collision == "complex" else unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX)
|
||||
# Walkable surfaces block with SIMPLE + COMPLEX collision so the player's
|
||||
# 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:
|
||||
body = mesh.get_editor_property("body_setup")
|
||||
body.set_editor_property("collision_trace_flag", trace)
|
||||
|
||||
Reference in new issue
Block a user