Execution Calculation - UGameplayEffectExecutionCalculation
UGameplayEffectExecutionCalculation 개요
기존 UGameplayModMagnitudeCalculation와는 다른 Custom Calculation 클래스이다.
UGameplayModMagnitudeCalculation과 UGameplayEffectExecutionCalculation모두 UGameplayEffectCalculation 클래스를 상속하고 있다.
// UGameplayModMagnitudeCalculation (기존 사용한 MMC)
class GAMEPLAYABILITIES_API UGameplayModMagnitudeCalculation : public UGameplayEffectCalculation
// UGameplayEffectExecutionCalculation
class GAMEPLAYABILITIES_API UGameplayEffectExecutionCalculation : public UGameplayEffectCalculation
// UGameplayEffectCalculation (부모 클래스)
/** Abstract base for specialized gameplay effect calculations; Capable of specifing attribute captures */
UCLASS(BlueprintType, Blueprintable, Abstract)
class GAMEPLAYABILITIES_API UGameplayEffectCalculation : public UObject
{
GENERATED_UCLASS_BODY()
public:
/** Simple accessor to capture definitions for attributes */
virtual const TArray<FGameplayEffectAttributeCaptureDefinition>& GetAttributeCaptureDefinitions() const;
protected:
/** Attributes to capture that are relevant to the calculation */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Attributes)
TArray<FGameplayEffectAttributeCaptureDefinition> RelevantAttributesToCapture;
};
/**
* UGameplayEffect
* The GameplayEffect definition. This is the data asset defined in the editor that drives everything.
* This is only blueprintable to allow for templating gameplay effects. Gameplay effects should NOT contain blueprint graphs.
*/
UCLASS(Blueprintable, meta = (ShortTooltip="A GameplayEffect modifies attributes and tags."))
class GAMEPLAYABILITIES_API UGameplayEffect : public UObject, public IGameplayTagAssetInterface
{
public:
GENERATED_UCLASS_BODY()
// ...
/** Array of modifiers that will affect the target of this effect */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=GameplayEffect, meta=(TitleProperty=Attribute))
TArray<FGameplayModifierInfo> Modifiers;
// UGameplayEffect 클래스 내부의 FGameplayModifierInfo 변수
// FGameplayModifierInfo 구조체는 FGameplayEffectModifierMagnitude를 멤버변수로 지님
// FGameplayEffectModifierMagnitude 구조체는 생성자 중 하나로 FCustomCalculationBasedFloat를 받음
// FCustomCalculationBasedFloat 구조체가 UGameplayModMagnitudeCalculation를 래핑하는 구조체임.
// (UGameplayModMagnitudeCalculation의 서브 클래스를 멤버 변수로 지님)
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = GameplayEffect)
TArray<FGameplayEffectExecutionDefinition> Executions;
// FGameplayModifierInfo 변수 아래에 Executions라 이름 붙여진 FGameplayEffectExecutionDefinition 구조체 변수
// FGameplayEffectExecutionDefinition 구조체는 UGameplayEffectExecutionCalculation를 소유한다.
}
/**
* Struct representing the definition of a custom execution for a gameplay effect.
* Custom executions run special logic from an outside class each time the gameplay effect executes.
*/
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FGameplayEffectExecutionDefinition
{
GENERATED_USTRUCT_BODY()
/**
* Gathers and populates the specified array with the capture definitions that the execution would like in order
* to perform its custom calculation. Up to the individual execution calculation to handle if some of them are missing
* or not.
*
* @param OutCaptureDefs [OUT] Capture definitions requested by the execution
*/
void GetAttributeCaptureDefinitions(OUT TArray<FGameplayEffectAttributeCaptureDefinition>& OutCaptureDefs) const;
/** Custom execution calculation class to run when the gameplay effect executes */
UPROPERTY(EditDefaultsOnly, Category=Execution)
TSubclassOf<UGameplayEffectExecutionCalculation> CalculationClass;
/** These tags are passed into the execution as is, and may be used to do conditional logic */
UPROPERTY(EditDefaultsOnly, Category = Execution)
FGameplayTagContainer PassedInTags;
/** Modifiers that are applied "in place" during the execution calculation */
UPROPERTY(EditDefaultsOnly, Category = Execution)
TArray<FGameplayEffectExecutionScopedModifierInfo> CalculationModifiers;
/** Deprecated. */
UPROPERTY()
TArray<TSubclassOf<UGameplayEffect>> ConditionalGameplayEffectClasses_DEPRECATED;
/** Other Gameplay Effects that will be applied to the target of this execution if the execution is successful. Note if no execution class is selected, these will always apply. */
UPROPERTY(EditDefaultsOnly, Category = Execution)
TArray<FConditionalGameplayEffect> ConditionalGameplayEffects;
};
- GameplayEffect 블루프린트의 Execution 항목에서 UGameplayEffectExecutionCalculation 서브클래스를 지정할 수 있다.
(Modifier 항목이 아니므로 주의)

