Execution Calculation
복잡한 데미지 공식과 속성 계산 구현
Execution Calculation 개요
GameplayEffect의 고급 계산 시스템
UGameplayEffectExecutionCalculation은 GameplayEffect 내에서 복잡한 커스텀 계산을 수행하는 클래스입니다. 소스와 타겟의 여러 속성을 캡처하여 데미지 공식, 크리티컬 계산 등을 구현합니다.
Modifier vs Execution
Modifier: 단순한 속성 수정
Execution: 여러 속성 기반 복잡한 계산
속성 캡처
소스/타겟의 AttackPower, Defense 등을 스냅샷하여 계산에 사용
Damage Execution 구현
데미지 계산 클래스 작성
#pragma once
#include "CoreMinimal.h"
#include "GameplayEffectExecutionCalculation.h"
#include "DamageExecution.generated.h"
/**
* 데미지 계산 Execution
* 소스의 공격력과 타겟의 방어력을 기반으로 최종 데미지 계산
*/
UCLASS()
class MYGAME_API UDamageExecution : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
UDamageExecution();
virtual void Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
};
#include "DamageExecution.h"
#include "MyAttributeSet.h"
#include "AbilitySystemComponent.h"
// 속성 캡처 정의 구조체
struct FDamageStatics
{
// 소스에서 캡처할 속성
DECLARE_ATTRIBUTE_CAPTUREDEF(AttackPower);
// 타겟에서 캡처할 속성
DECLARE_ATTRIBUTE_CAPTUREDEF(DefensePower);
DECLARE_ATTRIBUTE_CAPTUREDEF(IncomingDamage);
FDamageStatics()
{
// 소스의 AttackPower 캡처 (스냅샷)
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, AttackPower, Source, true);
// 타겟의 DefensePower 캡처 (스냅샷)
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, DefensePower, Target, true);
// 타겟의 IncomingDamage (출력용)
DEFINE_ATTRIBUTE_CAPTUREDEF(UMyAttributeSet, IncomingDamage, Target, false);
}
};
static const FDamageStatics& DamageStatics()
{
static FDamageStatics Statics;
return Statics;
}
UDamageExecution::UDamageExecution()
{
// 캡처할 속성 등록
RelevantAttributesToCapture.Add(DamageStatics().AttackPowerDef);
RelevantAttributesToCapture.Add(DamageStatics().DefensePowerDef);
}
void UDamageExecution::Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
// ASC 가져오기
UAbilitySystemComponent* TargetASC = ExecutionParams.GetTargetAbilitySystemComponent();
UAbilitySystemComponent* SourceASC = ExecutionParams.GetSourceAbilitySystemComponent();
AActor* SourceActor = SourceASC ? SourceASC->GetAvatarActor() : nullptr;
AActor* TargetActor = TargetASC ? TargetASC->GetAvatarActor() : nullptr;
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
// 태그 컨테이너 (조건부 계산용)
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
// 속성 값 평가 파라미터
FAggregatorEvaluateParameters EvaluateParameters;
EvaluateParameters.SourceTags = SourceTags;
EvaluateParameters.TargetTags = TargetTags;
// ========== 속성 값 가져오기 ==========
// 소스 공격력
float AttackPower = 0.0f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
DamageStatics().AttackPowerDef, EvaluateParameters, AttackPower);
// 타겟 방어력
float DefensePower = 0.0f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
DamageStatics().DefensePowerDef, EvaluateParameters, DefensePower);
// SetByCaller 기본 데미지
float BaseDamage = 0.0f;
BaseDamage = Spec.GetSetByCallerMagnitude(
FGameplayTag::RequestGameplayTag(FName("Data.Damage")),
false, // WarnIfNotFound
0.0f // DefaultValue
);
// ========== 데미지 계산 ==========
// 공격력 스케일링
float ScaledDamage = BaseDamage + (AttackPower * 0.5f);
// 방어력 감소 공식: Damage * (100 / (100 + Defense))
float DamageReduction = 100.0f / (100.0f + DefensePower);
float DamageAfterDefense = ScaledDamage * DamageReduction;
// 크리티컬 계산 (20% 확률, 2배 데미지)
float CriticalChance = 0.2f;
float CriticalMultiplier = 2.0f;
if (FMath::FRand() < CriticalChance)
{
DamageAfterDefense *= CriticalMultiplier;
// 크리티컬 태그 추가 (GameplayCue용)
// OutExecutionOutput에 태그 추가 가능
}
// 타겟 태그에 따른 보정
if (TargetTags && TargetTags->HasTag(
FGameplayTag::RequestGameplayTag(FName("State.Invulnerable"))))
{
DamageAfterDefense = 0.0f; // 무적 상태
}
// ========== 결과 출력 ==========
float FinalDamage = FMath::Max(0.0f, DamageAfterDefense);
if (FinalDamage > 0.0f)
{
// IncomingDamage 속성에 데미지 적용
OutExecutionOutput.AddOutputModifier(
FGameplayModifierEvaluatedData(
DamageStatics().IncomingDamageProperty,
EGameplayModOp::Additive,
FinalDamage
)
);
}
}
GameplayEffect에 적용
Execution을 GameplayEffect에 연결
// GE_Damage (Blueprint GameplayEffect)
// Duration
Duration Policy: Instant
// Executions
[0] Calculation Class: DamageExecution
Conditional Gameplay Effects: (선택사항 - 조건부 추가 이펙트)
// 필요시 추가 Modifier
// 이 경우 Execution이 먼저 실행되고, 이후 Modifier 적용
// Gameplay Cues
Gameplay Cue Tags:
- GameplayCue.Combat.Damage // 히트 이펙트 트리거
GameplayEffect가 적용될 때 Execution이 먼저 실행되고, 그 결과로 수정된 속성에 대해 Modifier가 이후 적용됩니다.
조건부 이펙트
계산 결과에 따른 추가 효과
// Execution에서 조건부 이펙트 적용
void UDamageExecution::Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
// ... 데미지 계산 ...
// 크리티컬 히트 시 추가 이펙트
if (bIsCritical)
{
// Conditional Gameplay Effects가 설정되어 있으면 자동 적용
// 또는 직접 태그로 처리
// 방법 1: ConditionalGameplayEffects 사용 (Blueprint에서 설정)
// Execution의 Conditional Effects에 조건과 이펙트 지정
// 방법 2: 직접 이펙트 적용 (C++)
if (TargetASC && CriticalBonusEffectClass)
{
FGameplayEffectSpecHandle BonusSpec =
TargetASC->MakeOutgoingSpec(
CriticalBonusEffectClass, 1, Context);
if (BonusSpec.IsValid())
{
TargetASC->ApplyGameplayEffectSpecToSelf(*BonusSpec.Data.Get());
}
}
}
// 킬 시 추가 처리
if (TargetASC)
{
UMyAttributeSet* TargetAS = Cast<UMyAttributeSet>(
TargetASC->GetAttributeSet(UMyAttributeSet::StaticClass()));
if (TargetAS && (TargetAS->GetHealth() - FinalDamage) <= 0.0f)
{
// 킬 이벤트 발생
FGameplayEventData EventData;
EventData.Instigator = SourceActor;
EventData.Target = TargetActor;
SourceASC->HandleGameplayEvent(
FGameplayTag::RequestGameplayTag(FName("Event.Combat.Kill")),
&EventData
);
}
}
}
Modifier Magnitude Calculation
단일 속성 커스텀 계산
단일 Modifier의 값만 커스텀 계산이 필요할 때는 UGameplayModMagnitudeCalculation을 사용합니다.
// HealCalculation.h
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "HealCalculation.generated.h"
UCLASS()
class MYGAME_API UHealCalculation : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UHealCalculation();
virtual float CalculateBaseMagnitude_Implementation(
const FGameplayEffectSpec& Spec) const override;
};
// HealCalculation.cpp
#include "HealCalculation.h"
#include "MyAttributeSet.h"
UHealCalculation::UHealCalculation()
{
// 캡처할 속성 정의
FGameplayEffectAttributeCaptureDefinition MaxHealthDef;
MaxHealthDef.AttributeToCapture = UMyAttributeSet::GetMaxHealthAttribute();
MaxHealthDef.AttributeSource = EGameplayEffectAttributeCaptureSource::Target;
MaxHealthDef.bSnapshot = false;
RelevantAttributesToCapture.Add(MaxHealthDef);
}
float UHealCalculation::CalculateBaseMagnitude_Implementation(
const FGameplayEffectSpec& Spec) const
{
// 타겟의 MaxHealth 가져오기
float MaxHealth = 0.0f;
FAggregatorEvaluateParameters EvaluateParams;
GetCapturedAttributeMagnitude(
RelevantAttributesToCapture[0],
Spec,
EvaluateParams,
MaxHealth
);
// MaxHealth의 20% 회복
return MaxHealth * 0.2f;
}
핵심 요약
- ExecutionCalculation: 여러 속성 기반 복잡한 계산 (데미지 공식)
- ModMagnitudeCalculation: 단일 Modifier의 커스텀 계산
- DECLARE/DEFINE_ATTRIBUTE_CAPTUREDEF로 속성 캡처 정의
- AddOutputModifier로 계산 결과를 속성에 적용
- 소스/타겟의 태그를 활용한 조건부 계산 가능
도전 과제
배운 내용을 직접 실습해보세요
UGameplayEffectExecutionCalculation을 상속하여 RPG 데미지 공식(Damage = AttackPower * SkillMultiplier - Defense * 0.5)을 구현하세요. Execute_Implementation()에서 Source/Target의 Attribute를 캡처하여 계산하세요.
ExecutionCalculation에서 CritRate와 CritDamage Attribute를 캡처하고, FMath::FRand()로 크리티컬 확률을 판정하세요. 크리티컬 히트 시 GameplayCue를 발동하는 로직도 추가하세요.
화속성 공격이 얼음 속성 몬스터에게 2배 데미지, 같은 속성에는 0.5배 데미지를 주는 상성 시스템을 ExecutionCalculation에 구현하세요. GameplayTag로 속성을 판별하고, DataTable에서 상성 배율을 조회하세요.