Unreal GAS (32) - Execution Calculation

wnsduf0000·2025년 12월 1일

Unreal_GAS

목록 보기
33/34
  • 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 서브 클래스 작성

      • 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 데모와 같은 여러 프로젝트에서 많이 사용하는 방식으로, 구조체를 정의하고, 이를 반환하는 정적 함수 내부에서 정적 구조체 변수를 선언하여 활용하는 방식이 있다.

          • Attribute의 캡쳐에는 이전에 MMC(UGameplayModMagnitudeCalculation) 에서도 사용한 FGameplayEffectAttributeCaptureDefinition 구조체 변수를 활용하지만, 활용하는 방식이 약간 다르다.
            • UGameplayEffectExecutionCalculation 클래스 내에는 FGameplayEffectAttributeCaptureDefinition에 대한 매크로가 선언되어 있다.
              // -------------------------------------------------------------------------
              //	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 배열에 추가하여 사용할 수 있다.

profile
저는 게임 개발자로 일하고 싶어요

0개의 댓글