qa: walkable demo fixed — terrain collision root cause, spawn, and a scripted QA pass #7

Closed
zwitschi wants to merge 13 commits from qa-input-collision-fix-0.1.0 into main
pull from: qa-input-collision-fix-0.1.0
8 changed files with 97 additions and 24 deletions
Showing only changes of commit 2199a546bd - Show all commits

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,9 +7,7 @@
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerController.h"
#include "InputAction.h"
#include "GameFramework/PlayerController.h"
#include "InputMappingContext.h"
#include "Components/CapsuleComponent.h"
#include "Engine/World.h"
#include "TimerManager.h"
@@ -89,9 +87,9 @@ AGigLineCharacter::AGigLineCharacter()
void AGigLineCharacter::LoadInputAssets()
{
// The assets are hard-referenced and loaded in the constructor; this deferred
// call pushes the now-valid mapping context to the local player, after the
// pawn has been possessed and its PlayerController exists.
// 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()))
@@ -100,7 +98,17 @@ void AGigLineCharacter::LoadInputAssets()
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"));
}
}
}
@@ -112,11 +120,39 @@ void AGigLineCharacter::BeginPlay()
StartLocation = GetActorLocation();
StartRotation = GetActorRotation();
// Input assets are hard-referenced in the constructor. Defer the mapping
// context push to just after spawn, when the PlayerController exists and a
// packaged game has finished mounting its pak IoStore.
FTimerHandle InputHandle;
GetWorldTimerManager().SetTimer(InputHandle, this, &AGigLineCharacter::LoadInputAssets, 1.0f, false);
// 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 1280 720"));
}
}, 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: worldTime=%.2f Z=%.0f grav=%.1f fall=%d pawn=%s"),
GetWorld() ? GetWorld()->GetTimeSeconds() : -1.f,
GetActorLocation().Z,
M ? M->GetGravityZ() : 0.f,
(M && M->IsFalling()) ? 1 : 0,
*GetName());
}, 1.0f, true);
}
void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
@@ -130,6 +166,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)
@@ -165,6 +205,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
@@ -191,6 +237,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);
}
@@ -212,11 +260,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);
@@ -224,6 +277,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;
};
+8 -4
View File
@@ -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.