ActionRPG-2-RPGAttributeSet
AttributeSet属性集是表示Actor各种属性值的集合,保存在ASC中,是GE的主要作用目标
#pragma once
#include "ActionRPG.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "RPGAttributeSet.generated.h"
/**
* Uses macros from AttributeSet.h
* 属性访问器宏,代表四个方法:
* 拿到属性:static FGameplayAttribute UMyHealthSet::GetHealthAttribute();
* 拿到值:FORCEINLINE float UMyHealthSet::GetHealth()const;
* 设置值:FORCEINLINE void UMyHealthSet::SetHealth(float NewVal);
* 初始化值:FORCEINLINE void UMyHealthSet::InitHealth(float Newval);
*/
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/**
* This holds all of the attributes used by abilities, it instantiates a copy of this on every character
*/
UCLASS()
class ACTIONRPG_API URPGAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
// Constructor and overrides
URPGAttributeSet();
/**
* @brief 属性值发生改变前被调用,可以检查属性值是否超出范围,如果超出则将属性值限制在合理范围内,
* 或在修改属性值时触发一些游戏事件。
* @param Attribute 要修改的属性,需要通过修改“NewValue”参数对属性值进行限制、验证或修改。
* @param NewValue 修改后的属性。
*/
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
/**
* @brief 用于在游戏效果GE执行后进行额外的操作
* @desc 重写函数时可以根据Data参数中的信息进行特定的逻辑处理,例如根据属性值的变化更新UI显示或触发一些游戏性事件。
* 此函数在GE执行后被调用,因此可以在函数中取到GE对属性值的影响,并对GE的结果进行处理。
* 同时,由于这个函数不会对属性值进行修改,因此可以安全的进行复杂的逻辑处理,而不会对属性值产生副作用。
* 另外,此函数在客户端与服务器上都会被调用,因此在编写逻辑时要考虑到网络同步和安全性问题。
* @param Data 参数包包括GE的相关信息如类型、持续事件、来源等。
*/
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
/**
* @brief 用于定义哪些属性需要在网络上同步
* @desc 每个属性都通过CONDITION参数来指定何时需要同步,例如仅客户端、仅服务端和都要同步。
* 此外还可以使用NOTIFY参数来指定当前属性值。
* 在GetLifeTimeReplicatedProps中可以使用DOREPLIFETIME_CONDITION_NOTIFY宏来定义属性同步的同步方式和条件。
* 此函数是在服务端和客户端上都会被调用,因此需要确保同步的属性和同步方式符合网络同步的要求,以避免不必要的问题。
* 同时,在使用DOREPLIFETIME_CONDITION_NOTIFY宏时,也要注意避免出现死循环等问题,以确保程序的稳定性和安全性。
* @param OutLifetimeProps 该参数的参数是一个数组,可以添加属性来保存需要同步的属性信息。
*/
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
/**
* @brief Helper function to proportionally adjust the value of an attribute when it's associated max attribute changes.
* (i.e. When MaxHealth increases, Health increases by an amount that maintains the same percentage as before)
* @brief 用于处理当一个属性的最大值发生改变时,该属性如何调整。
* 调用时该函数会根据最大属性值和新的最大值的比例来调整受影响的属性值以确保受影响的属性值在新的最大范围内。例如50/100 => 100/200
* 需要注意的是,此函数只是一个辅助函数,不能用于修改属性值,如果要在修改属性值时处理属性值的最大值,可以在重写PreAttributeChange
* 函数时进行处理,或使用GE中的AttributeClamp来实现。
* @prarm AffectedAttribute 受影响的属性
* @param MaxAttribute 最大属性值
* @prarm NewMaxValue 新的最大属性值
* @prarm AffectedAttributeProperty 受影响属性的名词
*/
void AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty);
public:
/**
* Current Health, when 0 we expect owner to die. Capped by MaxHealth
* 其中ReplicatedUsing = OnRep_Health是强制委托,当这个值被改变时调用OnRep_Health函数
* ATTRIBUTE_ACCESSOTS属性访问器宏是在属性声明后给属性添加的宏,其定义在源码AttributeSet.h最后
*/
UPROPERTY(BlueprintReadOnly, Category = "Health", ReplicatedUsing=OnRep_Health)
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(URPGAttributeSet, Health)
/** MaxHealth is its own attribute, since GameplayEffects may modify it */
UPROPERTY(BlueprintReadOnly, Category = "Health", ReplicatedUsing=OnRep_MaxHealth)
FGameplayAttributeData MaxHealth;
ATTRIBUTE_ACCESSORS(URPGAttributeSet, MaxHealth)
/** Current Mana, used to execute special abilities. Capped by MaxMana */
UPROPERTY(BlueprintReadOnly, Category = "Mana", ReplicatedUsing=OnRep_Mana)
FGameplayAttributeData Mana;
ATTRIBUTE_ACCESSORS(URPGAttributeSet, Mana)
/** MaxMana is its own attribute, since GameplayEffects may modify it */
UPROPERTY(BlueprintReadOnly, Category = "Mana", ReplicatedUsing = OnRep_MaxMana)
FGameplayAttributeData MaxMana;
ATTRIBUTE_ACCESSORS(URPGAttributeSet, MaxMana)
/**
* AttackPower of the attacker is multiplied by the base Damage to reduce health, so 1.0 means no bonus
* 攻击者的攻击力乘以基础伤害以减少生命值,因此1.0意味着没有奖励
*/
UPROPERTY(BlueprintReadOnly, Category = "Damage", ReplicatedUsing = OnRep_AttackPower)
FGameplayAttributeData AttackPower;
ATTRIBUTE_ACCESSORS(URPGAttributeSet, AttackPower)
/**
* Base Damage is divided by DefensePower to get actual damage done, so 1.0 means no bonus
* 基础伤害除以防御能力以获得实际伤害,因此1.0意味着没有额外奖励
*/
UPROPERTY(BlueprintReadOnly, Category = "Damage", ReplicatedUsing = OnRep_DefensePower)
FGameplayAttributeData DefensePower;
ATTRIBUTE_ACCESSORS(URPGAttributeSet, DefensePower)
/** MoveSpeed affects how fast characters can move */
UPROPERTY(BlueprintReadOnly, Category = "MoveSpeed", ReplicatedUsing = OnRep_MoveSpeed)
FGameplayAttributeData MoveSpeed;
ATTRIBUTE_ACCESSORS(URPGAttributeSet, MoveSpeed)
/**
* Damage is a 'temporary' attribute used by the DamageExecution to calculate final damage, which then turns into -Health
* 是DamageExecution用来计算最终伤害的“临时”属性,然后转化为-健康
*/
UPROPERTY(BlueprintReadOnly, Category = "Damage")
FGameplayAttributeData Damage;
ATTRIBUTE_ACCESSORS(URPGAttributeSet, Damage)
public:
// These OnRep functions exist to make sure that the ability system internal representations are synchronized properly during replication
UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldValue);
UFUNCTION()
virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldValue);
UFUNCTION()
virtual void OnRep_Mana(const FGameplayAttributeData& OldValue);
UFUNCTION()
virtual void OnRep_MaxMana(const FGameplayAttributeData& OldValue);
UFUNCTION()
virtual void OnRep_AttackPower(const FGameplayAttributeData& OldValue);
UFUNCTION()
virtual void OnRep_DefensePower(const FGameplayAttributeData& OldValue);
UFUNCTION()
virtual void OnRep_MoveSpeed(const FGameplayAttributeData& OldValue);
};
#include "Abilities/RPGAttributeSet.h"
#include "Abilities/RPGAbilitySystemComponent.h"
#include "RPGCharacterBase.h"
#include "GameplayEffect.h"
#include "GameplayEffectExtension.h" // 包含Data.EffectSpec.GetContext()
URPGAttributeSet::URPGAttributeSet(): Health(1.f), MaxHealth(1.f), Mana(0.f), MaxMana(0.f), AttackPower(1.0f),
DefensePower(1.0f), MoveSpeed(1.0f), Damage(0.0f) {
//
}
void URPGAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) {
// This is called whenever attributes change, so for max health/mana we want to scale the current totals to match
// 这是在属性发生变化时调用的,因此对于最大生命值/法力值,我们希望缩放当前总数以匹配
Super::PreAttributeChange(Attribute, NewValue);
if (Attribute == GetMaxHealthAttribute()) {
AdjustAttributeForMaxChange(Health, MaxHealth, NewValue, GetHealthAttribute());
}
else if (Attribute == GetMaxManaAttribute()) {
AdjustAttributeForMaxChange(Mana, MaxMana, NewValue, GetManaAttribute());
}
}
void URPGAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) {
Super::PostGameplayEffectExecute(Data);
// 获取GE的上下文
FGameplayEffectContextHandle Context = Data.EffectSpec.GetContext();
UAbilitySystemComponent* Source = Context.GetOriginalInstigatorAbilitySystemComponent();
const FGameplayTagContainer& SourceTags = *Data.EffectSpec.CapturedSourceTags.GetAggregatedTags();
// Compute the delta between old and new, if it is available
// 计算新旧之间的增量(如果可用)
float DeltaValue = 0;
// 检查当前的GE是否是数值加法类型,如果是加法则EvaluatedData.Magnitude表示修改的值,否则代表修改的百分比。
// 当前PostGameplayEffectExecute函数只是暂存了DeltaValue的值,并没有立即对属性进行更改,因为POSTGEExecute是
// 在GE执行之后,如果在中国函数里直接修改属性的值,会导致新的修改又被重新应用到当前的GE上从而导致属性值错误,
// 因此在PostGEExecute中只暂存,在PreAttributeChange函数中再对属性进行更改
if (Data.EvaluatedData.ModifierOp == EGameplayModOp::Type::Additive) {
// If this was additive, store the raw delta value to be passed along later
// 如果这是加法,则存储稍后传递的原始delta值
DeltaValue = Data.EvaluatedData.Magnitude;
}
// Get the Target actor, which should be our owner
// 获取目标演员,该演员应该是我们的所有者
AActor* TargetActor = nullptr;
AController* TargetController = nullptr;
ARPGCharacterBase* TargetCharacter = nullptr;
if (Data.Target.AbilityActorInfo.IsValid()/*判断GE的目标角色信息有效*/&& Data.Target.AbilityActorInfo->AvatarActor.IsValid()) {
TargetActor = Data.Target.AbilityActorInfo->AvatarActor.Get();
TargetController = Data.Target.AbilityActorInfo->PlayerController.Get();
TargetCharacter = Cast<ARPGCharacterBase>(TargetActor);
}
if (Data.EvaluatedData.Attribute == GetDamageAttribute()) {
// Get the Source actor 获取来源的actor
AActor* SourceActor = nullptr;
AController* SourceController = nullptr;
ARPGCharacterBase* SourceCharacter = nullptr;
if (Source && Source->AbilityActorInfo.IsValid() && Source->AbilityActorInfo->AvatarActor.IsValid()) {
SourceActor = Source->AbilityActorInfo->AvatarActor.Get();
SourceController = Source->AbilityActorInfo->PlayerController.Get();
if (SourceController == nullptr && SourceActor != nullptr) {
if (APawn* Pawn = Cast<APawn>(SourceActor)) {
SourceController = Pawn->GetController();
}
}
// Use the controller to find the source pawn 使用controller寻找来源的pawn
if (SourceController) {
SourceCharacter = Cast<ARPGCharacterBase>(SourceController->GetPawn());
} else {
SourceCharacter = Cast<ARPGCharacterBase>(SourceActor);
}
// Set the causer actor based on context if it's set
// 如果已设置,则根据上下文设置sourceActor
if (Context.GetEffectCauser()) {
SourceActor = Context.GetEffectCauser();
}
}
// Try to extract a hit result.尝试提取命中结果
FHitResult HitResult;
if (Context.GetHitResult()) {
HitResult = *Context.GetHitResult();
}
// Store a local copy of the amount of damage done and clear the damage attribute
// 存储所造成伤害的本地副本,并清除伤害属性
const float LocalDamageDone = GetDamage();
SetDamage(0.f);
if (LocalDamageDone > 0){
// Apply the health change and then clamp it.应用health变化,然后钳制他
const float OldHealth = GetHealth();
SetHealth(FMath::Clamp(OldHealth - LocalDamageDone, 0.0f, GetMaxHealth()));
if (TargetCharacter) {
// This is proper damage.适当的伤害
TargetCharacter->HandleDamage(LocalDamageDone, HitResult, SourceTags, SourceCharacter, SourceActor);
// Call for all health changes.调用所有health change
TargetCharacter->HandleHealthChanged(-LocalDamageDone, SourceTags);
}
}
} else if (Data.EvaluatedData.Attribute == GetHealthAttribute()) {
// Handle other health changes such as from healing or direct modifiers
// 处理其他健康变化,如治疗或直接修改
// First clamp it.首先钳制他们
SetHealth(FMath::Clamp(GetHealth(), 0.0f, GetMaxHealth()));
if (TargetCharacter) {
// Call for all health changes.调用所有health change
TargetCharacter->HandleHealthChanged(DeltaValue, SourceTags);
}
} else if (Data.EvaluatedData.Attribute == GetManaAttribute()) {
// Clamp mana
SetMana(FMath::Clamp(GetMana(), 0.0f, GetMaxMana()));
if (TargetCharacter) {
// Call for all mana changes
TargetCharacter->HandleManaChanged(DeltaValue, SourceTags);
}
}
else if (Data.EvaluatedData.Attribute == GetMoveSpeedAttribute()) {
if (TargetCharacter) {
// Call for all movespeed changes
TargetCharacter->HandleMoveSpeedChanged(DeltaValue, SourceTags);
}
}
}
void URPGAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const {
// 使用DOREPLIFETIME宏为AttributeSet中的属性生成默认的同步代码,以便在网络游戏中进行网络同步,默认为当属性变化后就会被同步
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(URPGAttributeSet, Health);
DOREPLIFETIME(URPGAttributeSet, MaxHealth);
DOREPLIFETIME(URPGAttributeSet, Mana);
DOREPLIFETIME(URPGAttributeSet, MaxMana);
DOREPLIFETIME(URPGAttributeSet, AttackPower);
DOREPLIFETIME(URPGAttributeSet, DefensePower);
DOREPLIFETIME(URPGAttributeSet, MoveSpeed);
}
void URPGAttributeSet::AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute,
float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty) {
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();
const float CurrentMaxValue = MaxAttribute.GetCurrentValue();
// 判断当前最大值和新的最大值相等
if (!FMath::IsNearlyEqual(CurrentMaxValue, NewMaxValue) && AbilityComp) {
// Change current value to maintain the current Val / Max percent
// 获取受影响属性的最大值
const float CurrentValue = AffectedAttribute.GetCurrentValue();
// 计算出要变化的值
float NewDelta = (CurrentMaxValue > 0.f) ? (CurrentValue * NewMaxValue / CurrentMaxValue) - CurrentValue : NewMaxValue;
// 将要变化的值应用到受影响属性上。
// ApplyModToAttributeUnsafe可以直接修改属性的值而不需要使用GE,这个函数通常用于处理属性值的变化
AbilityComp->ApplyModToAttributeUnsafe(AffectedAttributeProperty, EGameplayModOp::Additive, NewDelta);
}
}
void URPGAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue) {
// GAMEPLAYATTRIBUTE_REPNOTIFY宏用于在属性同步时触发属性值的更新,在GAS的OnRep_中使用来触发属性值的更新
// 参数一是当前类名,参数二是要同步的属性名称,参数三是同步前的属性(可选)
// 需要注意的是GAMEPLAYATTRIBUTE_REPNOTIFY宏会自动调用PostGameplayEffectExecute进行属性值更新,因此调用这个宏时通常不需要手动更新
GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, Health, OldValue);
}
void URPGAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldValue) {
GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, MaxHealth, OldValue);
}
void URPGAttributeSet::OnRep_Mana(const FGameplayAttributeData& OldValue) {
GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, Mana, OldValue);
}
void URPGAttributeSet::OnRep_MaxMana(const FGameplayAttributeData& OldValue) {
GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, MaxMana, OldValue);
}
void URPGAttributeSet::OnRep_AttackPower(const FGameplayAttributeData& OldValue) {
GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, AttackPower, OldValue);
}
void URPGAttributeSet::OnRep_DefensePower(const FGameplayAttributeData& OldValue) {
GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, DefensePower, OldValue);
}
void URPGAttributeSet::OnRep_MoveSpeed(const FGameplayAttributeData& OldValue) {
GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, MoveSpeed, OldValue);
}
配置好GE后给玩家类(比如Character)添加:
protected:
protected:
UPROPERTY()
class UBasicAttributeSet* BasicAttributeSet;
并且在构造函数里面初始化:
BasicAttributeSet = CreateDefaultSubobject<UBasicAttributeSet>(TEXT("BasicAttributeSet"))
然后在其他类里读取值就是:获得玩家类->GetAbilitySystemComponent->GetFloatAttributeFromAbilitySystemComponent
改变值就是:获得玩家类->GetAbilitySystemComponent->ApplyGameplayEffectToSelf调用一个GE修改该值
class UBasicAttributeSet* BasicAttributeSet;
并且在构造函数里面初始化:
BasicAttributeSet = CreateDefaultSubobject<UBasicAttributeSet>(TEXT("BasicAttributeSet"))
然后在其他类里读取值就是:获得玩家类->GetAbilitySystemComponent->GetFloatAttributeFromAbilitySystemComponent
改变值就是:获得玩家类->GetAbilitySystemComponent->ApplyGameplayEffectToSelf调用一个GE修改该值

浙公网安备 33010602011771号