UGameplayEffectExecutionCalculation는 Attribute를 캡쳐할 수 있으며, MMC(UGameplayModMagnitudeCalculation)와는 다르게 하나 이상의 Attribute를 변경할 수 있는 등 좀 더 커스터마이징의 여지가 크다.
반대로, 예측(Prediction)을 지원하지 않으며,
Duration Policy가 Duration이나 Infinite여선 안되고,
PreAttributeChange의 영향을 받지 않기 때문에, 해당 함수에서 처리해야 하는 것이 있다면 UGameplayEffectExecutionCalculation에서 동일하게 한번 더 처리해줘야 한다.
또한, Net Execution Policy가 Local Predicted, Server Initiated, Server Only인 GameplayAbility에서 오직 서버 측에서만 작동한다.
캡쳐할 Source Attribute에 대해서 스냅샷(Snapshot)을 사용할 수 있는데,
Snapshot을 사용하는 경우는 GameplayEffectSpec이 생성되는 시점의 Attribute를,
사용하지 않는 경우는 GameplayEffectSpec이 적용되는 시점의 Attribute를 적용한다.
- Source의 Attribute에 대해서만 고려할 사항이다. 왜냐하면, 이 Effect Spec이 어떤 대상에게 적용될 지 알 수 없는 시점이기 때문이다.
(예를 들어, 파이어볼이 생성되는 시점에는 이것이 누구에게 적중할 지 알 수 없으므로)

