Attibute Set
/**
* Defines the set of all GameplayAttributes for your game
* Games should subclass this and add FGameplayAttributeData properties to represent attributes like health, damage, etc
* AttributeSets are added to the actors as subobjects, and then registered with the AbilitySystemComponent
* It often desired to have several sets per project that inherit from each other
* You could make a base health set, then have a player set that inherits from it and adds more attributes
*/
Attribute Set
中定义的属性就是RPG游戏中的人物属性,如血量、魔法值。数据类型为FGameplayAttributeData
(对float的封装)。
之前写过一篇关于AttributeSet的文章,已经讲的和详细了,这里就不复述了。
GAS - Gameplay Attributes
RPGAttributeSet.cpp
首先,因为AttributeSet是支持Replication的,不要漏了GetLifetimeReplicatedProps
。
void URPGAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
RPGAction添加了函数 AdjustAttributeForMaxChange
, 其作用是,当属性最大值发生改变时,按比例改变当前值。
void URPGAttributeSet::AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty)
{
//获得UAbilitySystemComponent实例
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();、
//Getter方法获取的都是current value而不是base value
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;
// 通过UAbilitySystemComponent实例
AbilityComp->ApplyModToAttributeUnsafe(AffectedAttributeProperty, EGameplayModOp::Additive, NewDelta);
}
}
我们要注意到对属性值的修改是通过调用GameplayAbilityComponent的ApplyModToAttributeUnsafe
方法实现的。
接下来是两个利用GASt提供的回调函数的例子。
- PreAttributeChange : 任意属性发生变化前都会调用,通常用于强加规则给数值
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());
}
}
- PostGameplayEffectExecute: 任意影响Attribute的GameplayEffect执行之后都会调用,通常用于clamp数值,触发游戏中的事件
void URPGAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
// Context !!! 还记得讲GameplayAbility时提到的RPGAbilityType么?
// 我们可以通过引擎提供的FGameplayEffectContextHandle得到许多关联的信息
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;
if (Data.EvaluatedData.ModifierOp == EGameplayModOp::Type::Additive)
{
// If this was additive, store the raw delta value to be passed along later
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() && 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
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
if (SourceController)
{
SourceCharacter = Cast<ARPGCharacterBase>(SourceController->GetPawn());
}
else
{
SourceCharacter = Cast<ARPGCharacterBase>(SourceActor);
}
// Set the causer actor based on context if it's set
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
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
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
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);
}
}
}
我们需要注意对FGameplayEffectContextHandle
的使用。如果引擎默认提供的信息不足,我们可以考虑拓展FGameplayEffectContextHandle
类。这里所有的Set函数也都是通过AbilitySystemComponent调用方法改变数值的。
随着AttributeSet中的属性增多,回调函数中的判断也会变得更加复杂。
网友评论