GameplayEffectExecutionCalculation
参考: GameplayEffectExectutionCalculation
简单来说,GameplayEffectExecutionCalculation就是GameplayEffect拥有的一个函数。它在技能开始时调用,或者是以固定间隔持续调用。
GameplayEffectExecutionCalculation几乎可以胜任所有事情,因为Execute
方法提供了必要的所有参数以影响对应的actor, ability system , 甚至系统之外的世界。但是,GameplayEffectExecutionCalculation由于只支持c++编写,且没有ability类似的影响外部世界的机制,通常较难设置。在二者都能解决问题的情况下,我们倾向于使用ability。
GameplayEffectExecutionCalculation的独到之处在于它能捕获技能施放者和目标身上的attributes。当被激活时,GameplayEffectExecutionCalculation可以应用modifier来修改attributes,也可以把attributes当做参数进行计算,也可以snapshot特定的attributes供以后使用(比如:你可以把GameplayEffectSpec挂载在一个火球上,当火球撞击目标后触发效果,此时火球造成的伤害将仅仅由释放时人物的属性决定)。这些功能使得它非常适合全局伤害计算,接下来的会举一个这样的例子。
例子
假设人物的attributes为:
- Health : 生命值
- AttackMultiplier :输出伤害加成
- DefenseMultiplier: 接受伤害比例( 0 ~ 1)
- BaseAttackPower: 基础伤害值
UCLASS()
class UMyAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
//Hitpoints. Self-explanatory.
UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite)
FGameplayAttributeData Health;
//Outgoing damage-multiplier.
UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite, meta = (HideFromModifiers))
FGameplayAttributeData AttackMultiplier;
//Incoming damage-multiplier.
UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite)
FGameplayAttributeData DefenseMultiplier;
//Base damage of an outgoing attack.
UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite)
FGameplayAttributeData BaseAttackPower;
}
我们需要给Execute
函数提供CaptureDefinition
(需要捕获哪些属性,捕获对象是谁,是否要snapshot?)。因此,定义一个结构体,并使用引擎提供的宏定义:
struct AttStruct
{
DECLARE_ATTRIBUTE_CAPTUREDEF(Health); //The DECLARE_ATTRIBUTE_CAPTUREDEF macro actually only declares two variables. The variable names are dependent on the input, however. Here they will be HealthProperty(which is a UPROPERTY pointer)
//and HealthDef(which is a FGameplayEffectAttributeCaptureDefinition).
DECLARE_ATTRIBUTE_CAPTUREDEF(AttackMultiplier); //Here AttackMultiplierProperty and AttackMultiplierDef. I hope you get the drill.
DECLARE_ATTRIBUTE_CAPTUREDEF(DefenseMultiplier);
DECLARE_ATTRIBUTE_CAPTUREDEF(BaseAttackPower);
AttStruct()
{
// We define the values of the variables we declared now. In this example, HealthProperty will point to the Health attribute in the UMyAttributeSet on the receiving target of this execution. The last parameter is a bool, and determines if we snapshot the attribute's value at the time of definition.
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, Health, Target, false);
//This here is a different example: We still take the attribute from UMyAttributeSet, but this time it is BaseAttackPower, and we look at the effect's source for it. We also want to snapshot is because the effect's strength should be determined during its initial creation. A projectile wouldn't change
//damage values depending on the source's stat changes halfway through flight, after all.
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, BaseAttackPower, Source, true);
//The same rules apply for the multiplier attributes too.
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, AttackMultiplier, Source, true);
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, DefenseMultiplier, Target, false);
}
};
之后,我们在GameplayEffectExecutionCalculation
的构造函数中填充RelevantAttributesToCapture
数组:
UDamageExec::UDamageExec(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
AttStruct Attributes;
RelevantAttributesToCapture.Add(Attributes.HealthDef); //RelevantAttributesToCapture is the array that contains all attributes you wish to capture, without exceptions.
InvalidScopedModifierAttributes.Add(Attributes.HealthDef); //However, an attribute added here on top of being added in RelevantAttributesToCapture will still be captured, but will not be shown for potential in-function modifiers in the GameplayEffect blueprint, more on that later.
RelevantAttributesToCapture.Add(Attributes.BaseAttackPowerDef);
RelevantAttributesToCapture.Add(Attributes.DefenseMultiplierDef);
RelevantAttributesToCapture.Add(Attributes.AttackMultiplierDef);
}
最后,重写Execute_Implementation
函数:
void UDamageExec::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
AttStruct Attributes; //Creating the attribute struct, we will need its values later when we want to get the attribute values.
UAbilitySystemComponent* TargetAbilitySystemComponent = ExecutionParams.GetTargetAbilitySystemComponent(); //We put AbilitySystemComponents into little helper variables. Not necessary, but it helps keeping us from typing so much.
UAbilitySystemComponent* SourceAbilitySystemComponent = ExecutionParams.GetSourceAbilitySystemComponent();
AActor* SourceActor = SourceAbilitySystemComponent ? SourceAbilitySystemComponent->AvatarActor : nullptr; //If our AbilitySystemComponents are valid, we get each their owning actors and put them in variables. This is mostly to prevent crashing by trying to get the AvatarActor variable from
AActor* TargetActor = TargetAbilitySystemComponent ? TargetAbilitySystemComponent->AvatarActor : nullptr; //a null pointer.
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags(); //Some more helper variables: Spec is the spec this execution originated from, and the Source/TargetTags are pointers to the tags granted to source/target actor, respectively.
FAggregatorEvaluateParameters EvaluationParameters; //We use these tags to set up an FAggregatorEvaluateParameters struct, which we will need to get the values of our captured attributes later in this function.
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
float Health = 0.f;
//Alright, this is where we get the attribute's captured value into our function. Damage().HealthDef is the definition of the attribute we want to get, we defined EvaluationParameters just above us, and Health is the variable where we will put the captured value into(the Health variable we just declared)
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Attributes.HealthDef, EvaluationParameters, Health);
float BaseAttackPower = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Attribute.BaseAttackPowerDef, EvaluationParameters, BaseAttackPower); // We do this for all other attributes, as well.
float AttackMultiplier = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Attribute.AttackMultiplierDef, EvaluationParameters, AttackMultiplier);
float DefensePower = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Attribute.DefenseMultiplierPowerDef, EvaluationParameters, DefenseMultiplier);
//Finally, we go through our simple example damage calculation. BaseAttackPower and AttackMultiplier come from soruce, DefensePower comes from target.
float DamageDone = BaseAttackPower * AttackMultiplier * DefensePower;
//An optional step is to clamp to not take health lower than 0. This can be ignored, or implemented in the attribute sets' PostGameplayEffectExecution function. Your call, really.
DamageDone = FMath::Min<float>( DamageDone, Health );
//Finally, we check if we even did any damage in this whole ordeal. If yes, then we will add an outgoing execution modifer to the Health attribute we got from our target, which is a modifier that can still be thrown out by the attribute system if it wishes to throw out the GameplayEffectExecutionCalculation.
if (DamageDone > 0.f)
{
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(Attributes.HealthProperty, EGameplayModOp::Additive, -DamageDone));
}
//Congratulations, your damage calculation is complete!
RPGDamageExecution.h/cpp
ActionRPG的代码与上面举的例子极其相似,就不展开讲了。
头文件:
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ActionRPG.h"
#include "GameplayEffectExecutionCalculation.h"
#include "RPGDamageExecution.generated.h"
/**
* A damage execution, which allows doing damage by combining a raw Damage number with AttackPower and DefensePower
* Most games will want to implement multiple game-specific executions
*/
UCLASS()
class ACTIONRPG_API URPGDamageExecution : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
// Constructor and overrides
URPGDamageExecution();
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
};
源文件:
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "Abilities/RPGDamageExecution.h"
#include "Abilities/RPGAttributeSet.h"
#include "AbilitySystemComponent.h"
struct RPGDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(DefensePower);
DECLARE_ATTRIBUTE_CAPTUREDEF(AttackPower);
DECLARE_ATTRIBUTE_CAPTUREDEF(Damage);
RPGDamageStatics()
{
// Capture the Target's DefensePower attribute. Do not snapshot it, because we want to use the health value at the moment we apply the execution.
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, DefensePower, Target, false);
// Capture the Source's AttackPower. We do want to snapshot this at the moment we create the GameplayEffectSpec that will execute the damage.
// (imagine we fire a projectile: we create the GE Spec when the projectile is fired. When it hits the target, we want to use the AttackPower at the moment
// the projectile was launched, not when it hits).
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, AttackPower, Source, true);
// Also capture the source's raw Damage, which is normally passed in directly via the execution
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, Damage, Source, true);
}
};
static const RPGDamageStatics& DamageStatics()
{
static RPGDamageStatics DmgStatics;
return DmgStatics;
}
URPGDamageExecution::URPGDamageExecution()
{
RelevantAttributesToCapture.Add(DamageStatics().DefensePowerDef);
RelevantAttributesToCapture.Add(DamageStatics().AttackPowerDef);
RelevantAttributesToCapture.Add(DamageStatics().DamageDef);
}
void URPGDamageExecution::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
UAbilitySystemComponent* TargetAbilitySystemComponent = ExecutionParams.GetTargetAbilitySystemComponent();
UAbilitySystemComponent* SourceAbilitySystemComponent = ExecutionParams.GetSourceAbilitySystemComponent();
AActor* SourceActor = SourceAbilitySystemComponent ? SourceAbilitySystemComponent->AvatarActor : nullptr;
AActor* TargetActor = TargetAbilitySystemComponent ? TargetAbilitySystemComponent->AvatarActor : nullptr;
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
// Gather the tags from the source and target as that can affect which buffs should be used
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
// --------------------------------------
// Damage Done = Damage * AttackPower / DefensePower
// If DefensePower is 0, it is treated as 1.0
// --------------------------------------
float DefensePower = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DefensePowerDef, EvaluationParameters, DefensePower);
if (DefensePower == 0.0f)
{
DefensePower = 1.0f;
}
float AttackPower = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().AttackPowerDef, EvaluationParameters, AttackPower);
float Damage = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DamageDef, EvaluationParameters, Damage);
float DamageDone = Damage * AttackPower / DefensePower;
if (DamageDone > 0.f)
{
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().DamageProperty, EGameplayModOp::Additive, DamageDone));
}
}