UGameplayEffectExecutionCalculation는 Attribute뿐만 아니라 Set By Caller Magnitude도 캡쳐 가능하다.
GameplayEffect 자체에서는 Attibute 기반의 Calculation Modifier를 추가할 수 있다.
Modify Policy (Add, Override, Subtract, Multiply) 도 설정할 수 있으며, GameplayEffectSpec의 적용 시점에 Source와 Target의 Attribute를 참조할 수 있다.
여러 Attribute를 참조해야 하는 복잡한 계산에 적절하다. (데미지 계산 등)
UGameplayEffectExecutionCalculation 서브 클래스 작성
#pragma once
#include "CoreMinimal.h"
#include "GameplayEffectExecutionCalculation.h"
#include "ExecCalc_Damage.generated.h"
UCLASS()
class AURA_API UExecCalc_Damage : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
UExecCalc_Damage();
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
};헤더 파일에서 생성자와 Execute_Implementation() 함수를 선언한다.
UGameplayEffectExecutionCalculation 클래스에 Execute() 함수가 BlueprintNative 지정자로 선언되어 있기 때문에, 그에 맞춰서 Implementation 함수를 동일한 매개변수로 선언해주면 된다.
#include "AbilitySystem/ExecCalc/ExecCalc_Damage.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystem/AuraAttributeSet.h"
struct AuraDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
AuraDamageStatics()
{
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, Armor, Target, false);
}
};
static const AuraDamageStatics& DamageStatics()
{
static AuraDamageStatics DamageStatics;
return DamageStatics;
}
UExecCalc_Damage::UExecCalc_Damage()
{
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
}
void UExecCalc_Damage::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
const UAbilitySystemComponent* SourceASC = ExecutionParams.GetSourceAbilitySystemComponent();
const UAbilitySystemComponent* TargetASC = ExecutionParams.GetTargetAbilitySystemComponent();
const AActor* SourceAvatar = SourceASC ? SourceASC->GetAvatarActor() : nullptr;
const AActor* TargetAvatar = TargetASC ? TargetASC->GetAvatarActor() : nullptr;
const FGameplayEffectSpec& EffectSpec = ExecutionParams.GetOwningSpec();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = EffectSpec.CapturedSourceTags.GetAggregatedTags();
EvaluationParameters.TargetTags = EffectSpec.CapturedTargetTags.GetAggregatedTags();
float Armor = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorDef, EvaluationParameters, Armor);
// PreAttributeChange()
Armor = FMath::Max<float>(0.f, Armor);
// FGameplayModifierEvaluatedData는 변화한 Attribute를 반영하기 위한 구조체로 보인다.
// Attribute를 선언하고, 계산 방식과 값을 넘겨준다.
// FGameplayModifierEvaluatedData는 필요한 만큼 생성하고, 이를 모두 OutExecutionOutput에 추가할 수 있다
FGameplayModifierEvaluatedData EvaluatedData(DamageStatics().ArmorProperty, EGameplayModOp::Additive, Armor);
OutExecutionOutput.AddOutputModifier(EvaluatedData);
}
Execute_Implementation()의 매개변수인 FGameplayEffectCustomExecutionParameters는 여러 유용한 함수를 제공하는 구조체이기 때문에, 해당 구조체를 적극적으로 활용하면 편리하게 Execute_Implementation() 함수의 내용을 구현할 수 있다.
UGameplayEffectExecutionCalculation을 이용하는 에픽게임즈의 액션RPG 데모와 같은 여러 프로젝트에서 많이 사용하는 방식으로, 구조체를 정의하고, 이를 반환하는 정적 함수 내부에서 정적 구조체 변수를 선언하여 활용하는 방식이 있다.
// -------------------------------------------------------------------------
// Helper macros for declaring attribute captures
// -------------------------------------------------------------------------
#define DECLARE_ATTRIBUTE_CAPTUREDEF(P) \
FProperty* P##Property; \
FGameplayEffectAttributeCaptureDefinition P##Def; \
// 위 매크로는 넘겨받은 P에 대해서 FProperty*와 FGameplayEffectAttributeCaptureDefinition을 생성해준다.
// 따라서 해당 매크로를 사용하면 직접 FGameplayEffectAttributeCaptureDefinition 변수를 선언할 필요가 없다.
#define DEFINE_ATTRIBUTE_CAPTUREDEF(S, P, T, B) \
{ \
P##Property = FindFieldChecked<FProperty>(S::StaticClass(), GET_MEMBER_NAME_CHECKED(S, P)); \
P##Def = FGameplayEffectAttributeCaptureDefinition(P##Property, EGameplayEffectAttributeCaptureSource::T, B); \
}
// 위 매크로는 특정 클래스에 대해 멤버 변수를 확인하여, FGameplayEffectAttributeCaptureDefinition를 초기화한다.
// 위 2가지 매크로를 사용하는 절차는 MMC에서 아래의 코드를 헤더 및 생성자에서 작성한 것과 같은 효과이다.
// 1. 헤더에서 변수 선언
FGameplayEffectAttributeCaptureDefinition VigorDefinition;
// 2. 생성자에서 초기화
VigorDefinition.AttributeToCapture = UAuraAttributeSet::GetVigorAttribute();
VigorDefinition.AttributeSource = EGameplayEffectAttributeCaptureSource::Target;
VigorDefinition.bSnapshot = false;AttemptCalculateCapturedAttributeMagnitude()를 통해 필요한 Attribute를 로컬 변수에 복사하고, 필요한 계산을 실행한 다음, FGameplayModifierEvaluatedData 구조체 변수를 생성하여 계산된 값을 담고, 이를 OutExecutionOutput.AddOutputModifier()로 추가하면 된다.
작성한 UGameplayEffectExecutionCalculation 클래스는 GameplayEffect 블루프린트에서 Executions 배열에 추가하여 사용할 수 있다.