GameplayEffectExecutionCalculation
參考: GameplayEffectExectutionCalculation
簡單來說,GameplayEffectExecutionCalculation就是GameplayEffect擁有的一個函數(shù)。它在技能開始時調(diào)用,或者是以固定間隔持續(xù)調(diào)用。
GameplayEffectExecutionCalculation幾乎可以勝任所有事情,因為Execute方法提供了必要的所有參數(shù)以影響對應(yīng)的actor, ability system , 甚至系統(tǒng)之外的世界。但是,GameplayEffectExecutionCalculation由于只支持c++編寫,且沒有ability類似的影響外部世界的機制,通常較難設(shè)置。在二者都能解決問題的情況下,我們傾向于使用ability。
GameplayEffectExecutionCalculation的獨到之處在于它能捕獲技能施放者和目標身上的attributes。當被激活時,GameplayEffectExecutionCalculation可以應(yīng)用modifier來修改attributes,也可以把attributes當做參數(shù)進行計算,也可以snapshot特定的attributes供以后使用(比如:你可以把GameplayEffectSpec掛載在一個火球上,當火球撞擊目標后觸發(fā)效果,此時火球造成的傷害將僅僅由釋放時人物的屬性決定)。這些功能使得它非常適合全局傷害計算,接下來的會舉一個這樣的例子。
例子
假設(shè)人物的attributes為:
- Health : 生命值
- AttackMultiplier :輸出傷害加成
- DefenseMultiplier: 接受傷害比例( 0 ~ 1)
- BaseAttackPower: 基礎(chǔ)傷害值
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函數(shù)提供CaptureDefinition(需要捕獲哪些屬性,捕獲對象是誰,是否要snapshot?)。因此,定義一個結(jié)構(gòu)體,并使用引擎提供的宏定義:
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的構(gòu)造函數(shù)中填充RelevantAttributesToCapture數(shù)組:
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函數(shù):
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));
}
}