From 83cc8f592ed01e4ed3bb781b67ee9a2de8f5d62d Mon Sep 17 00:00:00 2001 From: Dolobarsch Date: Thu, 27 Aug 2026 23:43:11 +0200 Subject: [PATCH] =?UTF-8?q?Sprint-=20und=20Ausdauermechanik=20hinzugef?= =?UTF-8?q?=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Sprint-Funktionalität wurde in `AKingshearthLegacyCharacter` implementiert, einschließlich neuer Methoden (`StartSprint`, `StopSprint`, `Tick`) und Eigenschaften (`SprintSpeedMultiplier`, `SprintStaminaDrainRate`, etc.). Die Eingabeverarbeitung wurde entsprechend erweitert. Eine neue `UStaminaComponent` wurde hinzugefügt, die Ausdauer, Fatigue und Regeneration verwaltet. Ereignisse wie `OnStaminaChanged` und `OnExhausted` ermöglichen die Überwachung von Änderungen. Die Benutzeroberfläche wurde angepasst: `UPlayerHUDWidget` und `UStaminaBarWidget` unterstützen nun die Anzeige der Ausdauer. Alte, fest codierte Werte wurden entfernt, und die Widgets sind nun dynamisch an die `StaminaComponent` gebunden. Zusätzlich wurden Änderungen an `.uasset`-Dateien vorgenommen, um die neuen Mechaniken in Unreal Engine zu unterstützen. --- Content/Input/Actions/IA_Sprint.uasset | Bin 0 -> 1164 bytes Content/Input/IMC_Default.uasset | Bin 7787 -> 8320 bytes .../Blueprints/BP_ThirdPersonCharacter.uasset | Bin 98358 -> 110137 bytes .../KingshearthLegacyCharacter.cpp | 59 ++++++++- .../KingshearthLegacyCharacter.h | 43 ++++++- .../KingshearthLegacyPlayerController.cpp | 3 +- Source/KingshearthLegacy/StaminaComponent.cpp | 115 ++++++++++++++++++ Source/KingshearthLegacy/StaminaComponent.h | 93 ++++++++++++++ .../KingshearthLegacy/UI/PlayerHUDWidget.cpp | 7 +- Source/KingshearthLegacy/UI/PlayerHUDWidget.h | 3 +- .../KingshearthLegacy/UI/StaminaBarWidget.cpp | 36 +++++- .../KingshearthLegacy/UI/StaminaBarWidget.h | 37 +++--- 12 files changed, 365 insertions(+), 31 deletions(-) create mode 100644 Content/Input/Actions/IA_Sprint.uasset create mode 100644 Source/KingshearthLegacy/StaminaComponent.cpp create mode 100644 Source/KingshearthLegacy/StaminaComponent.h diff --git a/Content/Input/Actions/IA_Sprint.uasset b/Content/Input/Actions/IA_Sprint.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c163c91f792243677830bc79717f8f31bbcca4da GIT binary patch literal 1164 zcmX@utTpfZ|Ns9Jm>C$jm>3v-0cjA(2gLgBiMgrzo_PhOCHjuZC7Jno#rmF(@xcW}nRz8Z zc?JefAZ`X30R)ji+Z2JcnSqnBg@u!`lclATsk5Phsild5g^{zPiKU^Xle38nTro)B zJ)kBaV(0@AK;Q>;E{Fyh2;&C<`5>GIl@9~bt%YLen3r#GUFY!b%H!pARv`008008c z1`eRPuS^n|g@8P5?NC=!?V!}0)WqUcT~j>^usl$L#S@1-P!0^HmF>v@F?xXZMF4{f z83051KPU)6PLDy7V+Hd4^7B#|zAzuJpG@R9PjU2rMd5qDv1s8bApP zWQ!j#$&{r+BG48|CsHEN1IwxtG*cbykg&v@($tX3f>dBGVPJ5|&j)6VJa9UOMFdb7 l6&wVXE7QL%ZP2qmauMV%8B}S8vU?VRET{Hs8bHNC>H+5p?G69{ literal 0 HcmV?d00001 diff --git a/Content/Input/IMC_Default.uasset b/Content/Input/IMC_Default.uasset index 4dddf94c3e47016fcd802444fa67c72b2adc6f04..57c1ef7cf0146eb57455fed98bd6c4580cd1a2ca 100644 GIT binary patch literal 8320 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 Z81pYsT~+p{<<#s&Lyo+c%YTap{a?ux`;q_v diff --git a/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset b/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset index ce3b0a3bffc1aeab5a4581409d933e7a1e42852a..f1874949ea7ee335003d7c97c764a1100d3c4961 100644 GIT binary patch delta 30024 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 HQJ4P%5uH)n delta 24697 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 diff --git a/Source/KingshearthLegacy/KingshearthLegacyCharacter.cpp b/Source/KingshearthLegacy/KingshearthLegacyCharacter.cpp index 9519f1b..4f80a44 100644 --- a/Source/KingshearthLegacy/KingshearthLegacyCharacter.cpp +++ b/Source/KingshearthLegacy/KingshearthLegacyCharacter.cpp @@ -11,6 +11,7 @@ #include "EnhancedInputSubsystems.h" #include "InputActionValue.h" #include "KingshearthLegacy.h" +#include "StaminaComponent.h" AKingshearthLegacyCharacter::AKingshearthLegacyCharacter() { @@ -50,11 +51,41 @@ AKingshearthLegacyCharacter::AKingshearthLegacyCharacter() // 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(); +} + +void AKingshearthLegacyCharacter::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + if (!StaminaComponent) + { + return; + } + + const bool bIsMoving = GetVelocity().SizeSquared() > FMath::Square(SprintMovementThreshold); + + if (bWantsToSprint && bIsMoving && StaminaComponent->GetStamina() > 0.0f) + { + GetCharacterMovement()->MaxWalkSpeed = BaseWalkSpeed * SprintSpeedMultiplier; + StaminaComponent->RemoveStamina(SprintStaminaDrainRate * DeltaSeconds); + } + else + { + GetCharacterMovement()->MaxWalkSpeed = BaseWalkSpeed; + } +} + void AKingshearthLegacyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { // Set up action bindings if (UEnhancedInputComponent* EnhancedInputComponent = Cast(PlayerInputComponent)) { - + // Jumping EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump); EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping); @@ -65,6 +96,10 @@ void AKingshearthLegacyCharacter::SetupPlayerInputComponent(UInputComponent* Pla // Looking 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 { @@ -131,3 +166,25 @@ void AKingshearthLegacyCharacter::DoJumpEnd() // signal the character to stop jumping 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; +} diff --git a/Source/KingshearthLegacy/KingshearthLegacyCharacter.h b/Source/KingshearthLegacy/KingshearthLegacyCharacter.h index 91cec80..20cf8e6 100644 --- a/Source/KingshearthLegacy/KingshearthLegacyCharacter.h +++ b/Source/KingshearthLegacy/KingshearthLegacyCharacter.h @@ -10,6 +10,7 @@ class USpringArmComponent; class UCameraComponent; class UInputAction; +class UStaminaComponent; struct FInputActionValue; DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All); @@ -49,16 +50,42 @@ protected: UPROPERTY(EditAnywhere, Category="Input") 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: /** Constructor */ - AKingshearthLegacyCharacter(); + AKingshearthLegacyCharacter(); protected: + virtual void BeginPlay() override; + virtual void Tick(float DeltaSeconds) override; + /** Initialize input action bindings */ virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + /** Overridden to charge JumpStaminaCost whenever a jump actually starts. */ + virtual void Jump() override; + protected: /** Called for movement input */ @@ -67,6 +94,20 @@ protected: /** Called for looking input */ 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 StaminaComponent; + public: /** Handles move inputs from either controls or UI interfaces */ diff --git a/Source/KingshearthLegacy/KingshearthLegacyPlayerController.cpp b/Source/KingshearthLegacy/KingshearthLegacyPlayerController.cpp index f53545f..0d87dbd 100644 --- a/Source/KingshearthLegacy/KingshearthLegacyPlayerController.cpp +++ b/Source/KingshearthLegacy/KingshearthLegacyPlayerController.cpp @@ -10,6 +10,7 @@ #include "Widgets/Input/SVirtualJoystick.h" #include "UI/PlayerHUDWidget.h" #include "HealthComponent.h" +#include "StaminaComponent.h" void AKingshearthLegacyPlayerController::BeginPlay() { @@ -87,7 +88,7 @@ void AKingshearthLegacyPlayerController::OnPossess(APawn* InPawn) if (PlayerHUD) { - PlayerHUD->InitializeHUD(InPawn->FindComponentByClass()); + PlayerHUD->InitializeHUD(InPawn->FindComponentByClass(), InPawn->FindComponentByClass()); } } diff --git a/Source/KingshearthLegacy/StaminaComponent.cpp b/Source/KingshearthLegacy/StaminaComponent.cpp new file mode 100644 index 0000000..b3aaca4 --- /dev/null +++ b/Source/KingshearthLegacy/StaminaComponent.cpp @@ -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); +} diff --git a/Source/KingshearthLegacy/StaminaComponent.h b/Source/KingshearthLegacy/StaminaComponent.h new file mode 100644 index 0000000..9ad6d34 --- /dev/null +++ b/Source/KingshearthLegacy/StaminaComponent.h @@ -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; +}; diff --git a/Source/KingshearthLegacy/UI/PlayerHUDWidget.cpp b/Source/KingshearthLegacy/UI/PlayerHUDWidget.cpp index c73553b..619baa4 100644 --- a/Source/KingshearthLegacy/UI/PlayerHUDWidget.cpp +++ b/Source/KingshearthLegacy/UI/PlayerHUDWidget.cpp @@ -2,12 +2,15 @@ #include "UI/HealthBarWidget.h" #include "UI/StaminaBarWidget.h" -void UPlayerHUDWidget::InitializeHUD(UHealthComponent* InHealthComponent) +void UPlayerHUDWidget::InitializeHUD(UHealthComponent* InHealthComponent, UStaminaComponent* InStaminaComponent) { if (HealthBar) { HealthBar->InitializeHealthBar(InHealthComponent); } - // StaminaBar self-initializes from hardcoded defaults in its own NativeConstruct. + if (StaminaBar) + { + StaminaBar->InitializeStaminaBar(InStaminaComponent); + } } diff --git a/Source/KingshearthLegacy/UI/PlayerHUDWidget.h b/Source/KingshearthLegacy/UI/PlayerHUDWidget.h index 234c5ac..4fc6c20 100644 --- a/Source/KingshearthLegacy/UI/PlayerHUDWidget.h +++ b/Source/KingshearthLegacy/UI/PlayerHUDWidget.h @@ -7,6 +7,7 @@ class UHealthBarWidget; class UStaminaBarWidget; class UHealthComponent; +class UStaminaComponent; /** * Top-level player HUD. Owns a health bar and a stamina bar and forwards @@ -20,7 +21,7 @@ class UPlayerHUDWidget : public UUserWidget public: UFUNCTION(BlueprintCallable, Category = "HUD") - void InitializeHUD(UHealthComponent* InHealthComponent); + void InitializeHUD(UHealthComponent* InHealthComponent, UStaminaComponent* InStaminaComponent); protected: UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) diff --git a/Source/KingshearthLegacy/UI/StaminaBarWidget.cpp b/Source/KingshearthLegacy/UI/StaminaBarWidget.cpp index ace3dac..c7cfa02 100644 --- a/Source/KingshearthLegacy/UI/StaminaBarWidget.cpp +++ b/Source/KingshearthLegacy/UI/StaminaBarWidget.cpp @@ -1,11 +1,39 @@ #include "StaminaBarWidget.h" +#include "StaminaComponent.h" -void UStaminaBarWidget::NativeConstruct() +void UStaminaBarWidget::InitializeStaminaBar(UStaminaComponent* InStaminaComponent) { - Super::NativeConstruct(); + if (!InStaminaComponent) + { + return; + } - SetStatValue(DefaultCurrentStamina, DefaultMaxStamina); - SetFatigue(DefaultFatigue); + if (StaminaComponent) + { + 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) diff --git a/Source/KingshearthLegacy/UI/StaminaBarWidget.h b/Source/KingshearthLegacy/UI/StaminaBarWidget.h index aa8bc09..cf363ff 100644 --- a/Source/KingshearthLegacy/UI/StaminaBarWidget.h +++ b/Source/KingshearthLegacy/UI/StaminaBarWidget.h @@ -4,16 +4,12 @@ #include "UI/StatBarWidget.h" #include "StaminaBarWidget.generated.h" +class UStaminaComponent; + /** - * Stat bar for stamina. Currently self-initializes from hardcoded defaults, - * since there is no stamina/fatigue gameplay component (DataModel) yet. - * - * 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. + * Stat bar bound to a live UStaminaComponent. Call InitializeStaminaBar once the + * component is known (typically from the owning PlayerController on possession), + * mirroring UHealthBarWidget::InitializeHealthBar. */ UCLASS(abstract) class UStaminaBarWidget : public UStatBarWidget @@ -21,6 +17,9 @@ class UStaminaBarWidget : public UStatBarWidget GENERATED_BODY() public: + UFUNCTION(BlueprintCallable, Category = "Stamina") + void InitializeStaminaBar(UStaminaComponent* InStaminaComponent); + UFUNCTION(BlueprintCallable, Category = "Fatigue") void SetFatigue(float NewFatigue); @@ -28,22 +27,18 @@ public: float GetFatigue() const { return Fatigue; } 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") void OnFatigueChanged(float NewFatigue); - /** Placeholder starting values until a real UStaminaComponent DataModel exists. */ - UPROPERTY(EditDefaultsOnly, Category = "Stamina") - 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; + UPROPERTY(BlueprintReadOnly, Category = "Stamina") + TObjectPtr StaminaComponent; /** 0-1 exhaustion amount. Drives the bar's penalty segment via SetPenaltyPercent. */ UPROPERTY(BlueprintReadOnly, Category = "Fatigue")