feat(camera): third-person rig, V toggle, visible body
One pawn, both rigs (ADR-9): a skeletal body hidden in first person, a collision-tested spring arm, and a toggle that swaps the active camera and the body's visibility. Shipped default stays first person; -thirdperson starts the pawn in the third-person rig so the review shot is reproducible, and the screenshot run passes it. Three defects found and two fixed: - The skin material had no SkeletalMesh usage flag, so Unreal silently substituted the default grey. That is why the body rendered as clay. - render_screenshots.sh still called ./Engine/... on this branch (the fix lives on the facade branch and never reached main); applied here so the shots generate. - OPEN: the idle clip is bound and PlayAnimation is called - the run logs the clip name - but the body still renders in bind pose, arms out. Recorded with that evidence in TODO.md rather than called done. Shot 5 (demo-third-person) added and documented; camera collision is implemented but unproven, since the spawn has open space behind it.
This commit is contained in:
1 parent
96238bdb6a
commit
05c1488c00
49 files changed
+333
-65
No files matched your search
@@ -1,6 +1,9 @@
|
||||
#include "GigLineCharacter.h"
|
||||
|
||||
#include "Animation/AnimSequence.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "EnhancedInputComponent.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
@@ -28,7 +31,7 @@ static TAutoConsoleVariable<int32> CVarGigLineSetupLogs(
|
||||
|
||||
AGigLineCharacter::AGigLineCharacter()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = false;
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
// 1.75 m tall, 0.42 m wide, human scale in Unreal units (1 m = 100 uu).
|
||||
GetCapsuleComponent()->InitCapsuleSize(42.0f, 87.5f);
|
||||
@@ -51,9 +54,59 @@ AGigLineCharacter::AGigLineCharacter()
|
||||
FirstPersonCamera->SetRelativeLocation(FVector(0.0f, 0.0f, 72.5f)); // eye height 1.60 m
|
||||
FirstPersonCamera->bUsePawnControlRotation = true;
|
||||
|
||||
// An eye-level mesh is not needed for 0.1.0; the body is a capsule with a camera.
|
||||
GetMesh()->SetHiddenInGame(true);
|
||||
// Visible body (0.2.0). The animated import carries its own skeleton, so the
|
||||
// clips resolve without a retarget step; it is hidden while the first-person
|
||||
// camera is active and shown in third person.
|
||||
static ConstructorHelpers::FObjectFinder<USkeletalMesh> BodyFinder(
|
||||
TEXT("/Game/Test/Character/Character_Anim.Character_Anim"));
|
||||
if (BodyFinder.Succeeded())
|
||||
{
|
||||
GetMesh()->SetSkeletalMesh(BodyFinder.Object);
|
||||
}
|
||||
// The animated import carries no material of its own; without this the body is
|
||||
// grey clay. The warm skin material was created by the character import.
|
||||
static ConstructorHelpers::FObjectFinder<UMaterialInterface> SkinFinder(
|
||||
TEXT("/Game/Test/Character/M_Character_Export.M_Character_Export"));
|
||||
if (SkinFinder.Succeeded())
|
||||
{
|
||||
GetMesh()->SetMaterial(0, SkinFinder.Object);
|
||||
}
|
||||
static ConstructorHelpers::FObjectFinder<UAnimSequence> IdleFinder(
|
||||
TEXT("/Game/Test/Character/Character_AnimHuman_rig_GigLine_Idle.Character_AnimHuman_rig_GigLine_Idle"));
|
||||
if (IdleFinder.Succeeded())
|
||||
{
|
||||
IdleAnimation = IdleFinder.Object;
|
||||
}
|
||||
static ConstructorHelpers::FObjectFinder<UAnimSequence> WalkFinder(
|
||||
TEXT("/Game/Test/Character/Character_AnimHuman_rig_GigLine_Walk.Character_AnimHuman_rig_GigLine_Walk"));
|
||||
if (WalkFinder.Succeeded())
|
||||
{
|
||||
WalkAnimation = WalkFinder.Object;
|
||||
}
|
||||
GetMesh()->SetRelativeLocation(FVector(0.0f, 0.0f, -87.5f));
|
||||
GetMesh()->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f));
|
||||
GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
GetMesh()->SetHiddenInGame(true);
|
||||
|
||||
// Third-person boom. bDoCollisionTest is the camera-collision feature: the arm
|
||||
// shortens when something is between the pivot and the desired camera position.
|
||||
ThirdPersonArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("ThirdPersonArm"));
|
||||
ThirdPersonArm->SetupAttachment(GetCapsuleComponent());
|
||||
ThirdPersonArm->SetRelativeLocation(FVector(0.0f, 0.0f, 60.0f));
|
||||
ThirdPersonArm->TargetArmLength = ThirdPersonBoomLength;
|
||||
ThirdPersonArm->bUsePawnControlRotation = true;
|
||||
ThirdPersonArm->bDoCollisionTest = true;
|
||||
ThirdPersonArm->ProbeSize = 12.0f;
|
||||
ThirdPersonArm->ProbeChannel = ECollisionChannel::ECC_Camera;
|
||||
ThirdPersonArm->bEnableCameraLag = true;
|
||||
ThirdPersonArm->CameraLagSpeed = 8.0f;
|
||||
ThirdPersonArm->SetAutoActivate(false);
|
||||
|
||||
ThirdPersonCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("ThirdPersonCamera"));
|
||||
ThirdPersonCamera->SetupAttachment(ThirdPersonArm, USpringArmComponent::SocketName);
|
||||
ThirdPersonCamera->bUsePawnControlRotation = false;
|
||||
ThirdPersonCamera->SetAutoActivate(false);
|
||||
ThirdPersonCamera->SetFieldOfView(90.0f);
|
||||
|
||||
// Hard-reference the Enhanced Input assets with ConstructorHelpers so the
|
||||
// cooker always includes them and they load during CDO construction. Runtime
|
||||
@@ -78,6 +131,8 @@ AGigLineCharacter::AGigLineCharacter()
|
||||
TEXT("/Game/Input/IA_Jump.IA_Jump"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> ResetF(
|
||||
TEXT("/Game/Input/IA_Reset.IA_Reset"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> CamF(
|
||||
TEXT("/Game/Input/IA_ToggleCamera.IA_ToggleCamera"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> PauseF(
|
||||
TEXT("/Game/Input/IA_Pause.IA_Pause"));
|
||||
|
||||
@@ -89,6 +144,7 @@ AGigLineCharacter::AGigLineCharacter()
|
||||
LookAction = LookF.Object;
|
||||
JumpAction = JumpF.Object;
|
||||
ResetAction = ResetF.Object;
|
||||
ToggleCameraAction = CamF.Object;
|
||||
PauseAction = PauseF.Object;
|
||||
|
||||
if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction
|
||||
@@ -138,6 +194,23 @@ void AGigLineCharacter::BeginPlay()
|
||||
StartLocation = GetActorLocation();
|
||||
StartRotation = GetActorRotation();
|
||||
|
||||
// `-thirdperson` starts the pawn in the third-person rig. The first-person view
|
||||
// stays the default (owner decision for 0.1.0), but the screenshot run needs the
|
||||
// body visible without a human pressing V, so the review is reproducible.
|
||||
if (CVarGigLineSetupLogs.GetValueOnGameThread())
|
||||
{
|
||||
UE_LOG(LogTemp, Log,
|
||||
TEXT("GigLine: BeginPlay thirdperson=%d body=%s idle=%s walk=%s"),
|
||||
FParse::Param(FCommandLine::Get(), TEXT("thirdperson")) ? 1 : 0,
|
||||
GetMesh()->GetSkeletalMeshAsset() ? TEXT("ok") : TEXT("nil"),
|
||||
IdleAnimation ? TEXT("ok") : TEXT("nil"),
|
||||
WalkAnimation ? TEXT("ok") : TEXT("nil"));
|
||||
}
|
||||
if (FParse::Param(FCommandLine::Get(), TEXT("thirdperson")))
|
||||
{
|
||||
ToggleCamera();
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -557,6 +630,10 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
|
||||
{
|
||||
Input->BindAction(PauseAction, ETriggerEvent::Started, this, &AGigLineCharacter::TogglePause);
|
||||
}
|
||||
if (ToggleCameraAction)
|
||||
{
|
||||
Input->BindAction(ToggleCameraAction, ETriggerEvent::Started, this, &AGigLineCharacter::ToggleCamera);
|
||||
}
|
||||
if (JumpAction)
|
||||
{
|
||||
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
|
||||
@@ -644,6 +721,68 @@ void AGigLineCharacter::Look(const FInputActionValue& Value)
|
||||
AddControllerPitchInput(Axis.Y * LookSensitivity * (bInvertLookY ? -1.0f : 1.0f));
|
||||
}
|
||||
|
||||
void AGigLineCharacter::Tick(float DeltaSeconds)
|
||||
{
|
||||
Super::Tick(DeltaSeconds);
|
||||
// Speed-driven animation state: correct whether the player walked, was pushed,
|
||||
// or fell, and independent of which input produced the movement.
|
||||
UpdateBodyAnimation();
|
||||
}
|
||||
|
||||
void AGigLineCharacter::ToggleCamera()
|
||||
{
|
||||
// Both rigs exist from construction; the toggle only swaps which one is active
|
||||
// and whether the body is drawn. Deactivating the other camera matters: with
|
||||
// two active cameras the view is whichever was registered last.
|
||||
bThirdPerson = !bThirdPerson;
|
||||
FirstPersonCamera->SetActive(!bThirdPerson);
|
||||
ThirdPersonCamera->SetActive(bThirdPerson);
|
||||
GetMesh()->SetHiddenInGame(!bThirdPerson);
|
||||
if (bThirdPerson)
|
||||
{
|
||||
UpdateBodyAnimation();
|
||||
}
|
||||
if (CVarGigLineSetupLogs.GetValueOnGameThread())
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("GigLine: camera %s"), bThirdPerson ? TEXT("third person") : TEXT("first person"));
|
||||
}
|
||||
}
|
||||
|
||||
void AGigLineCharacter::UpdateBodyAnimation()
|
||||
{
|
||||
if (!bThirdPerson || !GetMesh())
|
||||
{
|
||||
return;
|
||||
}
|
||||
const bool bMoving = GetVelocity().Size2D() > WalkAnimationThreshold;
|
||||
UAnimSequence* Wanted = bMoving ? WalkAnimation.Get() : IdleAnimation.Get();
|
||||
if (!Wanted)
|
||||
{
|
||||
// Logged once: a missing clip presents as an A-posed body, which looks like
|
||||
// an animation problem rather than a missing asset reference.
|
||||
static bool bWarnedMissingClip = false;
|
||||
if (!bWarnedMissingClip)
|
||||
{
|
||||
bWarnedMissingClip = true;
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("GigLine: body clip missing (idle=%s walk=%s) - body stays in bind pose"),
|
||||
IdleAnimation ? TEXT("ok") : TEXT("nil"),
|
||||
WalkAnimation ? TEXT("ok") : TEXT("nil"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Wanted == PlayingAnimation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
PlayingAnimation = Wanted;
|
||||
if (CVarGigLineSetupLogs.GetValueOnGameThread())
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("GigLine: playing body clip %s"), *Wanted->GetName());
|
||||
}
|
||||
GetMesh()->PlayAnimation(Wanted, true);
|
||||
}
|
||||
|
||||
void AGigLineCharacter::ResetToStart()
|
||||
{
|
||||
// Reset is a level-restart in miniature: put the body back where it spawned,
|
||||
|
||||
Reference in new issue
Block a user