PART 4 - 강의 7/8

Execution Calculation

복잡한 데미지 공식과 속성 계산 구현

01

Execution Calculation 개요

GameplayEffect의 고급 계산 시스템

UGameplayEffectExecutionCalculation은 GameplayEffect 내에서 복잡한 커스텀 계산을 수행하는 클래스입니다. 소스와 타겟의 여러 속성을 캡처하여 데미지 공식, 크리티컬 계산 등을 구현합니다.

Modifier vs Execution

Modifier: 단순한 속성 수정
Execution: 여러 속성 기반 복잡한 계산

속성 캡처

소스/타겟의 AttackPower, Defense 등을 스냅샷하여 계산에 사용

02

Damage Execution 구현

데미지 계산 클래스 작성

DamageExecution.h #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; };
DamageExecution.cpp #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 ) ); } }
03

GameplayEffect에 적용

Execution을 GameplayEffect에 연결

Blueprint 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 // 히트 이펙트 트리거
Execution 실행 순서

GameplayEffect가 적용될 때 Execution이 먼저 실행되고, 그 결과로 수정된 속성에 대해 Modifier가 이후 적용됩니다.

04

조건부 이펙트

계산 결과에 따른 추가 효과

C++ // 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 ); } } }
05

Modifier Magnitude Calculation

단일 속성 커스텀 계산

단일 Modifier의 값만 커스텀 계산이 필요할 때는 UGameplayModMagnitudeCalculation을 사용합니다.

HealCalculation.h/cpp // 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; }
SUMMARY

핵심 요약

  • ExecutionCalculation: 여러 속성 기반 복잡한 계산 (데미지 공식)
  • ModMagnitudeCalculation: 단일 Modifier의 커스텀 계산
  • DECLARE/DEFINE_ATTRIBUTE_CAPTUREDEF로 속성 캡처 정의
  • AddOutputModifier로 계산 결과를 속성에 적용
  • 소스/타겟의 태그를 활용한 조건부 계산 가능
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 기본 데미지 Execution Calculation

UGameplayEffectExecutionCalculation을 상속하여 RPG 데미지 공식(Damage = AttackPower * SkillMultiplier - Defense * 0.5)을 구현하세요. Execute_Implementation()에서 Source/Target의 Attribute를 캡처하여 계산하세요.

실습 2: 크리티컬 히트 시스템 구현

ExecutionCalculation에서 CritRate와 CritDamage Attribute를 캡처하고, FMath::FRand()로 크리티컬 확률을 판정하세요. 크리티컬 히트 시 GameplayCue를 발동하는 로직도 추가하세요.

심화 과제: 속성 상성 데미지 계산

화속성 공격이 얼음 속성 몬스터에게 2배 데미지, 같은 속성에는 0.5배 데미지를 주는 상성 시스템을 ExecutionCalculation에 구현하세요. GameplayTag로 속성을 판별하고, DataTable에서 상성 배율을 조회하세요.