Implement sprinting

This commit is contained in:
2026-05-15 12:51:23 -07:00
parent cd8de0f906
commit 9eed8c5483
8 changed files with 286 additions and 3 deletions
+99 -1
View File
@@ -16,9 +16,12 @@
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"
#include "AgrarianGame.h"
#include "Net/UnrealNetwork.h"
AAgrarianGameCharacter::AAgrarianGameCharacter()
{
PrimaryActorTick.bCanEverTick = true;
// Set size for collision capsule
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
@@ -35,7 +38,7 @@ AAgrarianGameCharacter::AAgrarianGameCharacter()
// instead of recompiling to adjust them
GetCharacterMovement()->JumpZVelocity = 500.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->MaxWalkSpeed = 500.f;
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f;
@@ -60,6 +63,37 @@ AAgrarianGameCharacter::AAgrarianGameCharacter()
// are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
}
void AAgrarianGameCharacter::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (!HasAuthority() || !bWantsToSprint)
{
return;
}
if (!CanSprint())
{
SetWantsToSprint(false);
return;
}
if (GetVelocity().SizeSquared2D() > KINDA_SMALL_NUMBER && SurvivalComponent)
{
SurvivalComponent->SpendStamina(SprintStaminaCostPerSecond * DeltaSeconds);
if (!CanSprint())
{
SetWantsToSprint(false);
}
}
}
void AAgrarianGameCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AAgrarianGameCharacter, bWantsToSprint);
}
void AAgrarianGameCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
// Set up action bindings
@@ -81,6 +115,13 @@ void AAgrarianGameCharacter::SetupPlayerInputComponent(UInputComponent* PlayerIn
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &AAgrarianGameCharacter::Interact);
}
if (SprintAction)
{
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Started, this, &AAgrarianGameCharacter::StartSprint);
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &AAgrarianGameCharacter::StopSprint);
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Canceled, this, &AAgrarianGameCharacter::StopSprint);
}
if (ToggleCameraAction)
{
EnhancedInputComponent->BindAction(ToggleCameraAction, ETriggerEvent::Started, this, &AAgrarianGameCharacter::ToggleCameraPerspective);
@@ -115,6 +156,16 @@ void AAgrarianGameCharacter::Interact()
TryInteract();
}
void AAgrarianGameCharacter::StartSprint()
{
SetWantsToSprint(true);
}
void AAgrarianGameCharacter::StopSprint()
{
SetWantsToSprint(false);
}
void AAgrarianGameCharacter::ToggleCameraPerspective()
{
SetFirstPersonCamera(!bFirstPersonCamera);
@@ -155,6 +206,48 @@ void AAgrarianGameCharacter::SetFirstPersonCamera(bool bEnableFirstPerson)
}
}
void AAgrarianGameCharacter::SetWantsToSprint(bool bNewWantsToSprint)
{
const bool bAllowedSprintIntent = bNewWantsToSprint && CanSprint();
if (bWantsToSprint == bAllowedSprintIntent)
{
return;
}
bWantsToSprint = bAllowedSprintIntent;
ApplyMovementSpeed();
if (!HasAuthority())
{
ServerSetWantsToSprint(bAllowedSprintIntent);
}
}
bool AAgrarianGameCharacter::CanSprint() const
{
return SurvivalComponent
&& SurvivalComponent->IsAlive()
&& SurvivalComponent->Survival.Stamina > MinSprintStamina;
}
bool AAgrarianGameCharacter::IsSprinting() const
{
return bWantsToSprint && CanSprint();
}
void AAgrarianGameCharacter::ApplyMovementSpeed()
{
if (UCharacterMovementComponent* MovementComponent = GetCharacterMovement())
{
MovementComponent->MaxWalkSpeed = IsSprinting() ? SprintSpeed : WalkSpeed;
}
}
void AAgrarianGameCharacter::OnRep_SprintState()
{
ApplyMovementSpeed();
}
void AAgrarianGameCharacter::DoMove(float Right, float Forward)
{
if (GetController() != nullptr)
@@ -240,6 +333,11 @@ void AAgrarianGameCharacter::TryInteract()
}
}
void AAgrarianGameCharacter::ServerSetWantsToSprint_Implementation(bool bNewWantsToSprint)
{
SetWantsToSprint(bNewWantsToSprint);
}
void AAgrarianGameCharacter::ServerInteract_Implementation(AActor* TargetActor)
{
if (!TargetActor || !TargetActor->GetClass()->ImplementsInterface(UAgrarianInteractable::StaticClass()))
@@ -73,6 +73,10 @@ protected:
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* InteractAction;
/** Hold to sprint while stamina allows it. */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* SprintAction;
/** Toggle between third-person and first-person camera views. */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* ToggleCameraAction;
@@ -81,6 +85,22 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Interaction", meta = (ClampMin = "100"))
float InteractionDistance = 450.0f;
/** Baseline movement speed before sprint, skill, injury, load, and terrain modifiers. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Movement", meta = (ClampMin = "0"))
float WalkSpeed = 500.0f;
/** Short-burst movement speed used by the first sprinting pass. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Movement", meta = (ClampMin = "0"))
float SprintSpeed = 750.0f;
/** Stamina spent each second while sprinting and moving. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Movement", meta = (ClampMin = "0"))
float SprintStaminaCostPerSecond = 18.0f;
/** Minimum stamina required to start or continue sprinting. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Movement", meta = (ClampMin = "0", ClampMax = "100"))
float MinSprintStamina = 5.0f;
/** Third-person spring arm distance used when returning from first person. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Camera", meta = (ClampMin = "0"))
float ThirdPersonCameraDistance = 400.0f;
@@ -93,6 +113,10 @@ protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Agrarian|Camera", meta = (AllowPrivateAccess = "true"))
bool bFirstPersonCamera = false;
/** Replicated player intent to sprint; actual sprinting also depends on stamina and alive state. */
UPROPERTY(ReplicatedUsing = OnRep_SprintState, VisibleAnywhere, BlueprintReadOnly, Category="Agrarian|Movement", meta = (AllowPrivateAccess = "true"))
bool bWantsToSprint = false;
public:
/** Constructor */
@@ -100,6 +124,9 @@ public:
protected:
virtual void Tick(float DeltaSeconds) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/** Initialize input action bindings */
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
@@ -114,12 +141,30 @@ protected:
/** Called for interaction input */
void Interact();
/** Called when sprint input is pressed. */
void StartSprint();
/** Called when sprint input is released. */
void StopSprint();
/** Called for camera perspective toggle input */
void ToggleCameraPerspective();
/** Applies local camera presentation state. */
void SetFirstPersonCamera(bool bEnableFirstPerson);
/** Applies the requested sprint intent locally and, when needed, on the server. */
void SetWantsToSprint(bool bNewWantsToSprint);
/** Returns true when the character has enough survival state to sprint. */
bool CanSprint() const;
/** Applies current walk or sprint speed to character movement. */
void ApplyMovementSpeed();
UFUNCTION()
void OnRep_SprintState();
public:
/** Handles move inputs from either controls or UI interfaces */
@@ -146,10 +191,18 @@ public:
UFUNCTION(BlueprintPure, Category="Agrarian|Camera")
bool IsFirstPersonCamera() const { return bFirstPersonCamera; }
/** Returns true when sprint intent and current stamina allow sprinting. */
UFUNCTION(BlueprintPure, Category="Agrarian|Movement")
bool IsSprinting() const;
/** Server-authoritative interaction entry point. */
UFUNCTION(Server, Reliable)
void ServerInteract(AActor* TargetActor);
/** Server-authoritative sprint intent update. */
UFUNCTION(Server, Reliable)
void ServerSetWantsToSprint(bool bNewWantsToSprint);
public:
/** Returns CameraBoom subobject **/