Files
gig-line/Game/GigLine/Source/GigLine/GigLineCharacter.h
T
zwitschi 05c1488c00 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.
2026-11-09 10:33:06 +01:00

177 lines
6.0 KiB
C++

// First-person player character for the 0.1.0 walkable Hollywood block.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "GigLineCharacter.generated.h"
class UAnimSequence;
class UCameraComponent;
class UInputAction;
class UInputMappingContext;
class USpringArmComponent;
/**
* Walking first-person character.
*
* Movement is C++ and network-agnostic in the shapes it uses (Enhanced Input ->
* AddMovementInput), so a Blueprint subclass can retune values without touching
* code. Defaults are human-scale: 1.75 m capsule, 1.6 m eye height, 4 m/s walk.
*/
UCLASS()
class GIGLINE_API AGigLineCharacter : public ACharacter
{
GENERATED_BODY()
public:
AGigLineCharacter();
virtual void BeginPlay() override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual void Tick(float DeltaSeconds) override;
protected:
/** Loads the Enhanced Input assets from /Game/Input; called from BeginPlay. */
void LoadInputAssets();
/** Teleports back to the map's PlayerStart and clears velocity. */
void ResetToStart();
void TogglePause();
/** Swaps between the first-person and third-person rigs (V). */
void ToggleCamera();
/**
* Plays the idle or walk clip on the visible body.
*
* Called from the movement tick rather than from input: an animation state
* driven by speed is correct whether the player walked, was pushed, or fell.
* Only switches when the clip changes, because PlayAnimation restarts a clip
* and calling it every frame would freeze the cycle at its first key.
*/
void UpdateBodyAnimation();
/** Velocity above which the walk clip plays, in cm/s (0.1 m/s tolerance). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
float WalkAnimationThreshold = 10.0f;
/**
* 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. */
UPROPERTY()
FVector StartLocation = FVector::ZeroVector;
UPROPERTY()
FRotator StartRotation = FRotator::ZeroRotator;
bool bPaused = false;
/** Moves along the control yaw by the given forward/strafe amounts. */
void ApplyMove(float Forward, float Strafe);
/** Handles input actions to movement; assigned in C++ so the project runs with no assets. */
/** Forward/back and strafe are separate 1D actions, summed into one move. */
void MoveForward(const FInputActionValue& Value);
void MoveStrafe(const FInputActionValue& Value);
void MoveBack(const FInputActionValue& Value);
void MoveLeft(const FInputActionValue& Value);
void Look(const FInputActionValue& Value);
void StartJump();
void StopJump();
/** Mapping context for the gamepad and keyboard. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputMappingContext> MappingContext;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> MoveAction;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> MoveBackAction;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> StrafeAction;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> StrafeLeftAction;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> LookAction;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> JumpAction;
/** Respawn at the start point (R). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> ResetAction;
/** Toggle the pause menu (Escape). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> PauseAction;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
TObjectPtr<UCameraComponent> FirstPersonCamera;
/**
* Third-person boom. Collision testing is on, which is what stops the camera
* from ending up inside a wall: the arm shortens until the camera is clear
* instead of clipping through geometry.
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
TObjectPtr<USpringArmComponent> ThirdPersonArm;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
TObjectPtr<UCameraComponent> ThirdPersonCamera;
/** Boom length in cm: far enough to see the body, close enough to read the street. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
float ThirdPersonBoomLength = 340.0f;
UPROPERTY()
TObjectPtr<UAnimSequence> IdleAnimation;
UPROPERTY()
TObjectPtr<UAnimSequence> WalkAnimation;
/** True while the third-person rig is the active view. */
UPROPERTY(BlueprintReadOnly, Category = "Camera")
bool bThirdPerson = false;
/** Clip currently playing on the body, so UpdateBodyAnimation can diff it. */
UPROPERTY()
TObjectPtr<UAnimSequence> PlayingAnimation;
/** Toggle-camera action (V). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> ToggleCameraAction;
/** Degrees per second per unit of mouse input; defaults to Unreal's usual 45. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
float LookSensitivity = 45.0f;
/** Invert the pitch axis, for players who prefer it. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
bool bInvertLookY = false;
/** Walk speed in metres per second; converted to Unreal units on first use. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float WalkSpeedMetresPerSecond = 4.0f;
};