Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
495424a2b2 | ||
|
|
83cc8f592e |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,7 +9,7 @@ void UHealthComponent::BeginPlay()
|
|||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
|
|
||||||
CurrentHealth = MaxHealth;
|
CurrentHealth = GetEffectiveMaxHealth();
|
||||||
}
|
}
|
||||||
|
|
||||||
float UHealthComponent::GetHealth() const
|
float UHealthComponent::GetHealth() const
|
||||||
@@ -22,18 +22,28 @@ float UHealthComponent::GetMaxHealth() const
|
|||||||
return MaxHealth;
|
return MaxHealth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float UHealthComponent::GetHealthDebuff() const
|
||||||
|
{
|
||||||
|
return HealthDebuff;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UHealthComponent::GetEffectiveMaxHealth() const
|
||||||
|
{
|
||||||
|
return MaxHealth * (1.0f - HealthDebuff);
|
||||||
|
}
|
||||||
|
|
||||||
void UHealthComponent::SetMaxHealth(float NewMaxHealth)
|
void UHealthComponent::SetMaxHealth(float NewMaxHealth)
|
||||||
{
|
{
|
||||||
MaxHealth = FMath::Max(NewMaxHealth, 0.0f);
|
MaxHealth = FMath::Max(NewMaxHealth, 0.0f);
|
||||||
|
|
||||||
CurrentHealth = FMath::Clamp(CurrentHealth, 0.0f, MaxHealth);
|
CurrentHealth = FMath::Clamp(CurrentHealth, 0.0f, GetEffectiveMaxHealth());
|
||||||
}
|
}
|
||||||
|
|
||||||
void UHealthComponent::SetHealth(float NewHealth)
|
void UHealthComponent::SetHealth(float NewHealth)
|
||||||
{
|
{
|
||||||
const float PreviousHealth = CurrentHealth;
|
const float PreviousHealth = CurrentHealth;
|
||||||
|
|
||||||
CurrentHealth = FMath::Clamp(NewHealth, 0.0f, MaxHealth);
|
CurrentHealth = FMath::Clamp(NewHealth, 0.0f, GetEffectiveMaxHealth());
|
||||||
|
|
||||||
OnHealthChanged.Broadcast(CurrentHealth);
|
OnHealthChanged.Broadcast(CurrentHealth);
|
||||||
|
|
||||||
@@ -63,3 +73,17 @@ void UHealthComponent::RemoveHealth(float Amount)
|
|||||||
|
|
||||||
SetHealth(CurrentHealth - Amount);
|
SetHealth(CurrentHealth - Amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UHealthComponent::SetHealthDebuff(float NewHealthDebuff)
|
||||||
|
{
|
||||||
|
HealthDebuff = FMath::Clamp(NewHealthDebuff, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
const float ClampedHealth = FMath::Clamp(CurrentHealth, 0.0f, GetEffectiveMaxHealth());
|
||||||
|
if (!FMath::IsNearlyEqual(ClampedHealth, CurrentHealth))
|
||||||
|
{
|
||||||
|
CurrentHealth = ClampedHealth;
|
||||||
|
OnHealthChanged.Broadcast(CurrentHealth);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnHealthDebuffChanged.Broadcast(HealthDebuff);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "HealthComponent.generated.h"
|
#include "HealthComponent.generated.h"
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthDebuffChanged, float, NewHealthDebuff);
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
|
||||||
|
|
||||||
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
|
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
|
||||||
@@ -23,9 +24,14 @@ public:
|
|||||||
UFUNCTION(BlueprintPure, Category = "Health")
|
UFUNCTION(BlueprintPure, Category = "Health")
|
||||||
float GetHealth() const;
|
float GetHealth() const;
|
||||||
|
|
||||||
|
/** Nominal cap, unaffected by HealthDebuff. Feeds the health bar's total width. */
|
||||||
UFUNCTION(BlueprintPure, Category = "Health")
|
UFUNCTION(BlueprintPure, Category = "Health")
|
||||||
float GetMaxHealth() const;
|
float GetMaxHealth() const;
|
||||||
|
|
||||||
|
/** 0-1 fraction of MaxHealth currently locked/unusable (curses, status effects, ...). */
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Health")
|
||||||
|
float GetHealthDebuff() const;
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Health")
|
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||||
void SetMaxHealth(float NewMaxHealth);
|
void SetMaxHealth(float NewMaxHealth);
|
||||||
|
|
||||||
@@ -38,17 +44,30 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "Health")
|
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||||
void RemoveHealth(float Amount);
|
void RemoveHealth(float Amount);
|
||||||
|
|
||||||
|
/** Sets how much of MaxHealth is currently locked away (0-1). Not driven by anything yet - a future debuff/curse/status-effect system calls this. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||||
|
void SetHealthDebuff(float NewHealthDebuff);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/** MaxHealth minus whatever HealthDebuff currently locks away - what Health is actually clamped against. */
|
||||||
|
float GetEffectiveMaxHealth() const;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Health")
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Health")
|
||||||
float MaxHealth = 100.0f;
|
float MaxHealth = 100.0f;
|
||||||
|
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Health")
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Health")
|
||||||
float CurrentHealth = 100.0f;
|
float CurrentHealth = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Health")
|
||||||
|
float HealthDebuff = 0.0f;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintAssignable, Category = "Health")
|
UPROPERTY(BlueprintAssignable, Category = "Health")
|
||||||
FOnHealthChanged OnHealthChanged;
|
FOnHealthChanged OnHealthChanged;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Health")
|
||||||
|
FOnHealthDebuffChanged OnHealthDebuffChanged;
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category = "Health")
|
UPROPERTY(BlueprintAssignable, Category = "Health")
|
||||||
FOnDeath OnDeath;
|
FOnDeath OnDeath;
|
||||||
};
|
};
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "EnhancedInputSubsystems.h"
|
#include "EnhancedInputSubsystems.h"
|
||||||
#include "InputActionValue.h"
|
#include "InputActionValue.h"
|
||||||
#include "KingshearthLegacy.h"
|
#include "KingshearthLegacy.h"
|
||||||
|
#include "StaminaComponent.h"
|
||||||
|
|
||||||
AKingshearthLegacyCharacter::AKingshearthLegacyCharacter()
|
AKingshearthLegacyCharacter::AKingshearthLegacyCharacter()
|
||||||
{
|
{
|
||||||
@@ -50,6 +51,37 @@ AKingshearthLegacyCharacter::AKingshearthLegacyCharacter()
|
|||||||
// are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
|
// are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyCharacter::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
BaseWalkSpeed = GetCharacterMovement()->MaxWalkSpeed;
|
||||||
|
StaminaComponent = FindComponentByClass<UStaminaComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyCharacter::Tick(float DeltaSeconds)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaSeconds);
|
||||||
|
|
||||||
|
if (!StaminaComponent)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool bIsMoving = GetVelocity().SizeSquared() > FMath::Square(SprintMovementThreshold);
|
||||||
|
const bool bIsGrounded = GetCharacterMovement()->IsMovingOnGround();
|
||||||
|
|
||||||
|
if (bWantsToSprint && bIsMoving && bIsGrounded && StaminaComponent->GetStamina() > 0.0f)
|
||||||
|
{
|
||||||
|
GetCharacterMovement()->MaxWalkSpeed = BaseWalkSpeed * SprintSpeedMultiplier;
|
||||||
|
StaminaComponent->RemoveStamina(SprintStaminaDrainRate * DeltaSeconds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GetCharacterMovement()->MaxWalkSpeed = BaseWalkSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AKingshearthLegacyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
void AKingshearthLegacyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||||
{
|
{
|
||||||
// Set up action bindings
|
// Set up action bindings
|
||||||
@@ -65,6 +97,10 @@ void AKingshearthLegacyCharacter::SetupPlayerInputComponent(UInputComponent* Pla
|
|||||||
|
|
||||||
// Looking
|
// Looking
|
||||||
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AKingshearthLegacyCharacter::Look);
|
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AKingshearthLegacyCharacter::Look);
|
||||||
|
|
||||||
|
// Sprinting
|
||||||
|
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Started, this, &AKingshearthLegacyCharacter::StartSprint);
|
||||||
|
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &AKingshearthLegacyCharacter::StopSprint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -131,3 +167,25 @@ void AKingshearthLegacyCharacter::DoJumpEnd()
|
|||||||
// signal the character to stop jumping
|
// signal the character to stop jumping
|
||||||
StopJumping();
|
StopJumping();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyCharacter::Jump()
|
||||||
|
{
|
||||||
|
const bool bCouldJump = CanJump();
|
||||||
|
|
||||||
|
Super::Jump();
|
||||||
|
|
||||||
|
if (bCouldJump && StaminaComponent)
|
||||||
|
{
|
||||||
|
StaminaComponent->RemoveStamina(JumpStaminaCost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyCharacter::StartSprint()
|
||||||
|
{
|
||||||
|
bWantsToSprint = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AKingshearthLegacyCharacter::StopSprint()
|
||||||
|
{
|
||||||
|
bWantsToSprint = false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
class USpringArmComponent;
|
class USpringArmComponent;
|
||||||
class UCameraComponent;
|
class UCameraComponent;
|
||||||
class UInputAction;
|
class UInputAction;
|
||||||
|
class UStaminaComponent;
|
||||||
struct FInputActionValue;
|
struct FInputActionValue;
|
||||||
|
|
||||||
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
|
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
|
||||||
@@ -49,6 +50,26 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, Category="Input")
|
UPROPERTY(EditAnywhere, Category="Input")
|
||||||
UInputAction* MouseLookAction;
|
UInputAction* MouseLookAction;
|
||||||
|
|
||||||
|
/** Sprint Input Action */
|
||||||
|
UPROPERTY(EditAnywhere, Category="Input")
|
||||||
|
UInputAction* SprintAction;
|
||||||
|
|
||||||
|
/** Walk speed is multiplied by this while sprinting */
|
||||||
|
UPROPERTY(EditAnywhere, Category="Sprint")
|
||||||
|
float SprintSpeedMultiplier = 1.6f;
|
||||||
|
|
||||||
|
/** Stamina drained per second while sprinting */
|
||||||
|
UPROPERTY(EditAnywhere, Category="Sprint")
|
||||||
|
float SprintStaminaDrainRate = 25.0f;
|
||||||
|
|
||||||
|
/** Minimum speed (cm/s) for the character to be considered "moving" for sprint purposes. */
|
||||||
|
UPROPERTY(EditAnywhere, Category="Sprint")
|
||||||
|
float SprintMovementThreshold = 10.0f;
|
||||||
|
|
||||||
|
/** Stamina consumed by each successful jump. */
|
||||||
|
UPROPERTY(EditAnywhere, Category="Stamina")
|
||||||
|
float JumpStaminaCost = 15.0f;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/** Constructor */
|
/** Constructor */
|
||||||
@@ -56,9 +77,15 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void Tick(float DeltaSeconds) override;
|
||||||
|
|
||||||
/** Initialize input action bindings */
|
/** Initialize input action bindings */
|
||||||
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
||||||
|
|
||||||
|
/** Overridden to charge JumpStaminaCost whenever a jump actually starts. */
|
||||||
|
virtual void Jump() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/** Called for movement input */
|
/** Called for movement input */
|
||||||
@@ -67,6 +94,20 @@ protected:
|
|||||||
/** Called for looking input */
|
/** Called for looking input */
|
||||||
void Look(const FInputActionValue& Value);
|
void Look(const FInputActionValue& Value);
|
||||||
|
|
||||||
|
/** Called when the sprint input is pressed */
|
||||||
|
void StartSprint();
|
||||||
|
|
||||||
|
/** Called when the sprint input is released */
|
||||||
|
void StopSprint();
|
||||||
|
|
||||||
|
/** Cached from the CharacterMovementComponent's MaxWalkSpeed in BeginPlay, restored whenever not sprinting. */
|
||||||
|
float BaseWalkSpeed = 0.0f;
|
||||||
|
|
||||||
|
bool bWantsToSprint = false;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UStaminaComponent> StaminaComponent;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/** Handles move inputs from either controls or UI interfaces */
|
/** Handles move inputs from either controls or UI interfaces */
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "Widgets/Input/SVirtualJoystick.h"
|
#include "Widgets/Input/SVirtualJoystick.h"
|
||||||
#include "UI/PlayerHUDWidget.h"
|
#include "UI/PlayerHUDWidget.h"
|
||||||
#include "HealthComponent.h"
|
#include "HealthComponent.h"
|
||||||
|
#include "StaminaComponent.h"
|
||||||
|
|
||||||
void AKingshearthLegacyPlayerController::BeginPlay()
|
void AKingshearthLegacyPlayerController::BeginPlay()
|
||||||
{
|
{
|
||||||
@@ -87,7 +88,7 @@ void AKingshearthLegacyPlayerController::OnPossess(APawn* InPawn)
|
|||||||
|
|
||||||
if (PlayerHUD)
|
if (PlayerHUD)
|
||||||
{
|
{
|
||||||
PlayerHUD->InitializeHUD(InPawn->FindComponentByClass<UHealthComponent>());
|
PlayerHUD->InitializeHUD(InPawn->FindComponentByClass<UHealthComponent>(), InPawn->FindComponentByClass<UStaminaComponent>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#include "StaminaComponent.h"
|
||||||
|
|
||||||
|
UStaminaComponent::UStaminaComponent()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UStaminaComponent::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
CurrentStamina = MaxStamina;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UStaminaComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||||
|
{
|
||||||
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||||
|
|
||||||
|
TimeSinceLastStaminaSpend += DeltaTime;
|
||||||
|
|
||||||
|
const float TimeSinceRegenStarted = TimeSinceLastStaminaSpend - StaminaRegenDelay;
|
||||||
|
if (TimeSinceRegenStarted > 0.0f && CurrentStamina < GetEffectiveMaxStamina())
|
||||||
|
{
|
||||||
|
const float RampedRegenRate = StaminaRegenRampTime > 0.0f
|
||||||
|
? FMath::Min(TimeSinceRegenStarted / StaminaRegenRampTime, 1.0f) * StaminaRegenRate
|
||||||
|
: StaminaRegenRate;
|
||||||
|
|
||||||
|
SetStamina(CurrentStamina + RampedRegenRate * DeltaTime);
|
||||||
|
|
||||||
|
// Fatigue only builds while Stamina is actively regaining - never while draining, never
|
||||||
|
// while idle. It never recovers on its own; only an explicit SetFatigue call does.
|
||||||
|
SetFatigue(Fatigue + FatigueGainRateWhileRegaining * DeltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float UStaminaComponent::GetStamina() const
|
||||||
|
{
|
||||||
|
return CurrentStamina;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UStaminaComponent::GetMaxStamina() const
|
||||||
|
{
|
||||||
|
return MaxStamina;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UStaminaComponent::GetFatigue() const
|
||||||
|
{
|
||||||
|
return Fatigue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UStaminaComponent::GetEffectiveMaxStamina() const
|
||||||
|
{
|
||||||
|
return MaxStamina * (1.0f - Fatigue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UStaminaComponent::SetMaxStamina(float NewMaxStamina)
|
||||||
|
{
|
||||||
|
MaxStamina = FMath::Max(NewMaxStamina, 0.0f);
|
||||||
|
|
||||||
|
CurrentStamina = FMath::Clamp(CurrentStamina, 0.0f, GetEffectiveMaxStamina());
|
||||||
|
}
|
||||||
|
|
||||||
|
void UStaminaComponent::SetStamina(float NewStamina)
|
||||||
|
{
|
||||||
|
const float PreviousStamina = CurrentStamina;
|
||||||
|
|
||||||
|
CurrentStamina = FMath::Clamp(NewStamina, 0.0f, GetEffectiveMaxStamina());
|
||||||
|
|
||||||
|
if (CurrentStamina < PreviousStamina)
|
||||||
|
{
|
||||||
|
TimeSinceLastStaminaSpend = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnStaminaChanged.Broadcast(CurrentStamina);
|
||||||
|
|
||||||
|
// Only trigger the exhaustion signal when crossing from not-empty to empty.
|
||||||
|
if (PreviousStamina > 0.0f && CurrentStamina <= 0.0f)
|
||||||
|
{
|
||||||
|
OnExhausted.Broadcast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UStaminaComponent::AddStamina(float Amount)
|
||||||
|
{
|
||||||
|
if (Amount <= 0.0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetStamina(CurrentStamina + Amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UStaminaComponent::RemoveStamina(float Amount)
|
||||||
|
{
|
||||||
|
if (Amount <= 0.0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetStamina(CurrentStamina - Amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UStaminaComponent::SetFatigue(float NewFatigue)
|
||||||
|
{
|
||||||
|
Fatigue = FMath::Clamp(NewFatigue, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
const float ClampedStamina = FMath::Clamp(CurrentStamina, 0.0f, GetEffectiveMaxStamina());
|
||||||
|
if (!FMath::IsNearlyEqual(ClampedStamina, CurrentStamina))
|
||||||
|
{
|
||||||
|
CurrentStamina = ClampedStamina;
|
||||||
|
OnStaminaChanged.Broadcast(CurrentStamina);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnFatigueChanged.Broadcast(Fatigue);
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "StaminaComponent.generated.h"
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStaminaChanged, float, NewStamina);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFatigueChanged, float, NewFatigue);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnExhausted);
|
||||||
|
|
||||||
|
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
|
||||||
|
class KINGSHEARTHLEGACY_API UStaminaComponent : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UStaminaComponent();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Stamina
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Stamina")
|
||||||
|
float GetStamina() const;
|
||||||
|
|
||||||
|
/** Nominal cap, unaffected by Fatigue. Feeds the stamina bar's total width. */
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Stamina")
|
||||||
|
float GetMaxStamina() const;
|
||||||
|
|
||||||
|
/** 0-1 fraction of MaxStamina currently locked/unusable due to over-extension. */
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Stamina")
|
||||||
|
float GetFatigue() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Stamina")
|
||||||
|
void SetMaxStamina(float NewMaxStamina);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Stamina")
|
||||||
|
void SetStamina(float NewStamina);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Stamina")
|
||||||
|
void AddStamina(float Amount);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Stamina")
|
||||||
|
void RemoveStamina(float Amount);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Fatigue")
|
||||||
|
void SetFatigue(float NewFatigue);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** MaxStamina minus whatever Fatigue currently locks away - what Stamina is actually clamped against. */
|
||||||
|
float GetEffectiveMaxStamina() const;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Stamina")
|
||||||
|
float MaxStamina = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Stamina")
|
||||||
|
float CurrentStamina = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Fatigue")
|
||||||
|
float Fatigue = 0.0f;
|
||||||
|
|
||||||
|
/** Regen rate once fully ramped up, in stamina/sec. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Stamina")
|
||||||
|
float StaminaRegenRate = 20.0f;
|
||||||
|
|
||||||
|
/** Seconds after the last stamina spend before regen starts at all. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Stamina")
|
||||||
|
float StaminaRegenDelay = 1.0f;
|
||||||
|
|
||||||
|
/** Seconds for the regen rate to ramp from 0 up to StaminaRegenRate once regen starts, instead of kicking in at full speed immediately. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Stamina")
|
||||||
|
float StaminaRegenRampTime = 1.5f;
|
||||||
|
|
||||||
|
/** Seconds since Stamina was last reduced by a spend (RemoveStamina/SetStamina decrease). Resets the regen delay/ramp whenever stamina is spent. */
|
||||||
|
float TimeSinceLastStaminaSpend = 0.0f;
|
||||||
|
|
||||||
|
/** Fatigue gained per second while Stamina is actively regaining (ramping back up). Never recovers on its own otherwise - only an explicit SetFatigue call (e.g. a debug key) reduces it. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Fatigue")
|
||||||
|
float FatigueGainRateWhileRegaining = 0.1f;
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Stamina")
|
||||||
|
FOnStaminaChanged OnStaminaChanged;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Fatigue")
|
||||||
|
FOnFatigueChanged OnFatigueChanged;
|
||||||
|
|
||||||
|
/** Broadcast when Stamina is driven from a positive value to empty. */
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Stamina")
|
||||||
|
FOnExhausted OnExhausted;
|
||||||
|
};
|
||||||
@@ -11,16 +11,18 @@ void UHealthBarWidget::InitializeHealthBar(UHealthComponent* InHealthComponent)
|
|||||||
if (HealthComponent)
|
if (HealthComponent)
|
||||||
{
|
{
|
||||||
HealthComponent->OnHealthChanged.RemoveDynamic(this, &UHealthBarWidget::HandleHealthChanged);
|
HealthComponent->OnHealthChanged.RemoveDynamic(this, &UHealthBarWidget::HandleHealthChanged);
|
||||||
|
HealthComponent->OnHealthDebuffChanged.RemoveDynamic(this, &UHealthBarWidget::HandleHealthDebuffChanged);
|
||||||
HealthComponent->OnDeath.RemoveDynamic(this, &UHealthBarWidget::HandleDeath);
|
HealthComponent->OnDeath.RemoveDynamic(this, &UHealthBarWidget::HandleDeath);
|
||||||
}
|
}
|
||||||
|
|
||||||
HealthComponent = InHealthComponent;
|
HealthComponent = InHealthComponent;
|
||||||
|
|
||||||
HealthComponent->OnHealthChanged.AddDynamic(this, &UHealthBarWidget::HandleHealthChanged);
|
HealthComponent->OnHealthChanged.AddDynamic(this, &UHealthBarWidget::HandleHealthChanged);
|
||||||
|
HealthComponent->OnHealthDebuffChanged.AddDynamic(this, &UHealthBarWidget::HandleHealthDebuffChanged);
|
||||||
HealthComponent->OnDeath.AddDynamic(this, &UHealthBarWidget::HandleDeath);
|
HealthComponent->OnDeath.AddDynamic(this, &UHealthBarWidget::HandleDeath);
|
||||||
|
|
||||||
SetStatValue(HealthComponent->GetHealth(), HealthComponent->GetMaxHealth());
|
SetStatValue(HealthComponent->GetHealth(), HealthComponent->GetMaxHealth());
|
||||||
SetMaxHealthDebuff(DefaultMaxHealthDebuffPercent);
|
SetMaxHealthDebuff(HealthComponent->GetHealthDebuff());
|
||||||
}
|
}
|
||||||
|
|
||||||
void UHealthBarWidget::SetMaxHealthDebuff(float InDebuffPercent)
|
void UHealthBarWidget::SetMaxHealthDebuff(float InDebuffPercent)
|
||||||
@@ -36,6 +38,11 @@ void UHealthBarWidget::HandleHealthChanged(float NewHealth)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UHealthBarWidget::HandleHealthDebuffChanged(float NewHealthDebuff)
|
||||||
|
{
|
||||||
|
SetMaxHealthDebuff(NewHealthDebuff);
|
||||||
|
}
|
||||||
|
|
||||||
void UHealthBarWidget::HandleDeath()
|
void UHealthBarWidget::HandleDeath()
|
||||||
{
|
{
|
||||||
OnHealthDepleted();
|
OnHealthDepleted();
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ protected:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleHealthChanged(float NewHealth);
|
void HandleHealthChanged(float NewHealth);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleHealthDebuffChanged(float NewHealthDebuff);
|
||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleDeath();
|
void HandleDeath();
|
||||||
|
|
||||||
@@ -36,8 +39,4 @@ protected:
|
|||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "Health")
|
UPROPERTY(BlueprintReadOnly, Category = "Health")
|
||||||
TObjectPtr<UHealthComponent> HealthComponent;
|
TObjectPtr<UHealthComponent> HealthComponent;
|
||||||
|
|
||||||
/** 0-1 starting max-health debuff applied in InitializeHealthBar, e.g. 0.1 = 10% of max health locked/unusable. Defaults to 0 since this bar reflects a live component. */
|
|
||||||
UPROPERTY(EditDefaultsOnly, Category = "Health")
|
|
||||||
float DefaultMaxHealthDebuffPercent = 0.0f;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
#include "UI/HealthBarWidget.h"
|
#include "UI/HealthBarWidget.h"
|
||||||
#include "UI/StaminaBarWidget.h"
|
#include "UI/StaminaBarWidget.h"
|
||||||
|
|
||||||
void UPlayerHUDWidget::InitializeHUD(UHealthComponent* InHealthComponent)
|
void UPlayerHUDWidget::InitializeHUD(UHealthComponent* InHealthComponent, UStaminaComponent* InStaminaComponent)
|
||||||
{
|
{
|
||||||
if (HealthBar)
|
if (HealthBar)
|
||||||
{
|
{
|
||||||
HealthBar->InitializeHealthBar(InHealthComponent);
|
HealthBar->InitializeHealthBar(InHealthComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// StaminaBar self-initializes from hardcoded defaults in its own NativeConstruct.
|
if (StaminaBar)
|
||||||
|
{
|
||||||
|
StaminaBar->InitializeStaminaBar(InStaminaComponent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
class UHealthBarWidget;
|
class UHealthBarWidget;
|
||||||
class UStaminaBarWidget;
|
class UStaminaBarWidget;
|
||||||
class UHealthComponent;
|
class UHealthComponent;
|
||||||
|
class UStaminaComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top-level player HUD. Owns a health bar and a stamina bar and forwards
|
* Top-level player HUD. Owns a health bar and a stamina bar and forwards
|
||||||
@@ -20,7 +21,7 @@ class UPlayerHUDWidget : public UUserWidget
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
UFUNCTION(BlueprintCallable, Category = "HUD")
|
UFUNCTION(BlueprintCallable, Category = "HUD")
|
||||||
void InitializeHUD(UHealthComponent* InHealthComponent);
|
void InitializeHUD(UHealthComponent* InHealthComponent, UStaminaComponent* InStaminaComponent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||||
|
|||||||
@@ -1,11 +1,39 @@
|
|||||||
#include "StaminaBarWidget.h"
|
#include "StaminaBarWidget.h"
|
||||||
|
#include "StaminaComponent.h"
|
||||||
|
|
||||||
void UStaminaBarWidget::NativeConstruct()
|
void UStaminaBarWidget::InitializeStaminaBar(UStaminaComponent* InStaminaComponent)
|
||||||
{
|
{
|
||||||
Super::NativeConstruct();
|
if (!InStaminaComponent)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SetStatValue(DefaultCurrentStamina, DefaultMaxStamina);
|
if (StaminaComponent)
|
||||||
SetFatigue(DefaultFatigue);
|
{
|
||||||
|
StaminaComponent->OnStaminaChanged.RemoveDynamic(this, &UStaminaBarWidget::HandleStaminaChanged);
|
||||||
|
StaminaComponent->OnFatigueChanged.RemoveDynamic(this, &UStaminaBarWidget::HandleFatigueChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
StaminaComponent = InStaminaComponent;
|
||||||
|
|
||||||
|
StaminaComponent->OnStaminaChanged.AddDynamic(this, &UStaminaBarWidget::HandleStaminaChanged);
|
||||||
|
StaminaComponent->OnFatigueChanged.AddDynamic(this, &UStaminaBarWidget::HandleFatigueChanged);
|
||||||
|
|
||||||
|
SetStatValue(StaminaComponent->GetStamina(), StaminaComponent->GetMaxStamina());
|
||||||
|
SetFatigue(StaminaComponent->GetFatigue());
|
||||||
|
}
|
||||||
|
|
||||||
|
void UStaminaBarWidget::HandleStaminaChanged(float NewStamina)
|
||||||
|
{
|
||||||
|
if (StaminaComponent)
|
||||||
|
{
|
||||||
|
SetStatValue(NewStamina, StaminaComponent->GetMaxStamina());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UStaminaBarWidget::HandleFatigueChanged(float NewFatigue)
|
||||||
|
{
|
||||||
|
SetFatigue(NewFatigue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UStaminaBarWidget::SetFatigue(float NewFatigue)
|
void UStaminaBarWidget::SetFatigue(float NewFatigue)
|
||||||
|
|||||||
@@ -4,16 +4,12 @@
|
|||||||
#include "UI/StatBarWidget.h"
|
#include "UI/StatBarWidget.h"
|
||||||
#include "StaminaBarWidget.generated.h"
|
#include "StaminaBarWidget.generated.h"
|
||||||
|
|
||||||
|
class UStaminaComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stat bar for stamina. Currently self-initializes from hardcoded defaults,
|
* Stat bar bound to a live UStaminaComponent. Call InitializeStaminaBar once the
|
||||||
* since there is no stamina/fatigue gameplay component (DataModel) yet.
|
* component is known (typically from the owning PlayerController on possession),
|
||||||
*
|
* mirroring UHealthBarWidget::InitializeHealthBar.
|
||||||
* TODO: once a UStaminaComponent exists (mirroring UHealthComponent), replace
|
|
||||||
* the hardcoded NativeConstruct initialization with an InitializeStaminaBar(
|
|
||||||
* UStaminaComponent* InStaminaComponent) call bound to that component's own
|
|
||||||
* OnStaminaChanged/OnFatigueChanged delegates, following the exact pattern
|
|
||||||
* used by UHealthBarWidget::InitializeHealthBar. SetFatigue/OnFatigueChanged
|
|
||||||
* below are already shaped for that hookup.
|
|
||||||
*/
|
*/
|
||||||
UCLASS(abstract)
|
UCLASS(abstract)
|
||||||
class UStaminaBarWidget : public UStatBarWidget
|
class UStaminaBarWidget : public UStatBarWidget
|
||||||
@@ -21,6 +17,9 @@ class UStaminaBarWidget : public UStatBarWidget
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Stamina")
|
||||||
|
void InitializeStaminaBar(UStaminaComponent* InStaminaComponent);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Fatigue")
|
UFUNCTION(BlueprintCallable, Category = "Fatigue")
|
||||||
void SetFatigue(float NewFatigue);
|
void SetFatigue(float NewFatigue);
|
||||||
|
|
||||||
@@ -28,22 +27,18 @@ public:
|
|||||||
float GetFatigue() const { return Fatigue; }
|
float GetFatigue() const { return Fatigue; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void NativeConstruct() override;
|
UFUNCTION()
|
||||||
|
void HandleStaminaChanged(float NewStamina);
|
||||||
|
|
||||||
/** Hook for Blueprint-side fatigue visuals (e.g. a darkened "locked" portion of the bar). Unused until a real fatigue value is fed in. */
|
UFUNCTION()
|
||||||
|
void HandleFatigueChanged(float NewFatigue);
|
||||||
|
|
||||||
|
/** Hook for Blueprint-side fatigue visuals (e.g. a darkened "locked" portion of the bar). */
|
||||||
UFUNCTION(BlueprintImplementableEvent, Category = "Fatigue")
|
UFUNCTION(BlueprintImplementableEvent, Category = "Fatigue")
|
||||||
void OnFatigueChanged(float NewFatigue);
|
void OnFatigueChanged(float NewFatigue);
|
||||||
|
|
||||||
/** Placeholder starting values until a real UStaminaComponent DataModel exists. */
|
UPROPERTY(BlueprintReadOnly, Category = "Stamina")
|
||||||
UPROPERTY(EditDefaultsOnly, Category = "Stamina")
|
TObjectPtr<UStaminaComponent> StaminaComponent;
|
||||||
float DefaultCurrentStamina = 75.0f;
|
|
||||||
|
|
||||||
UPROPERTY(EditDefaultsOnly, Category = "Stamina")
|
|
||||||
float DefaultMaxStamina = 100.0f;
|
|
||||||
|
|
||||||
/** 0-1 starting fatigue applied in NativeConstruct, e.g. 0.1 = 10% of the bar locked/unusable. */
|
|
||||||
UPROPERTY(EditDefaultsOnly, Category = "Stamina")
|
|
||||||
float DefaultFatigue = 0.1f;
|
|
||||||
|
|
||||||
/** 0-1 exhaustion amount. Drives the bar's penalty segment via SetPenaltyPercent. */
|
/** 0-1 exhaustion amount. Drives the bar's penalty segment via SetPenaltyPercent. */
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "Fatigue")
|
UPROPERTY(BlueprintReadOnly, Category = "Fatigue")
|
||||||
|
|||||||
Reference in New Issue
Block a user