Integriere Gameplay Ability System (GAS)
Ersetze HealthComponent und StaminaComponent durch GAS-basierte AttributeSets (`UHealthAttributeSet`, `UStaminaAttributeSet`) und füge `UAbilitySystemComponent` hinzu. Implementiere neue GameplayEffects (`UGE_HealthDamage`, `UGE_HealthHealing`, `UGE_HealthDebuff`, `UGE_StaminaFatiguePenalty`) und eine Modifikator-Berechnung (`UMMC_MaxStaminaFatiguePenalty`). Erweitere `AKingshearthLegacyCharacter` um Methoden zur Attributmanipulation (`ApplyDamage`, `SpendStamina`, etc.) und Stamina-Regeneration. Aktualisiere UI-Widgets (`HealthBarWidget`, `StaminaBarWidget`) zur Nutzung der neuen AttributeSets. Entferne Legacy-Code für Health- und Stamina-Komponenten. Füge neue Assets (Niagara-Systeme, Materialien, Texturen) hinzu und passe Projektkonfiguration an (z. B. `GameplayAbilities`-Modul, `r.Substrate`).
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -27,6 +27,7 @@ r.Substrate.ProjectGBufferFormat=0
|
||||
r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8
|
||||
|
||||
r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8
|
||||
r.Lumen.HardwareRayTracing.LightingMode=0
|
||||
|
||||
[/Script/WindowsTargetPlatform.WindowsTargetSettings]
|
||||
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -42,6 +42,10 @@
|
||||
{
|
||||
"Name": "EditorToolset",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "GameplayAbilities",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#include "HealthAttributeSet.h"
|
||||
#include "GameplayEffectExtension.h"
|
||||
|
||||
UHealthAttributeSet::UHealthAttributeSet()
|
||||
{
|
||||
InitHealth(100.0f);
|
||||
InitMaxHealth(100.0f);
|
||||
}
|
||||
|
||||
void UHealthAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
|
||||
{
|
||||
Super::PreAttributeChange(Attribute, NewValue);
|
||||
|
||||
if (Attribute == GetHealthAttribute())
|
||||
{
|
||||
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
|
||||
}
|
||||
else if (Attribute == GetMaxHealthAttribute())
|
||||
{
|
||||
NewValue = FMath::Max(NewValue, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void UHealthAttributeSet::PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const
|
||||
{
|
||||
Super::PreAttributeBaseChange(Attribute, NewValue);
|
||||
|
||||
// Instant GameplayEffects (and any future ApplyModToAttribute caller) write BaseValue through this hook,
|
||||
// not PreAttributeChange - without clamping here a large delta can push BaseValue out of range while
|
||||
// PreAttributeChange still clamps the displayed CurrentValue, silently absorbing later deltas. See the
|
||||
// matching comment on UStaminaAttributeSet::PreAttributeBaseChange for how this actually manifested.
|
||||
if (Attribute == GetHealthAttribute())
|
||||
{
|
||||
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
|
||||
}
|
||||
else if (Attribute == GetMaxHealthAttribute())
|
||||
{
|
||||
NewValue = FMath::Max(NewValue, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void UHealthAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
|
||||
{
|
||||
Super::PostGameplayEffectExecute(Data);
|
||||
|
||||
// Only converts the Damage/Healing meta-attributes into a Health change - this hook fires for GE
|
||||
// "executes" (Instant effects, periodic ticks) only. GE_HealthDebuff is Infinite and non-periodic,
|
||||
// so applying/updating it does NOT fire this - see PostAttributeChange for the hook that does.
|
||||
const FGameplayAttribute& ModifiedAttribute = Data.EvaluatedData.Attribute;
|
||||
|
||||
if (ModifiedAttribute == GetDamageAttribute())
|
||||
{
|
||||
const float DamageDone = GetDamage();
|
||||
SetDamage(0.0f);
|
||||
SetHealth(GetHealth() - DamageDone);
|
||||
}
|
||||
else if (ModifiedAttribute == GetHealingAttribute())
|
||||
{
|
||||
const float HealingDone = GetHealing();
|
||||
SetHealing(0.0f);
|
||||
SetHealth(GetHealth() + HealingDone);
|
||||
}
|
||||
}
|
||||
|
||||
void UHealthAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
|
||||
{
|
||||
Super::PostAttributeChange(Attribute, OldValue, NewValue);
|
||||
|
||||
// Fires for every CurrentValue change regardless of source (GE execute, or - critically - an Infinite
|
||||
// GE like GE_HealthDebuff being applied/removed/re-evaluated, which never triggers PostGameplayEffectExecute).
|
||||
if (Attribute == GetHealthAttribute())
|
||||
{
|
||||
if (!FMath::IsNearlyEqual(OldValue, NewValue))
|
||||
{
|
||||
OnHealthChanged.Broadcast(NewValue);
|
||||
}
|
||||
|
||||
if (OldValue > 0.0f && NewValue <= 0.0f)
|
||||
{
|
||||
OnDeath.Broadcast();
|
||||
}
|
||||
}
|
||||
else if (Attribute == GetMaxHealthAttribute())
|
||||
{
|
||||
if (!FMath::IsNearlyEqual(OldValue, NewValue))
|
||||
{
|
||||
ClampHealth();
|
||||
OnHealthDebuffChanged.Broadcast(GetHealthDebuff());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UHealthAttributeSet::ClampHealth()
|
||||
{
|
||||
SetHealth(FMath::Clamp(GetHealth(), 0.0f, GetMaxHealth()));
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AttributeSet.h"
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "HealthAttributeSet.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthDebuffChanged, float, NewHealthDebuff);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
|
||||
|
||||
#define HEALTHATTRIBUTE_ACCESSORS(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UHealthAttributeSet, PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
|
||||
|
||||
/**
|
||||
* Health + MaxHealth, replacing UHealthComponent. HealthDebuff no longer exists as its own
|
||||
* attribute - it's GE_HealthDebuff applying a Multiply modifier to MaxHealth, so the "locked"
|
||||
* fraction is just (1 - MaxHealth.CurrentValue / MaxHealth.BaseValue). See GetHealthDebuff()
|
||||
* on AKingshearthLegacyCharacter.
|
||||
*/
|
||||
UCLASS()
|
||||
class KINGSHEARTHLEGACY_API UHealthAttributeSet : public UAttributeSet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UHealthAttributeSet();
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Health")
|
||||
FGameplayAttributeData Health;
|
||||
HEALTHATTRIBUTE_ACCESSORS(Health)
|
||||
|
||||
/** Nominal cap: BaseValue is the undebuffed max, CurrentValue is what GE_HealthDebuff (and similar) leave after their modifiers. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Health")
|
||||
FGameplayAttributeData MaxHealth;
|
||||
HEALTHATTRIBUTE_ACCESSORS(MaxHealth)
|
||||
|
||||
/** Unclamped meta attribute: incoming damage lands here via GE_HealthDamage; PostGameplayEffectExecute converts it into a Health decrease and resets it to 0. Never read directly. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Health")
|
||||
FGameplayAttributeData Damage;
|
||||
HEALTHATTRIBUTE_ACCESSORS(Damage)
|
||||
|
||||
/** Unclamped meta attribute: incoming healing lands here via GE_HealthHealing; PostGameplayEffectExecute converts it into a Health increase and resets it to 0. Never read directly. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Health")
|
||||
FGameplayAttributeData Healing;
|
||||
HEALTHATTRIBUTE_ACCESSORS(Healing)
|
||||
|
||||
/** Nominal cap, unaffected by debuffs. Feeds the health bar's total width. */
|
||||
float GetNominalMaxHealth() const { return MaxHealth.GetBaseValue(); }
|
||||
|
||||
/** 0-1 fraction of GetNominalMaxHealth() currently locked/unusable (curses, status effects, ...). */
|
||||
float GetHealthDebuff() const
|
||||
{
|
||||
const float NominalMaxHealth = FMath::Max(GetNominalMaxHealth(), KINDA_SMALL_NUMBER);
|
||||
return FMath::Clamp(1.0f - (GetMaxHealth() / NominalMaxHealth), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Health")
|
||||
FOnHealthChanged OnHealthChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Health")
|
||||
FOnHealthDebuffChanged OnHealthDebuffChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Health")
|
||||
FOnDeath OnDeath;
|
||||
|
||||
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
|
||||
virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const override;
|
||||
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
|
||||
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
|
||||
|
||||
private:
|
||||
void ClampHealth();
|
||||
};
|
||||
|
||||
#undef HEALTHATTRIBUTE_ACCESSORS
|
||||
@@ -0,0 +1,86 @@
|
||||
#include "StaminaAttributeSet.h"
|
||||
|
||||
UStaminaAttributeSet::UStaminaAttributeSet()
|
||||
{
|
||||
InitStamina(100.0f);
|
||||
InitMaxStamina(100.0f);
|
||||
InitFatigue(0.0f);
|
||||
}
|
||||
|
||||
void UStaminaAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
|
||||
{
|
||||
Super::PreAttributeChange(Attribute, NewValue);
|
||||
|
||||
if (Attribute == GetStaminaAttribute())
|
||||
{
|
||||
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxStamina());
|
||||
}
|
||||
else if (Attribute == GetMaxStaminaAttribute())
|
||||
{
|
||||
NewValue = FMath::Max(NewValue, 0.0f);
|
||||
}
|
||||
else if (Attribute == GetFatigueAttribute())
|
||||
{
|
||||
NewValue = FMath::Clamp(NewValue, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void UStaminaAttributeSet::PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const
|
||||
{
|
||||
Super::PreAttributeBaseChange(Attribute, NewValue);
|
||||
|
||||
// ApplyModToAttribute (SpendStamina/RestoreStamina/regen) writes BaseValue through this hook, not
|
||||
// PreAttributeChange - without clamping here, a large delta (e.g. RestoreStamina's debug "fill to max")
|
||||
// pushes BaseValue past MaxStamina. PreAttributeChange still clamps the displayed CurrentValue, but the
|
||||
// bloated BaseValue then absorbs every subsequent spend/regen delta before CurrentValue moves again.
|
||||
if (Attribute == GetStaminaAttribute())
|
||||
{
|
||||
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxStamina());
|
||||
}
|
||||
else if (Attribute == GetMaxStaminaAttribute())
|
||||
{
|
||||
NewValue = FMath::Max(NewValue, 0.0f);
|
||||
}
|
||||
else if (Attribute == GetFatigueAttribute())
|
||||
{
|
||||
NewValue = FMath::Clamp(NewValue, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void UStaminaAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
|
||||
{
|
||||
Super::PostAttributeChange(Attribute, OldValue, NewValue);
|
||||
|
||||
if (Attribute == GetStaminaAttribute())
|
||||
{
|
||||
if (!FMath::IsNearlyEqual(OldValue, NewValue))
|
||||
{
|
||||
OnStaminaChanged.Broadcast(NewValue);
|
||||
}
|
||||
|
||||
if (OldValue > 0.0f && NewValue <= 0.0f)
|
||||
{
|
||||
OnExhausted.Broadcast();
|
||||
}
|
||||
}
|
||||
else if (Attribute == GetMaxStaminaAttribute())
|
||||
{
|
||||
if (!FMath::IsNearlyEqual(OldValue, NewValue))
|
||||
{
|
||||
// MaxStamina shrank/grew (typically GE_StaminaFatiguePenalty re-evaluating) - pull Stamina back in range.
|
||||
ClampStamina();
|
||||
}
|
||||
}
|
||||
else if (Attribute == GetFatigueAttribute())
|
||||
{
|
||||
if (!FMath::IsNearlyEqual(OldValue, NewValue))
|
||||
{
|
||||
OnFatigueChanged.Broadcast(NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UStaminaAttributeSet::ClampStamina()
|
||||
{
|
||||
SetStamina(FMath::Clamp(GetStamina(), 0.0f, GetMaxStamina()));
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AttributeSet.h"
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "StaminaAttributeSet.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStaminaChanged, float, NewStamina);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFatigueChanged, float, NewFatigue);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnExhausted);
|
||||
|
||||
#define STAMINAATTRIBUTE_ACCESSORS(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UStaminaAttributeSet, PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
|
||||
|
||||
/**
|
||||
* Stamina + MaxStamina + Fatigue, replacing UStaminaComponent. Unlike Health, all three attributes
|
||||
* are mutated directly (via AbilitySystemComponent::ApplyModToAttribute from the character's tick,
|
||||
* see SpendStamina/TickStaminaRegen) rather than through discrete GameplayEffects - there's no
|
||||
* damage-type/resistance concept for stamina spend to warrant Health's meta-attribute indirection.
|
||||
* The one thing that IS a real GameplayEffect is GE_StaminaFatiguePenalty: an always-on Infinite
|
||||
* effect (applied once in BeginPlay) that uses MMC_MaxStaminaFatiguePenalty to keep MaxStamina's
|
||||
* CurrentValue equal to BaseValue * (1 - Fatigue), recalculated automatically by GAS's own
|
||||
* aggregator whenever Fatigue changes, replacing the old hand-written GetEffectiveMaxStamina().
|
||||
*
|
||||
* Broadcasts live in PostAttributeChange (fires for ANY mutation path, not just GameplayEffect
|
||||
* execution) specifically so the Fatigue-driven MaxStamina recalculation broadcasts correctly too.
|
||||
*/
|
||||
UCLASS()
|
||||
class KINGSHEARTHLEGACY_API UStaminaAttributeSet : public UAttributeSet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UStaminaAttributeSet();
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Stamina")
|
||||
FGameplayAttributeData Stamina;
|
||||
STAMINAATTRIBUTE_ACCESSORS(Stamina)
|
||||
|
||||
/** Nominal cap: BaseValue is the undebuffed max, CurrentValue is what GE_StaminaFatiguePenalty leaves after Fatigue. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Stamina")
|
||||
FGameplayAttributeData MaxStamina;
|
||||
STAMINAATTRIBUTE_ACCESSORS(MaxStamina)
|
||||
|
||||
/** 0-1 fraction of MaxStamina currently locked away due to over-extension. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Fatigue")
|
||||
FGameplayAttributeData Fatigue;
|
||||
STAMINAATTRIBUTE_ACCESSORS(Fatigue)
|
||||
|
||||
/** Nominal cap, unaffected by Fatigue. Feeds the stamina bar's total width. */
|
||||
float GetNominalMaxStamina() const { return MaxStamina.GetBaseValue(); }
|
||||
|
||||
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;
|
||||
|
||||
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
|
||||
virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const override;
|
||||
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
|
||||
|
||||
private:
|
||||
void ClampStamina();
|
||||
};
|
||||
|
||||
#undef STAMINAATTRIBUTE_ACCESSORS
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "GE_HealthDamage.h"
|
||||
#include "HealthAttributeSet.h"
|
||||
#include "KingshearthLegacyGameplayTags.h"
|
||||
|
||||
UGE_HealthDamage::UGE_HealthDamage()
|
||||
{
|
||||
DurationPolicy = EGameplayEffectDurationType::Instant;
|
||||
|
||||
FSetByCallerFloat SetByCaller;
|
||||
SetByCaller.DataTag = TAG_Data_Health_Damage.GetTag();
|
||||
|
||||
FGameplayModifierInfo Modifier;
|
||||
Modifier.Attribute = UHealthAttributeSet::GetDamageAttribute();
|
||||
Modifier.ModifierOp = EGameplayModOp::Additive;
|
||||
Modifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(SetByCaller);
|
||||
|
||||
Modifiers.Add(Modifier);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffect.h"
|
||||
#include "GE_HealthDamage.generated.h"
|
||||
|
||||
/** Instant effect: adds a caller-specified amount (SetByCaller TAG_Data_Health_Damage) to HealthAttributeSet::Damage. */
|
||||
UCLASS()
|
||||
class KINGSHEARTHLEGACY_API UGE_HealthDamage : public UGameplayEffect
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGE_HealthDamage();
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "GE_HealthDebuff.h"
|
||||
#include "HealthAttributeSet.h"
|
||||
#include "KingshearthLegacyGameplayTags.h"
|
||||
|
||||
UGE_HealthDebuff::UGE_HealthDebuff()
|
||||
{
|
||||
DurationPolicy = EGameplayEffectDurationType::Infinite;
|
||||
|
||||
FSetByCallerFloat SetByCaller;
|
||||
SetByCaller.DataTag = TAG_Data_HealthDebuff_Multiplier.GetTag();
|
||||
|
||||
FGameplayModifierInfo Modifier;
|
||||
Modifier.Attribute = UHealthAttributeSet::GetMaxHealthAttribute();
|
||||
Modifier.ModifierOp = EGameplayModOp::MultiplyAdditive;
|
||||
Modifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(SetByCaller);
|
||||
|
||||
Modifiers.Add(Modifier);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffect.h"
|
||||
#include "GE_HealthDebuff.generated.h"
|
||||
|
||||
/**
|
||||
* Infinite effect: multiplies MaxHealth's CurrentValue by a caller-specified fraction
|
||||
* (SetByCaller TAG_Data_HealthDebuff_Multiplier, e.g. 0.7 to lock away 30%). Stands in for
|
||||
* a future curse/status-effect system - AKingshearthLegacyCharacter::SetHealthDebuff applies/
|
||||
* removes one instance of this, tracked by its FActiveGameplayEffectHandle.
|
||||
*/
|
||||
UCLASS()
|
||||
class KINGSHEARTHLEGACY_API UGE_HealthDebuff : public UGameplayEffect
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGE_HealthDebuff();
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "GE_HealthHealing.h"
|
||||
#include "HealthAttributeSet.h"
|
||||
#include "KingshearthLegacyGameplayTags.h"
|
||||
|
||||
UGE_HealthHealing::UGE_HealthHealing()
|
||||
{
|
||||
DurationPolicy = EGameplayEffectDurationType::Instant;
|
||||
|
||||
FSetByCallerFloat SetByCaller;
|
||||
SetByCaller.DataTag = TAG_Data_Health_Healing.GetTag();
|
||||
|
||||
FGameplayModifierInfo Modifier;
|
||||
Modifier.Attribute = UHealthAttributeSet::GetHealingAttribute();
|
||||
Modifier.ModifierOp = EGameplayModOp::Additive;
|
||||
Modifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(SetByCaller);
|
||||
|
||||
Modifiers.Add(Modifier);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffect.h"
|
||||
#include "GE_HealthHealing.generated.h"
|
||||
|
||||
/** Instant effect: adds a caller-specified amount (SetByCaller TAG_Data_Health_Healing) to HealthAttributeSet::Healing. */
|
||||
UCLASS()
|
||||
class KINGSHEARTHLEGACY_API UGE_HealthHealing : public UGameplayEffect
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGE_HealthHealing();
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "GE_StaminaFatiguePenalty.h"
|
||||
#include "StaminaAttributeSet.h"
|
||||
#include "MMC_MaxStaminaFatiguePenalty.h"
|
||||
|
||||
UGE_StaminaFatiguePenalty::UGE_StaminaFatiguePenalty()
|
||||
{
|
||||
DurationPolicy = EGameplayEffectDurationType::Infinite;
|
||||
|
||||
FCustomCalculationBasedFloat CustomCalculation;
|
||||
CustomCalculation.CalculationClassMagnitude = UMMC_MaxStaminaFatiguePenalty::StaticClass();
|
||||
|
||||
FGameplayModifierInfo Modifier;
|
||||
Modifier.Attribute = UStaminaAttributeSet::GetMaxStaminaAttribute();
|
||||
Modifier.ModifierOp = EGameplayModOp::MultiplyAdditive;
|
||||
Modifier.ModifierMagnitude = FGameplayEffectModifierMagnitude(CustomCalculation);
|
||||
|
||||
Modifiers.Add(Modifier);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffect.h"
|
||||
#include "GE_StaminaFatiguePenalty.generated.h"
|
||||
|
||||
/**
|
||||
* Always-on Infinite effect applied once in AKingshearthLegacyCharacter::BeginPlay: multiplies
|
||||
* MaxStamina's CurrentValue by MMC_MaxStaminaFatiguePenalty's (1 - Fatigue), so MaxStamina tracks
|
||||
* Fatigue automatically via GAS's aggregator - replaces the old hand-written GetEffectiveMaxStamina().
|
||||
*/
|
||||
UCLASS()
|
||||
class KINGSHEARTHLEGACY_API UGE_StaminaFatiguePenalty : public UGameplayEffect
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGE_StaminaFatiguePenalty();
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "MMC_MaxStaminaFatiguePenalty.h"
|
||||
#include "StaminaAttributeSet.h"
|
||||
|
||||
UMMC_MaxStaminaFatiguePenalty::UMMC_MaxStaminaFatiguePenalty()
|
||||
{
|
||||
FatigueCaptureDef = FGameplayEffectAttributeCaptureDefinition(
|
||||
UStaminaAttributeSet::GetFatigueAttribute(),
|
||||
EGameplayEffectAttributeCaptureSource::Target,
|
||||
false);
|
||||
|
||||
RelevantAttributesToCapture.Add(FatigueCaptureDef);
|
||||
}
|
||||
|
||||
float UMMC_MaxStaminaFatiguePenalty::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
|
||||
{
|
||||
float Fatigue = 0.0f;
|
||||
GetCapturedAttributeMagnitude(FatigueCaptureDef, Spec, FAggregatorEvaluateParameters(), Fatigue);
|
||||
|
||||
return 1.0f - FMath::Clamp(Fatigue, 0.0f, 1.0f);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayModMagnitudeCalculation.h"
|
||||
#include "MMC_MaxStaminaFatiguePenalty.generated.h"
|
||||
|
||||
/** Captures Fatigue (live, non-snapshot) and returns (1 - Fatigue) as the Multiply(Additive) magnitude for GE_StaminaFatiguePenalty's MaxStamina modifier. */
|
||||
UCLASS()
|
||||
class KINGSHEARTHLEGACY_API UMMC_MaxStaminaFatiguePenalty : public UGameplayModMagnitudeCalculation
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UMMC_MaxStaminaFatiguePenalty();
|
||||
|
||||
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
|
||||
|
||||
private:
|
||||
FGameplayEffectAttributeCaptureDefinition FatigueCaptureDef;
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
#include "HealthComponent.h"
|
||||
|
||||
UHealthComponent::UHealthComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
void UHealthComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
CurrentHealth = GetEffectiveMaxHealth();
|
||||
}
|
||||
|
||||
float UHealthComponent::GetHealth() const
|
||||
{
|
||||
return CurrentHealth;
|
||||
}
|
||||
|
||||
float UHealthComponent::GetMaxHealth() const
|
||||
{
|
||||
return MaxHealth;
|
||||
}
|
||||
|
||||
float UHealthComponent::GetHealthDebuff() const
|
||||
{
|
||||
return HealthDebuff;
|
||||
}
|
||||
|
||||
float UHealthComponent::GetEffectiveMaxHealth() const
|
||||
{
|
||||
return MaxHealth * (1.0f - HealthDebuff);
|
||||
}
|
||||
|
||||
void UHealthComponent::SetMaxHealth(float NewMaxHealth)
|
||||
{
|
||||
MaxHealth = FMath::Max(NewMaxHealth, 0.0f);
|
||||
|
||||
CurrentHealth = FMath::Clamp(CurrentHealth, 0.0f, GetEffectiveMaxHealth());
|
||||
}
|
||||
|
||||
void UHealthComponent::SetHealth(float NewHealth)
|
||||
{
|
||||
const float PreviousHealth = CurrentHealth;
|
||||
|
||||
CurrentHealth = FMath::Clamp(NewHealth, 0.0f, GetEffectiveMaxHealth());
|
||||
|
||||
OnHealthChanged.Broadcast(CurrentHealth);
|
||||
|
||||
// Only trigger death when crossing from alive to dead.
|
||||
if (PreviousHealth > 0.0f && CurrentHealth <= 0.0f)
|
||||
{
|
||||
OnDeath.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void UHealthComponent::AddHealth(float Amount)
|
||||
{
|
||||
if (Amount <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetHealth(CurrentHealth + Amount);
|
||||
}
|
||||
|
||||
void UHealthComponent::RemoveHealth(float Amount)
|
||||
{
|
||||
if (Amount <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "HealthComponent.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthDebuffChanged, float, NewHealthDebuff);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
|
||||
|
||||
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
|
||||
class KINGSHEARTHLEGACY_API UHealthComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UHealthComponent();
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
public:
|
||||
// Health
|
||||
UFUNCTION(BlueprintPure, Category = "Health")
|
||||
float GetHealth() const;
|
||||
|
||||
/** Nominal cap, unaffected by HealthDebuff. Feeds the health bar's total width. */
|
||||
UFUNCTION(BlueprintPure, Category = "Health")
|
||||
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")
|
||||
void SetMaxHealth(float NewMaxHealth);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||
void SetHealth(float NewHealth);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||
void AddHealth(float Amount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||
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:
|
||||
/** MaxHealth minus whatever HealthDebuff currently locks away - what Health is actually clamped against. */
|
||||
float GetEffectiveMaxHealth() const;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Health")
|
||||
float MaxHealth = 100.0f;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Health")
|
||||
float CurrentHealth = 100.0f;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Health")
|
||||
float HealthDebuff = 0.0f;
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintAssignable, Category = "Health")
|
||||
FOnHealthChanged OnHealthChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Health")
|
||||
FOnHealthDebuffChanged OnHealthDebuffChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Health")
|
||||
FOnDeath OnDeath;
|
||||
};
|
||||
@@ -18,13 +18,18 @@ public class KingshearthLegacy : ModuleRules
|
||||
"StateTreeModule",
|
||||
"GameplayStateTreeModule",
|
||||
"UMG",
|
||||
"Slate"
|
||||
"Slate",
|
||||
"GameplayAbilities",
|
||||
"GameplayTags",
|
||||
"GameplayTasks"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { });
|
||||
|
||||
PublicIncludePaths.AddRange(new string[] {
|
||||
"KingshearthLegacy",
|
||||
"KingshearthLegacy/AttributeSets",
|
||||
"KingshearthLegacy/GameplayEffects",
|
||||
"KingshearthLegacy/Variant_Platforming",
|
||||
"KingshearthLegacy/Variant_Platforming/Animation",
|
||||
"KingshearthLegacy/Variant_Combat",
|
||||
|
||||
@@ -11,7 +11,14 @@
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "InputActionValue.h"
|
||||
#include "KingshearthLegacy.h"
|
||||
#include "StaminaComponent.h"
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "HealthAttributeSet.h"
|
||||
#include "StaminaAttributeSet.h"
|
||||
#include "GE_HealthDamage.h"
|
||||
#include "GE_HealthHealing.h"
|
||||
#include "GE_HealthDebuff.h"
|
||||
#include "GE_StaminaFatiguePenalty.h"
|
||||
#include "KingshearthLegacyGameplayTags.h"
|
||||
|
||||
AKingshearthLegacyCharacter::AKingshearthLegacyCharacter()
|
||||
{
|
||||
@@ -47,8 +54,16 @@ AKingshearthLegacyCharacter::AKingshearthLegacyCharacter()
|
||||
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
||||
FollowCamera->bUsePawnControlRotation = false;
|
||||
|
||||
// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
|
||||
// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
|
||||
// are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
|
||||
|
||||
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
|
||||
|
||||
HealthAttributeSet = CreateDefaultSubobject<UHealthAttributeSet>(TEXT("HealthAttributeSet"));
|
||||
AbilitySystemComponent->AddAttributeSetSubobject(HealthAttributeSet.Get());
|
||||
|
||||
StaminaAttributeSet = CreateDefaultSubobject<UStaminaAttributeSet>(TEXT("StaminaAttributeSet"));
|
||||
AbilitySystemComponent->AddAttributeSetSubobject(StaminaAttributeSet.Get());
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::BeginPlay()
|
||||
@@ -56,14 +71,172 @@ void AKingshearthLegacyCharacter::BeginPlay()
|
||||
Super::BeginPlay();
|
||||
|
||||
BaseWalkSpeed = GetCharacterMovement()->MaxWalkSpeed;
|
||||
StaminaComponent = FindComponentByClass<UStaminaComponent>();
|
||||
|
||||
if (AbilitySystemComponent)
|
||||
{
|
||||
AbilitySystemComponent->InitAbilityActorInfo(this, this);
|
||||
|
||||
// Always-on: keeps MaxStamina tracking Fatigue via MMC_MaxStaminaFatiguePenalty for the character's lifetime.
|
||||
const FGameplayEffectSpecHandle FatiguePenaltySpec = AbilitySystemComponent->MakeOutgoingSpec(UGE_StaminaFatiguePenalty::StaticClass(), 1.0f, AbilitySystemComponent->MakeEffectContext());
|
||||
if (FatiguePenaltySpec.IsValid())
|
||||
{
|
||||
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*FatiguePenaltySpec.Data.Get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* AKingshearthLegacyCharacter::GetAbilitySystemComponent() const
|
||||
{
|
||||
return AbilitySystemComponent;
|
||||
}
|
||||
|
||||
float AKingshearthLegacyCharacter::GetHealth() const
|
||||
{
|
||||
return HealthAttributeSet ? HealthAttributeSet->GetHealth() : 0.0f;
|
||||
}
|
||||
|
||||
float AKingshearthLegacyCharacter::GetMaxHealth() const
|
||||
{
|
||||
return HealthAttributeSet ? HealthAttributeSet->GetNominalMaxHealth() : 0.0f;
|
||||
}
|
||||
|
||||
float AKingshearthLegacyCharacter::GetHealthDebuff() const
|
||||
{
|
||||
return HealthAttributeSet ? HealthAttributeSet->GetHealthDebuff() : 0.0f;
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::ApplyDamage(float Amount)
|
||||
{
|
||||
if (Amount <= 0.0f || !AbilitySystemComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FGameplayEffectSpecHandle SpecHandle = AbilitySystemComponent->MakeOutgoingSpec(UGE_HealthDamage::StaticClass(), 1.0f, AbilitySystemComponent->MakeEffectContext());
|
||||
if (SpecHandle.IsValid())
|
||||
{
|
||||
SpecHandle.Data->SetSetByCallerMagnitude(TAG_Data_Health_Damage.GetTag(), Amount);
|
||||
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
|
||||
}
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::ApplyHealing(float Amount)
|
||||
{
|
||||
if (Amount <= 0.0f || !AbilitySystemComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FGameplayEffectSpecHandle SpecHandle = AbilitySystemComponent->MakeOutgoingSpec(UGE_HealthHealing::StaticClass(), 1.0f, AbilitySystemComponent->MakeEffectContext());
|
||||
if (SpecHandle.IsValid())
|
||||
{
|
||||
SpecHandle.Data->SetSetByCallerMagnitude(TAG_Data_Health_Healing.GetTag(), Amount);
|
||||
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
|
||||
}
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::SetHealthDebuff(float NewHealthDebuff)
|
||||
{
|
||||
if (!AbilitySystemComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HealthDebuffEffectHandle.IsValid())
|
||||
{
|
||||
AbilitySystemComponent->RemoveActiveGameplayEffect(HealthDebuffEffectHandle);
|
||||
HealthDebuffEffectHandle.Invalidate();
|
||||
}
|
||||
|
||||
const float ClampedDebuff = FMath::Clamp(NewHealthDebuff, 0.0f, 1.0f);
|
||||
if (ClampedDebuff <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FGameplayEffectSpecHandle SpecHandle = AbilitySystemComponent->MakeOutgoingSpec(UGE_HealthDebuff::StaticClass(), 1.0f, AbilitySystemComponent->MakeEffectContext());
|
||||
if (SpecHandle.IsValid())
|
||||
{
|
||||
SpecHandle.Data->SetSetByCallerMagnitude(TAG_Data_HealthDebuff_Multiplier.GetTag(), 1.0f - ClampedDebuff);
|
||||
HealthDebuffEffectHandle = AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
|
||||
}
|
||||
}
|
||||
|
||||
float AKingshearthLegacyCharacter::GetStamina() const
|
||||
{
|
||||
return StaminaAttributeSet ? StaminaAttributeSet->GetStamina() : 0.0f;
|
||||
}
|
||||
|
||||
float AKingshearthLegacyCharacter::GetMaxStamina() const
|
||||
{
|
||||
return StaminaAttributeSet ? StaminaAttributeSet->GetNominalMaxStamina() : 0.0f;
|
||||
}
|
||||
|
||||
float AKingshearthLegacyCharacter::GetFatigue() const
|
||||
{
|
||||
return StaminaAttributeSet ? StaminaAttributeSet->GetFatigue() : 0.0f;
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::SpendStamina(float Amount)
|
||||
{
|
||||
if (Amount <= 0.0f || !AbilitySystemComponent || !StaminaAttributeSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AbilitySystemComponent->ApplyModToAttribute(UStaminaAttributeSet::GetStaminaAttribute(), EGameplayModOp::AddBase, -Amount);
|
||||
TimeSinceLastStaminaSpend = 0.0f;
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::RestoreStamina(float Amount)
|
||||
{
|
||||
if (Amount <= 0.0f || !AbilitySystemComponent || !StaminaAttributeSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AbilitySystemComponent->ApplyModToAttribute(UStaminaAttributeSet::GetStaminaAttribute(), EGameplayModOp::AddBase, Amount);
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::TickStaminaRegen(float DeltaSeconds)
|
||||
{
|
||||
if (!AbilitySystemComponent || !StaminaAttributeSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TimeSinceLastStaminaSpend += DeltaSeconds;
|
||||
|
||||
const float TimeSinceRegenStarted = TimeSinceLastStaminaSpend - StaminaRegenDelay;
|
||||
if (TimeSinceRegenStarted > 0.0f && StaminaAttributeSet->GetStamina() < StaminaAttributeSet->GetMaxStamina())
|
||||
{
|
||||
const float RampedRegenRate = StaminaRegenRampTime > 0.0f
|
||||
? FMath::Min(TimeSinceRegenStarted / StaminaRegenRampTime, 1.0f) * StaminaRegenRate
|
||||
: StaminaRegenRate;
|
||||
|
||||
AbilitySystemComponent->ApplyModToAttribute(UStaminaAttributeSet::GetStaminaAttribute(), EGameplayModOp::AddBase, RampedRegenRate * DeltaSeconds);
|
||||
|
||||
// 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 (e.g. a debug key or a future rest mechanic) reduces it.
|
||||
AbilitySystemComponent->ApplyModToAttribute(UStaminaAttributeSet::GetFatigueAttribute(), EGameplayModOp::AddBase, FatigueGainRateWhileRegaining * DeltaSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::SetFatigue(float NewFatigue)
|
||||
{
|
||||
if (!AbilitySystemComponent || !StaminaAttributeSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AbilitySystemComponent->ApplyModToAttribute(UStaminaAttributeSet::GetFatigueAttribute(), EGameplayModOp::Override, FMath::Clamp(NewFatigue, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::Tick(float DeltaSeconds)
|
||||
{
|
||||
Super::Tick(DeltaSeconds);
|
||||
|
||||
if (!StaminaComponent)
|
||||
if (!StaminaAttributeSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -71,15 +244,17 @@ void AKingshearthLegacyCharacter::Tick(float DeltaSeconds)
|
||||
const bool bIsMoving = GetVelocity().SizeSquared() > FMath::Square(SprintMovementThreshold);
|
||||
const bool bIsGrounded = GetCharacterMovement()->IsMovingOnGround();
|
||||
|
||||
if (bWantsToSprint && bIsMoving && bIsGrounded && StaminaComponent->GetStamina() > 0.0f)
|
||||
if (bWantsToSprint && bIsMoving && bIsGrounded && StaminaAttributeSet->GetStamina() > 0.0f)
|
||||
{
|
||||
GetCharacterMovement()->MaxWalkSpeed = BaseWalkSpeed * SprintSpeedMultiplier;
|
||||
StaminaComponent->RemoveStamina(SprintStaminaDrainRate * DeltaSeconds);
|
||||
SpendStamina(SprintStaminaDrainRate * DeltaSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetCharacterMovement()->MaxWalkSpeed = BaseWalkSpeed;
|
||||
}
|
||||
|
||||
TickStaminaRegen(DeltaSeconds);
|
||||
}
|
||||
|
||||
void AKingshearthLegacyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||
@@ -174,9 +349,9 @@ void AKingshearthLegacyCharacter::Jump()
|
||||
|
||||
Super::Jump();
|
||||
|
||||
if (bCouldJump && StaminaComponent)
|
||||
if (bCouldJump)
|
||||
{
|
||||
StaminaComponent->RemoveStamina(JumpStaminaCost);
|
||||
SpendStamina(JumpStaminaCost);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
#include "AbilitySystemInterface.h"
|
||||
#include "ActiveGameplayEffectHandle.h"
|
||||
#include "KingshearthLegacyCharacter.generated.h"
|
||||
|
||||
class USpringArmComponent;
|
||||
class UCameraComponent;
|
||||
class UInputAction;
|
||||
class UStaminaComponent;
|
||||
class UAbilitySystemComponent;
|
||||
class UHealthAttributeSet;
|
||||
class UStaminaAttributeSet;
|
||||
struct FInputActionValue;
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
|
||||
@@ -20,7 +24,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
|
||||
* Implements a controllable orbiting camera
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class AKingshearthLegacyCharacter : public ACharacter
|
||||
class AKingshearthLegacyCharacter : public ACharacter, public IAbilitySystemInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -70,6 +74,22 @@ protected:
|
||||
UPROPERTY(EditAnywhere, Category="Stamina")
|
||||
float JumpStaminaCost = 15.0f;
|
||||
|
||||
/** Regen rate once fully ramped up, in stamina/sec. */
|
||||
UPROPERTY(EditAnywhere, Category="Stamina")
|
||||
float StaminaRegenRate = 20.0f;
|
||||
|
||||
/** Seconds after the last stamina spend before regen starts at all. */
|
||||
UPROPERTY(EditAnywhere, 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, Category="Stamina")
|
||||
float StaminaRegenRampTime = 1.5f;
|
||||
|
||||
/** Fatigue gained per second while Stamina is actively regaining (ramping back up). Never recovers on its own otherwise - only an explicit debug/design call reduces it. */
|
||||
UPROPERTY(EditAnywhere, Category="Fatigue")
|
||||
float FatigueGainRateWhileRegaining = 0.1f;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
@@ -100,16 +120,89 @@ protected:
|
||||
/** Called when the sprint input is released */
|
||||
void StopSprint();
|
||||
|
||||
/** Advances the regen delay/ramp curve and applies Stamina/Fatigue regen for this frame. */
|
||||
void TickStaminaRegen(float DeltaSeconds);
|
||||
|
||||
/** Cached from the CharacterMovementComponent's MaxWalkSpeed in BeginPlay, restored whenever not sprinting. */
|
||||
float BaseWalkSpeed = 0.0f;
|
||||
|
||||
bool bWantsToSprint = false;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UStaminaComponent> StaminaComponent;
|
||||
/** Seconds since Stamina was last reduced by a spend. Resets the regen delay/ramp whenever SpendStamina runs. */
|
||||
float TimeSinceLastStaminaSpend = 0.0f;
|
||||
|
||||
/** Drives all attribute sets (Health, Stamina, and future Nutrition/Blood). Self-owned: this character is both owner and avatar. */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Abilities", meta = (AllowPrivateAccess = "true"))
|
||||
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Abilities", meta = (AllowPrivateAccess = "true"))
|
||||
TObjectPtr<UHealthAttributeSet> HealthAttributeSet;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Abilities", meta = (AllowPrivateAccess = "true"))
|
||||
TObjectPtr<UStaminaAttributeSet> StaminaAttributeSet;
|
||||
|
||||
/** Currently-applied GE_HealthDebuff instance, if any - reapplied/removed wholesale by SetHealthDebuff so it behaves like the old "set to X" API instead of stacking. */
|
||||
FActiveGameplayEffectHandle HealthDebuffEffectHandle;
|
||||
|
||||
public:
|
||||
|
||||
//~ Begin IAbilitySystemInterface
|
||||
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
|
||||
//~ End IAbilitySystemInterface
|
||||
|
||||
/** Exposed for Blueprint to bind OnHealthChanged/OnHealthDebuffChanged/OnDeath - there's no "component bound event" for a plain UObject subobject, so bind via this getter's return pin instead (drag off it, search "Bind Event to..."). */
|
||||
UFUNCTION(BlueprintPure, Category = "Health")
|
||||
FORCEINLINE UHealthAttributeSet* GetHealthAttributeSet() const { return HealthAttributeSet; }
|
||||
|
||||
/** Exposed for Blueprint to bind OnStaminaChanged/OnFatigueChanged/OnExhausted - see GetHealthAttributeSet(). */
|
||||
UFUNCTION(BlueprintPure, Category = "Stamina")
|
||||
FORCEINLINE UStaminaAttributeSet* GetStaminaAttributeSet() const { return StaminaAttributeSet; }
|
||||
|
||||
// Health
|
||||
UFUNCTION(BlueprintPure, Category = "Health")
|
||||
float GetHealth() const;
|
||||
|
||||
/** Nominal cap, unaffected by HealthDebuff. Feeds the health bar's total width. */
|
||||
UFUNCTION(BlueprintPure, Category = "Health")
|
||||
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")
|
||||
void ApplyDamage(float Amount);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||
void ApplyHealing(float Amount);
|
||||
|
||||
/** Sets how much of MaxHealth is currently locked away (0-1) by applying/refreshing/removing a single GE_HealthDebuff instance. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||
void SetHealthDebuff(float NewHealthDebuff);
|
||||
|
||||
// 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;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Fatigue")
|
||||
float GetFatigue() const;
|
||||
|
||||
/** Deducts Amount from Stamina and resets the regen delay/ramp. No-op for Amount <= 0. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Stamina")
|
||||
void SpendStamina(float Amount);
|
||||
|
||||
/** Adds Amount to Stamina without affecting the regen delay/ramp (e.g. a stamina potion). No-op for Amount <= 0. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Stamina")
|
||||
void RestoreStamina(float Amount);
|
||||
|
||||
/** Sets Fatigue directly (0-1). Fatigue only ever builds on its own; this is the only way to reduce it (e.g. a debug key or a future rest mechanic). */
|
||||
UFUNCTION(BlueprintCallable, Category = "Fatigue")
|
||||
void SetFatigue(float NewFatigue);
|
||||
|
||||
/** Handles move inputs from either controls or UI interfaces */
|
||||
UFUNCTION(BlueprintCallable, Category="Input")
|
||||
virtual void DoMove(float Right, float Forward);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "KingshearthLegacyGameplayTags.h"
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_Data_Health_Damage, "Data.Health.Damage");
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_Data_Health_Healing, "Data.Health.Healing");
|
||||
UE_DEFINE_GAMEPLAY_TAG(TAG_Data_HealthDebuff_Multiplier, "Data.HealthDebuff.Multiplier");
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "NativeGameplayTags.h"
|
||||
|
||||
// SetByCaller data tags - the Name a GameplayEffect spec's magnitude is set against at runtime.
|
||||
KINGSHEARTHLEGACY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Data_Health_Damage)
|
||||
KINGSHEARTHLEGACY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Data_Health_Healing)
|
||||
KINGSHEARTHLEGACY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Data_HealthDebuff_Multiplier)
|
||||
@@ -9,8 +9,7 @@
|
||||
#include "KingshearthLegacy.h"
|
||||
#include "Widgets/Input/SVirtualJoystick.h"
|
||||
#include "UI/PlayerHUDWidget.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "StaminaComponent.h"
|
||||
#include "KingshearthLegacyCharacter.h"
|
||||
|
||||
void AKingshearthLegacyPlayerController::BeginPlay()
|
||||
{
|
||||
@@ -88,7 +87,11 @@ void AKingshearthLegacyPlayerController::OnPossess(APawn* InPawn)
|
||||
|
||||
if (PlayerHUD)
|
||||
{
|
||||
PlayerHUD->InitializeHUD(InPawn->FindComponentByClass<UHealthComponent>(), InPawn->FindComponentByClass<UStaminaComponent>());
|
||||
const AKingshearthLegacyCharacter* PossessedCharacter = Cast<AKingshearthLegacyCharacter>(InPawn);
|
||||
UHealthAttributeSet* PossessedHealthAttributeSet = PossessedCharacter ? PossessedCharacter->GetHealthAttributeSet() : nullptr;
|
||||
UStaminaAttributeSet* PossessedStaminaAttributeSet = PossessedCharacter ? PossessedCharacter->GetStaminaAttributeSet() : nullptr;
|
||||
|
||||
PlayerHUD->InitializeHUD(PossessedHealthAttributeSet, PossessedStaminaAttributeSet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ protected:
|
||||
/** Input mapping context setup */
|
||||
virtual void SetupInputComponent() override;
|
||||
|
||||
/** Spawns/initializes the player HUD once a pawn (and its HealthComponent) is available */
|
||||
/** Spawns/initializes the player HUD once a pawn (and its HealthAttributeSet) is available */
|
||||
virtual void OnPossess(APawn* InPawn) override;
|
||||
|
||||
/** Returns true if the player should use UMG touch controls */
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
#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;
|
||||
};
|
||||
@@ -1,28 +1,28 @@
|
||||
#include "HealthBarWidget.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "HealthAttributeSet.h"
|
||||
|
||||
void UHealthBarWidget::InitializeHealthBar(UHealthComponent* InHealthComponent)
|
||||
void UHealthBarWidget::InitializeHealthBar(UHealthAttributeSet* InHealthAttributeSet)
|
||||
{
|
||||
if (!InHealthComponent)
|
||||
if (!InHealthAttributeSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HealthComponent)
|
||||
if (HealthAttributeSet)
|
||||
{
|
||||
HealthComponent->OnHealthChanged.RemoveDynamic(this, &UHealthBarWidget::HandleHealthChanged);
|
||||
HealthComponent->OnHealthDebuffChanged.RemoveDynamic(this, &UHealthBarWidget::HandleHealthDebuffChanged);
|
||||
HealthComponent->OnDeath.RemoveDynamic(this, &UHealthBarWidget::HandleDeath);
|
||||
HealthAttributeSet->OnHealthChanged.RemoveDynamic(this, &UHealthBarWidget::HandleHealthChanged);
|
||||
HealthAttributeSet->OnHealthDebuffChanged.RemoveDynamic(this, &UHealthBarWidget::HandleHealthDebuffChanged);
|
||||
HealthAttributeSet->OnDeath.RemoveDynamic(this, &UHealthBarWidget::HandleDeath);
|
||||
}
|
||||
|
||||
HealthComponent = InHealthComponent;
|
||||
HealthAttributeSet = InHealthAttributeSet;
|
||||
|
||||
HealthComponent->OnHealthChanged.AddDynamic(this, &UHealthBarWidget::HandleHealthChanged);
|
||||
HealthComponent->OnHealthDebuffChanged.AddDynamic(this, &UHealthBarWidget::HandleHealthDebuffChanged);
|
||||
HealthComponent->OnDeath.AddDynamic(this, &UHealthBarWidget::HandleDeath);
|
||||
HealthAttributeSet->OnHealthChanged.AddDynamic(this, &UHealthBarWidget::HandleHealthChanged);
|
||||
HealthAttributeSet->OnHealthDebuffChanged.AddDynamic(this, &UHealthBarWidget::HandleHealthDebuffChanged);
|
||||
HealthAttributeSet->OnDeath.AddDynamic(this, &UHealthBarWidget::HandleDeath);
|
||||
|
||||
SetStatValue(HealthComponent->GetHealth(), HealthComponent->GetMaxHealth());
|
||||
SetMaxHealthDebuff(HealthComponent->GetHealthDebuff());
|
||||
SetStatValue(HealthAttributeSet->GetHealth(), HealthAttributeSet->GetNominalMaxHealth());
|
||||
SetMaxHealthDebuff(HealthAttributeSet->GetHealthDebuff());
|
||||
}
|
||||
|
||||
void UHealthBarWidget::SetMaxHealthDebuff(float InDebuffPercent)
|
||||
@@ -32,9 +32,9 @@ void UHealthBarWidget::SetMaxHealthDebuff(float InDebuffPercent)
|
||||
|
||||
void UHealthBarWidget::HandleHealthChanged(float NewHealth)
|
||||
{
|
||||
if (HealthComponent)
|
||||
if (HealthAttributeSet)
|
||||
{
|
||||
SetStatValue(NewHealth, HealthComponent->GetMaxHealth());
|
||||
SetStatValue(NewHealth, HealthAttributeSet->GetNominalMaxHealth());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
#include "UI/StatBarWidget.h"
|
||||
#include "HealthBarWidget.generated.h"
|
||||
|
||||
class UHealthComponent;
|
||||
class UHealthAttributeSet;
|
||||
|
||||
/**
|
||||
* Stat bar bound to a live UHealthComponent. Call InitializeHealthBar once the
|
||||
* component is known (typically from the owning PlayerController on possession).
|
||||
* Stat bar bound to a live UHealthAttributeSet. Call InitializeHealthBar once the
|
||||
* attribute set is known (typically from the owning PlayerController on possession).
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class UHealthBarWidget : public UStatBarWidget
|
||||
@@ -17,7 +17,7 @@ class UHealthBarWidget : public UStatBarWidget
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||
void InitializeHealthBar(UHealthComponent* InHealthComponent);
|
||||
void InitializeHealthBar(UHealthAttributeSet* InHealthAttributeSet);
|
||||
|
||||
/** Hook for a future debuff/status-effect system: 0-1 fraction of max health currently locked/unusable. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Health")
|
||||
@@ -38,5 +38,5 @@ protected:
|
||||
void OnHealthDepleted();
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Health")
|
||||
TObjectPtr<UHealthComponent> HealthComponent;
|
||||
TObjectPtr<UHealthAttributeSet> HealthAttributeSet;
|
||||
};
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
#include "UI/HealthBarWidget.h"
|
||||
#include "UI/StaminaBarWidget.h"
|
||||
|
||||
void UPlayerHUDWidget::InitializeHUD(UHealthComponent* InHealthComponent, UStaminaComponent* InStaminaComponent)
|
||||
void UPlayerHUDWidget::InitializeHUD(UHealthAttributeSet* InHealthAttributeSet, UStaminaAttributeSet* InStaminaAttributeSet)
|
||||
{
|
||||
if (HealthBar)
|
||||
{
|
||||
HealthBar->InitializeHealthBar(InHealthComponent);
|
||||
HealthBar->InitializeHealthBar(InHealthAttributeSet);
|
||||
}
|
||||
|
||||
if (StaminaBar)
|
||||
{
|
||||
StaminaBar->InitializeStaminaBar(InStaminaComponent);
|
||||
StaminaBar->InitializeStaminaBar(InStaminaAttributeSet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
class UHealthBarWidget;
|
||||
class UStaminaBarWidget;
|
||||
class UHealthComponent;
|
||||
class UStaminaComponent;
|
||||
class UHealthAttributeSet;
|
||||
class UStaminaAttributeSet;
|
||||
|
||||
/**
|
||||
* Top-level player HUD. Owns a health bar and a stamina bar and forwards
|
||||
@@ -21,7 +21,7 @@ class UPlayerHUDWidget : public UUserWidget
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category = "HUD")
|
||||
void InitializeHUD(UHealthComponent* InHealthComponent, UStaminaComponent* InStaminaComponent);
|
||||
void InitializeHUD(UHealthAttributeSet* InHealthAttributeSet, UStaminaAttributeSet* InStaminaAttributeSet);
|
||||
|
||||
protected:
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
#include "StaminaBarWidget.h"
|
||||
#include "StaminaComponent.h"
|
||||
#include "StaminaAttributeSet.h"
|
||||
|
||||
void UStaminaBarWidget::InitializeStaminaBar(UStaminaComponent* InStaminaComponent)
|
||||
void UStaminaBarWidget::InitializeStaminaBar(UStaminaAttributeSet* InStaminaAttributeSet)
|
||||
{
|
||||
if (!InStaminaComponent)
|
||||
if (!InStaminaAttributeSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (StaminaComponent)
|
||||
if (StaminaAttributeSet)
|
||||
{
|
||||
StaminaComponent->OnStaminaChanged.RemoveDynamic(this, &UStaminaBarWidget::HandleStaminaChanged);
|
||||
StaminaComponent->OnFatigueChanged.RemoveDynamic(this, &UStaminaBarWidget::HandleFatigueChanged);
|
||||
StaminaAttributeSet->OnStaminaChanged.RemoveDynamic(this, &UStaminaBarWidget::HandleStaminaChanged);
|
||||
StaminaAttributeSet->OnFatigueChanged.RemoveDynamic(this, &UStaminaBarWidget::HandleFatigueChanged);
|
||||
}
|
||||
|
||||
StaminaComponent = InStaminaComponent;
|
||||
StaminaAttributeSet = InStaminaAttributeSet;
|
||||
|
||||
StaminaComponent->OnStaminaChanged.AddDynamic(this, &UStaminaBarWidget::HandleStaminaChanged);
|
||||
StaminaComponent->OnFatigueChanged.AddDynamic(this, &UStaminaBarWidget::HandleFatigueChanged);
|
||||
StaminaAttributeSet->OnStaminaChanged.AddDynamic(this, &UStaminaBarWidget::HandleStaminaChanged);
|
||||
StaminaAttributeSet->OnFatigueChanged.AddDynamic(this, &UStaminaBarWidget::HandleFatigueChanged);
|
||||
|
||||
SetStatValue(StaminaComponent->GetStamina(), StaminaComponent->GetMaxStamina());
|
||||
SetFatigue(StaminaComponent->GetFatigue());
|
||||
SetStatValue(StaminaAttributeSet->GetStamina(), StaminaAttributeSet->GetNominalMaxStamina());
|
||||
SetFatigue(StaminaAttributeSet->GetFatigue());
|
||||
}
|
||||
|
||||
void UStaminaBarWidget::HandleStaminaChanged(float NewStamina)
|
||||
{
|
||||
if (StaminaComponent)
|
||||
if (StaminaAttributeSet)
|
||||
{
|
||||
SetStatValue(NewStamina, StaminaComponent->GetMaxStamina());
|
||||
SetStatValue(NewStamina, StaminaAttributeSet->GetNominalMaxStamina());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
#include "UI/StatBarWidget.h"
|
||||
#include "StaminaBarWidget.generated.h"
|
||||
|
||||
class UStaminaComponent;
|
||||
class UStaminaAttributeSet;
|
||||
|
||||
/**
|
||||
* Stat bar bound to a live UStaminaComponent. Call InitializeStaminaBar once the
|
||||
* component is known (typically from the owning PlayerController on possession),
|
||||
* Stat bar bound to a live UStaminaAttributeSet. Call InitializeStaminaBar once the
|
||||
* attribute set is known (typically from the owning PlayerController on possession),
|
||||
* mirroring UHealthBarWidget::InitializeHealthBar.
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
@@ -18,7 +18,7 @@ class UStaminaBarWidget : public UStatBarWidget
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Stamina")
|
||||
void InitializeStaminaBar(UStaminaComponent* InStaminaComponent);
|
||||
void InitializeStaminaBar(UStaminaAttributeSet* InStaminaAttributeSet);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Fatigue")
|
||||
void SetFatigue(float NewFatigue);
|
||||
@@ -38,7 +38,7 @@ protected:
|
||||
void OnFatigueChanged(float NewFatigue);
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Stamina")
|
||||
TObjectPtr<UStaminaComponent> StaminaComponent;
|
||||
TObjectPtr<UStaminaAttributeSet> StaminaAttributeSet;
|
||||
|
||||
/** 0-1 exhaustion amount. Drives the bar's penalty segment via SetPenaltyPercent. */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Fatigue")
|
||||
|
||||
Reference in New Issue
Block a user