Gameplay Attribute의 값을 변화시킬 수 있는 Gameplay Effect에는 여러 가지 Modifier Magnitude를 설정할 수 있다.
기존에 만들어진 Curve Table을 이용할 수 있는 Scalable float 옵션, 다른 어트리뷰트에 연산을 더한 값을 사용할 수 있는 Attribute Based 옵션, 호출하는 쪽에서 값을 정할 수 있는 Set by caller 옵션 등이 있다. 그리고 이번 글에서 다룰 Custom Calculation Class를 사용하면 프로그래머가 여러 어트리뷰트를 사용해서 임의로 계산식을 만들 수 있는 강력한 기능을 사용할 수 있다.
굉장히 긴 이름의 이 클래스를 상속하는 하위 클래스를 만듦으로써 계산식을 커스텀할 수 있다.
이 클래스의 맨 아래를 보면 다음과 같은 매크로식을 발견할 수 있다.
#define DECLARE_ATTRIBUTE_CAPTUREDEF(P) \
FProperty* P##Property; \
FGameplayEffectAttributeCaptureDefinition P##Def; \
#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); \
}
P
라는 파라미터를 받아서 P##Property
, P##Def
이런 식으로 변수명에 사용하고 있는 것을 볼 수 있는데, # 기호 두 개를 이어 붙인 ##
형태를 사용하면 마치 문자열을 이어 붙이듯이 파라미터를 이름에 사용할 수 있다.
AttributeSet에서 GAMEPLAYATTRIBUTE_VALUE_GETTER
매크로에서 사용해 본 적이 있다.
#define GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
FORCEINLINE float Get##PropertyName() const \
{ \
return PropertyName.GetCurrentValue(); \
}
예를 들어 PropertyName
에 Health
를 입력해서 선언하면 Get##PropertyName()
에 의해 GetHealth()
라는 getter 함수를 쉽게 생성할 수 있다.
이렇게 생성된 프로퍼티를 관리/접근할 수 있도록 static 변수를 만들어 주고, 계산에 사용될 어트리뷰트를 저장하는 TArray<FGameplayEffectAttributeCaptureDefinition>
인 RelevantAttributesToCapture
에 Definition을 추가한다.
static const AuraDamageStatics& DamageStatics()
{
static AuraDamageStatics DStatics;
return DStatics;
}
Gameplay Attribute가 실행되면 UGameplayEffectExecutionCalculation::Execute()
함수가 실행된다.
이 함수는 두 개의 파라미터를 가지며 하나는 FGameplayEffectCustomExecutionParameters
파라미터이고, 하나는 FGameplayEffectCustomExecutionOutput
이다. 함수의 선언부로 이동해 주석을 확인해 보면 ExecutionParams
는 Custom execution calculation에 필요한 여러 파라미터들을 담고 있는 구조체임을 알 수 있다. 그리고 OutExecutionOutput
은 실행의 결과를 담은 구조체임을 알 수 있다.
/**
* Called whenever the owning gameplay effect is executed. Allowed to do essentially whatever is desired, including generating new
* modifiers to instantly execute as well.
*
* @note: Native subclasses should override the auto-generated Execute_Implementation function and NOT this one.
*
* @param ExecutionParams Parameters for the custom execution calculation
* @param OutExecutionOutput [OUT] Output data populated by the execution detailing further behavior or results of the execution
*/
UFUNCTION(BlueprintNativeEvent, Category="Calculation")
void Execute(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const;
Execution Parameter 구조체에는 여러 멤버 변수들이 들어 있고,
output 구조체에는 결과를 나타내는 여러 멤버들이 들어 있다.
FAggregatorEvaluateParameters EvalParam;
EvalParam.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
EvalParam.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
float Armor = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorDef, EvalParam, Armor);
Armor = FMath::Max<float>(0.f, Armor);
FGameplayModifierEvaluatedData EvaluatedData(DamageStatics().ArmorProperty, EGameplayModOp::Additive, Armor);
OutExecutionOutput.AddOutputModifier(EvaluatedData);
AttemptCalculateCapturedAttributeMagnitude()
를 통해 Armor Definition의 값을 로컬 변수로 가져오고, 이를 토대로 EvaluatedData
를 만들어 Output
에 추가할 수 있다.
위 사진과 같이 Execution
에 해당 클래스를 Calculation Class 자리에 추가해 주면, 이 GE가 실행될 때 해당 클래스에서 오버라이드한 Execute()
함수가 실행된다.