https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gameplay-effects-for-the-gameplay-ability-system-in-unreal-engine?application_version=5.3

 

언리얼 엔진의 게임플레이 어빌리티 시스템용 게임플레이 어트리뷰트 및 게임플레이 이펙트 |

게임플레이 어빌리티 시스템 내 어트리뷰트 및 이펙트의 개요입니다.

dev.epicgames.com

 

 

https://github.com/tranek/GASDocumentation?tab=readme-ov-file#concepts-ge

 

GitHub - tranek/GASDocumentation: My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer s

My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer sample project. - tranek/GASDocumentation

github.com

 

  • GAS에서 Attribute를 변경하는 방식
  • UGameEffect를 상속받아 구현
  • BlueprintNative로 작업하지 않는 대신, 변수를 통해 환경설정을 한다.
  • GameplayEffects(이하 GE)는 Instance를 생성하지 않는다.
    • Ability나 ASC가 GE를 등록하기 원하면, GE는 ClassDefaultObjects로부터 GameplayEffectSpec를 생성한다.
    • 성공적으로 생성된 GameplayEffectSpec은 FActiveGameplayEffect에 추가되어
      ASC의 ActiveGameplayEffects에 저장된다.

Apply

  • GE는 GameplayAbilities와 ASC에서 제공하는 수 많은 ApplyGameplayEffectTo가 포함된 함수로 등록한다.
  • 또한 Duration, Infinite GE가 ASC에 등록될 때 Delegate를 받을 수 있다.
    • 더보기
      /** Delegate for when an effect is applied */
      DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnGameplayEffectAppliedDelegate, UAbilitySystemComponent*, const FGameplayEffectSpec&, FActiveGameplayEffectHandle);
      
      /** Called on server whenever a GE is applied to self. This includes instant and duration based GEs. */
      FOnGameplayEffectAppliedDelegate OnGameplayEffectAppliedDelegateToSelf;
      
      /** Called on server whenever a GE is applied to someone else. This includes instant and duration based GEs. */
      FOnGameplayEffectAppliedDelegate OnGameplayEffectAppliedDelegateToTarget;
      
      /** Called on both client and server whenever a duration based GE is added (E.g., instant GEs do not trigger this). */
      FOnGameplayEffectAppliedDelegate OnActiveGameplayEffectAddedDelegateToSelf;
      
      /** Called on server whenever a periodic GE executes on self */
      FOnGameplayEffectAppliedDelegate OnPeriodicGameplayEffectExecuteDelegateOnSelf;
      
      /** Called on server whenever a periodic GE executes on target */
      FOnGameplayEffectAppliedDelegate OnPeriodicGameplayEffectExecuteDelegateOnTarget;
      
      
      /** This ASC has successfully applied a GE to something (potentially itself) */
      void OnGameplayEffectAppliedToTarget(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle);
      void OnGameplayEffectAppliedToSelf(UAbilitySystemComponent* Source, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle);
      void OnPeriodicGameplayEffectExecuteOnTarget(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecExecuted, FActiveGameplayEffectHandle ActiveHandle);
      void OnPeriodicGameplayEffectExecuteOnSelf(UAbilitySystemComponent* Source, const FGameplayEffectSpec& SpecExecuted, FActiveGameplayEffectHandle ActiveHandle);
  • 위 함수들은 LocalRole에 따라 호출 여부가 다르다.
    • Authority: 언제나 항상 호출
    • Autonomous: 대상 GE의 Replication Mode가 Full이거나 Mixed인 경우에만 호출 됨
    • Simulated: 대상 GE의 Replication Mode가 Full일 때에만 호출 됨

 

Remove

  • GE는 GameplayAbilities와 ASC에서 RemoveActiveGameplayEffect가 포함된 함수들에 의해 제거된다.
  • 또한 Duration, Infinite Ge는 제거될 때 Delegate를 받을 수 있다.
    • 더보기
      /** Contains all of the gameplay effects that are currently active on this component */
      UPROPERTY(Replicated)
      FActiveGameplayEffectsContainer ActiveGameplayEffects;
          
          
      DECLARE_MULTICAST_DELEGATE_OneParam(FOnGivenActiveGameplayEffectRemoved, const FActiveGameplayEffect&);
      DECLARE_MULTICAST_DELEGATE_OneParam(FOnActiveGameplayEffectRemoved_Info, const FGameplayEffectRemovalInfo&);
      
      
      /** Called when any gameplay effects are removed */
      FOnGivenActiveGameplayEffectRemoved& OnAnyGameplayEffectRemovedDelegate();
      /** Returns delegate structure that allows binding to several gameplay effect changes */
      FOnActiveGameplayEffectRemoved_Info* OnGameplayEffectRemoved_InfoDelegate(FActiveGameplayEffectHandle Handle);
  • 위 함수들은 LocalRole에 따라 호출 여부가 다르다.
    • Authority: 언제나 항상 호출
    • Autonomous: 대상 GE의 Replication Mode가 Full이거나 Mixed인 경우에만 호출 됨
    • Simulated: 대상 GE의 Replication Mode가 Full일 때에만 호출 됨

 

Duration

더보기
/** Gameplay effect duration policies */
UENUM()
enum class EGameplayEffectDurationType : uint8
{
	/** This effect applies instantly */
	Instant,
	/** This effect lasts forever */
	Infinite,
	/** The duration of this effect will be specified by a magnitude */
	HasDuration
};



Instant

  • Attribute의 BaseValue를 즉시 영구적으로 바꾼다.
  • GameplayTags 사용이 불가하다.
  • GameplayCue, GameplayTags에서 Execute를 호출

Infinite

  • Attribute의 CurrentValue를 일시적으로 바꾼다.
  • GE가 삭제될 때 GameplayTags도 삭제된다.
  • ASC에서 스스로 만료되지 않기 때문에 반드시 명시적으로 삭제되어야 한다.
  • GameplayCue에서 Add, Remove를 호출. 
    • GE가 발생하는 시점과 효과가 발동하는 시점을 다르게 등록할 수 있음
    • Audio, Visual Effect 타이밍 구성에도 유용함

HasDuration

  • Attribute의 CurrentValue를 일시적으로 바꾼다.
  • GE가 만료되거나 삭제될 때 GameplayTags도 삭제된다.
  • Duration은 UGameplayEffect BP로 지정된다.
  • GameplayCue에서 Add, Remove를 호출.
    • GE가 발생하는 시점과 효과가 발동하는 시점을 다르게 등록할 수 있음
    • Audio, Visual Effect 타이밍 구성에도 유용함

Period

더보기
/** Period in seconds. 0.0 for non-periodic effects */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Duration|Period", meta=(EditCondition="DurationPolicy != EGameplayEffectDurationType::Instant", EditConditionHides))
FScalableFloat	Period;
  • Duration 타입이 HasDuration, Infinite인 경우에만 적용
  • GE의 Modifiers나 Executions를 등록된 수치의 seconds만큼 대기 했다가 실행
  • 이 때 Delay를 갖고 발생하는 Effects들은 다음 조건을 만족하는 경우 Instant GE로 취급한다.
    • Attribute의 BaseValue를 변경하는 경우
    • GameplayCue를 실행하는 경우
  • 도트딜을 작업할 때 유용함

Modifiers

더보기
/**
 * FGameplayModifierInfo
 *	Tells us "Who/What we" modify
 *	Does not tell us how exactly
 */
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FGameplayModifierInfo
{
	GENERATED_USTRUCT_BODY()
	
	/** The Attribute we modify or the GE we modify modifies. */
	UPROPERTY(EditDefaultsOnly, Category=GameplayModifier, meta=(FilterMetaTag="HideFromModifiers"))
	FGameplayAttribute Attribute;

	/** The numeric operation of this modifier: Override, Add, Multiply, etc  */
	UPROPERTY(EditDefaultsOnly, Category=GameplayModifier)
	TEnumAsByte<EGameplayModOp::Type> ModifierOp = EGameplayModOp::Additive;

	/** Magnitude of the modifier */
	UPROPERTY(EditDefaultsOnly, Category=GameplayModifier)
	FGameplayEffectModifierMagnitude ModifierMagnitude;

	/** Evaluation channel settings of the modifier */
	UPROPERTY(EditDefaultsOnly, Category=GameplayModifier)
	FGameplayModEvaluationChannelSettings EvaluationChannelSettings;

	UPROPERTY(EditDefaultsOnly, Category=GameplayModifier)
	FGameplayTagRequirements	SourceTags;

	UPROPERTY(EditDefaultsOnly, Category=GameplayModifier)
	FGameplayTagRequirements	TargetTags;

	/** Equality/Inequality operators */
	bool operator==(const FGameplayModifierInfo& Other) const;
	bool operator!=(const FGameplayModifierInfo& Other) const;
};


	/** Array of modifiers that will affect the target of this effect */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=GameplayEffect, meta=(TitleProperty=Attribute))
	TArray<FGameplayModifierInfo> Modifiers;
  • GameplayEffect가 Attribute와 상호작용 하는 방식을 결정
    • Attribute 자체에 대한 수학적 Interaction뿐 아니라, GameplayTag 정보도 포함된다.
  • 각 Modifier는 특정 연산으로 하나의 Attribute만 수정할 책임이 있다.
    • Add
      • Modifier의 특정 Attribute에 결과 값을 더한다.
      • 음수를 사용하여 뺄셈을 구현한다.
    • Multiply
      • Modifier의 특정 Attribute에 결과 값을 곱한다.
    • Divide
      • Modifier의 특정 Attribute에 결과 값을 나눈다.
    • Override
      • Modifier의 특정 Attribute에 결과 값을 덮어씌운다.
  • Attribute의 CurrentValue는 BaseValue에 가해지는 모든 Modifier의 종합 결과이다.
  • Modifier가 어떻게 집계될지에 대한 공식은 다음 함수에 표현되어 있다.
    • 더보기
      float FAggregatorModChannel::EvaluateWithBase(float InlineBaseValue, const FAggregatorEvaluateParameters& Parameters) const
      {
      	for (const FAggregatorMod& Mod : Mods[EGameplayModOp::Override])
      	{
      		if (Mod.Qualifies())
      		{
      			return Mod.EvaluatedMagnitude;
      		}
      	}
      
      	float Additive = SumMods(Mods[EGameplayModOp::Additive], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Additive), Parameters);
      	float Multiplicitive = SumMods(Mods[EGameplayModOp::Multiplicitive], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Multiplicitive), Parameters);
      	float Division = SumMods(Mods[EGameplayModOp::Division], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Division), Parameters);
      
      	if (FMath::IsNearlyZero(Division))
      	{
      		ABILITY_LOG(Warning, TEXT("Division summation was 0.0f in FAggregatorModChannel."));
      		Division = 1.f;
      	}
      
      	return ((InlineBaseValue + Additive) * Multiplicitive) / Division;
      }
    • 위 연산을 마치면 가장 마지막에 Override Modifier가 동작한다.

Modifier Type

Scalable Float

  • 변수를 Row로 갖고 Level을 Column으로 갖는 Data Table을 가르킬 수 있는 구조체
  • Ability의 현재 Level이나 GameplayEffectSpec에서 override 된 다른 Level을 가진 Row의 Value를 자동으로 읽는다.
    • 이 값은 계수에 의해 변동될 수 있다.
  • 만약 특정 DataTable이나 Row가 없다면 값이 1이 되어 계수 값을 가진 하나의 변수로 사용 가능하다.

Attribute Based

  • Source(GameplayEffectSpec을 만든 주체)나 Target(GameplayEffecSpec을 받을 주체)의 Attribute의 Current/Base Value를 기반으로 사용
  • 위 값을 기반으로 계수나 계수 전후의 추가값을 사용하여 수정
  • Snapshooting이 세팅되면 GameplayEffectSpec이 생성될 때 Attribute이 캡쳐 된다.
    • Snapshooting이 설정되지 않으면 GameplayEffeectSpec이 등록 될 때 Attribute가 캡쳐된다.

Custom Calculation Class

  • 복잡한 Modifier에 대해 가장 유연함을 자랑한다.
  • ModifierMagnitudeCalculation class를 사용하고 결과에 계수, 계수 전후 추가값으로 추가 보정이 가능하다.

Set By Caller

  • Runtime 상에서 Ability나 GameplayEffectSpec에서 GameplayEffectSpec 만들면서 GE 외에서 값에 영향을 미치는 경우에 사용
    • 예를 들어 버튼을 Hold한 만큼 데미지가 증가하는 경우 이 타입을 사용한다.
  • 이 타입은 사실 GameplayEffectSpec에 있는 TMap<FGameplayTag, float>
    • Modifier는 그저 제공된 GameplayTag와 관련된 SetByCaller 값을 읽고 연산할 뿐이다.
    • 때문에 Modifier에서 사용하는 SetByCallers는 GameplayTag 버전만 사용 가능하고, FName 버전은 불가하다.
  • Modifier가 SetByCaller 타입으로 선언 되었으나 GameplayEffectSpec에 적절한 GameTag가 없는 경우,
    게임은 Runtime Error를 생성하며 0을 반환한다.
    • 이 케이스는 주로 Divide 연산에서 자주 발생한다.

Multiply/Divide Modifiers

  • 기본적으로 Multiply/Divide Modifier는 Attribute의 BaseValue에 연산이 적용되기 전에 함께 추가된다.
    • 더보기
      /**
       * Helper function to sum all of the mods in the specified array, using the specified modifier bias and evaluation parameters
       * 
       * @param InMods		Mods to sum
       * @param Bias			Bias to apply to modifier magnitudes
       * @param Parameters	Evaluation parameters
       * 
       * @return Summed value of mods
       */
      float FAggregatorModChannel::SumMods(const TArray<FAggregatorMod>& InMods, float Bias, const FAggregatorEvaluateParameters& Parameters)
      {
      	float Sum = Bias;
      
      	for (const FAggregatorMod& Mod : InMods)
      	{
      		if (Mod.Qualifies())
      		{
      			Sum += (Mod.EvaluatedMagnitude - Bias);
      		}
      	}
      
      	return Sum;
      }
      
      /**
       * Evaluates the channel's mods with the specified base value and evaluation parameters
       * 
       * @param InlineBaseValue	Base value to use for the evaluation
       * @param Parameters		Additional evaluation parameters to use
       * 
       * @return Evaluated value based upon the channel's mods
       */
      float FAggregatorModChannel::EvaluateWithBase(float InlineBaseValue, const FAggregatorEvaluateParameters& Parameters) const
      {
      	for (const FAggregatorMod& Mod : Mods[EGameplayModOp::Override])
      	{
      		if (Mod.Qualifies())
      		{
      			return Mod.EvaluatedMagnitude;
      		}
      	}
      
      	float Additive = SumMods(Mods[EGameplayModOp::Additive], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Additive), Parameters);
      	float Multiplicitive = SumMods(Mods[EGameplayModOp::Multiplicitive], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Multiplicitive), Parameters);
      	float Division = SumMods(Mods[EGameplayModOp::Division], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Division), Parameters);
      
      	if (FMath::IsNearlyZero(Division))
      	{
      		ABILITY_LOG(Warning, TEXT("Division summation was 0.0f in FAggregatorModChannel."));
      		Division = 1.f;
      	}
      
      	return ((InlineBaseValue + Additive) * Multiplicitive) / Division;
      }
  • Multiply/Divide Modifier는 둘 다 1의 Bias Value를 가지고 있다.
    • 1 + (Mod1.Magnitude - 1) + (Mod2.Magnitude - 1) + ...
  • 이러한 식은 예상치 못한 결과를 야기한다.
    • BaseValue에 Multiply/Divide가 수행되기 전에 모든 Modifier를 더해준다.
      • 예를 들어 1.5 Multiply Modifier가 2개가 있을 때 사람들의 예상 값은 1.5 * 1.5 = 2.25이다.
      • 하지만 실제 연산은 1 + 0.5 + 0.5 = 2가 된다.
    • 설계된대로 사용할 수 있는 값에 대한 기록되지 않는 규칙이 존재한다.
      • 첫번째 Modifier 값이 1보다 작고 그 외의 값들이 모두 1 이상 2 미만인 경우
      • 첫번째 Modifier 값이 2보다 큰 경우
    • 이를 해결하기 위해 FAggregatorModeChannel::EvaluateWithBase를 다음과 같이 수정하는 것을 권장한다.
      • 더보기
        float FAggregatorModChannel::MultiplyMods(const TArray<FAggregatorMod>& InMods, const FAggregatorEvaluateParameters& Parameters)
        {
        	float Multiplier = 1.0f;
        
        	for (const FAggregatorMod& Mod : InMods)
        	{
        		if (Mod.Qualifies())
        		{
        			Multiplier *= Mod.EvaluatedMagnitude;
        		}
        	}
        
        	return Multiplier;
        }

Gameplay Tags on Modifiers

  • Modifier에는 SourceTag와 TargetTag를 지정할 수 있다.
    • 이 둘은 GE의 Application Tag Requirements와 동일하게 동작한다.
    • 때문에 Tag는 Effect가 적용될 때에만 고려된다.
  • Attribute Based Modifier는 SourceTagFilter와 TargeTagFilter를 지정할 수 있다.
    • 이는 Modifier의 Source의 규모를 결정할 때,
      Filter에서 제공하는 Tag를 모두 가지고 있는  Modifier를 연산에서 배제한다.

Stacking

  • Default GE는 이전에 생성된 GameplayEffectSpec Instance를 고려하지 않고 새로운 Instance를 등록할 것이다.
    • GE는 새로운 Instance를 추가하는 대신, 기존 Instance의 Stack Count를 바꾸는 방식으로 쌓을 수 있다.
  • 이 기능은 Infinite, Duration GE에서만 동작한다.
  • Stacking은 또한 만료, Duration 갱신, Period 초기화 기능도 제공한다.

Aggregate By Source

  • Target의 Source ASC가 각각 1개의 Instance를 가지는 경우
  • 각 Source가 동일한 수의 Stack Count를 가질 수 있다.

Aggregate By Target

  • Source 무관하게 Target 당 1개의 Instance를 가진다.
  • Souce는 공유된 Stack Count를 관리한다.

Tags

  • GE는 여러 개의 GameplayTagContainers를 가질 수 있다.
    • Designer는 Added/Removed GameplayTagContainer를 수정할 수 있고,
      그 결과는 Compile을 거쳐 Combined GameplayTagContainer에 나타난다.
  • Added Tag는 이 부모 GE에 없는 것고 이 GE에서 추가된 Tag를 지칭한다.
  • Removed Tag는 부모 GE가 가지고 있지만 이 GE가 가지고 있지 않는 Tag를 지칭한다.

Gameplay Effect Asset Tags

  • GE가 가지고 있는 Tag
  • 아무런 기능적 목적이 없고, GE를 표현하기 위해서만 사용된다.

Granted Tags

  • GE에 있지만 GE가 ASC에 등록될 때 전달되기도 하는 Tag
    • GE가 제거될 때 ASC에서도 제거된다.
  • Duration, Infinite GE에서만 동작한다.

Ongoing Tag Requirements

  • 한번만 등록되며 GE가 활성화 되었는지 여부를 알 수 있다.
    • GE가 등록되어 있더라도 이 Tag를 비활성화 할 수 있다.
  • 만약 GE가 Ongoing Tag Requirements가 실패해 비활성화 되었지만 Requirements가 그 뒤에 도착하면,
    GE가 Tag를 다시 활성화 하고 Modifier를 재등록한다.
  • Duration, Infinite GE에서만 동작한다.

Application Tag Requirements

  • GE가 Target에 적용될 수 있는지를 확인하는 Tag
  • 이 Requirements가 맞지 않으면 GE가 적용되지 않는다.

Remove Gameplay Effects with Tags

  • Asset Tag나 Granted Tag에서 이 Tag에 등록된 Tag는 GE가 적용될 때 Target으로부터 제거된다.

Immunity

  • 다른 GE의 동작을 효과적으로 막기 위해 GE는 GameplayTag를 이용해 Immunity를 부여할 수 있다.
  • Application Tag Requirements와 같이 다른 수단을 통햐 Immunity를 효과적으로 부여할 수 있기도 하지만,
    이 System을 사용하면 Immunity로 GE가 차단될 때 delegate를 받을 수 있습니다.
    • 더보기
      /** Notifies when GameplayEffectSpec is blocked by an ActiveGameplayEffect due to immunity  */
      DECLARE_MULTICAST_DELEGATE_TwoParams(FImmunityBlockGE, const FGameplayEffectSpec& /*BlockedSpec*/, const FActiveGameplayEffect* /*ImmunityGameplayEffect*/);
      
      /** Immunity notification support */
      FImmunityBlockGE OnImmunityBlockGameplayEffectDelegate;

GarantedApplicationImmunityTags

  • 5.3 버전에서 Deprecated 됨
  • UImmunityGameplayEffectComponent를 대신 사용

GrantedApplicationImmunityQuery

  • 5.3 버전에서 Deprecated 됨
  • UImmunityGameplayEffectComponent를 대신 사용

GameplayEffectSpec

  • GE의 인스턴스화로 취급할 수 있다.(이하 GESpec)
    • 자신이 가르키는 GE의 Reference 만들어진 Level, 만든 주체에 대한 정보를 내포하고 있다.
    • 이 정보들은 GE와 다르게 Runtime상에서 적용되기 전까지 자유롭게 생성/수정이 가능하다.
  • GE가 적용 될 때 GESpec이 GE로부터 생성되고 실질적으로 GESpec이 Target에 적용된다.
  • GESpec은 UASC::MakeOutgoingSpec()함수를 통해 GE로부터 생성된다.
    • 더보기
      /** Get an outgoing GameplayEffectSpec that is ready to be applied to other things. */
      UFUNCTION(BlueprintCallable, Category = GameplayEffects)
      virtual FGameplayEffectSpecHandle MakeOutgoingSpec(TSubclassOf<UGameplayEffect> GameplayEffectClass, float Level, FGameplayEffectContextHandle Context) const;
  • GESpec은 그 즉시 적용될 필요가 없다.
    • 보통은 투사체에 GESpec을 전달하고 충돌 발생 시 연산에 사용하곤 한다.
  • GESpec이 성공적으로 적용되면 FActiveGameplayEffect를 반환한다.
  • GESpec에는 아래 내용들이 제공된다.
    • GESpec을 만드는데 사용된 GE 클래스
    • GESpec의 Level
      • 보통 GESpec을 만드는데 사용한 Ability와 같지만 다를수도 있음
    • Duration
      • Default는 GE의 Duration이지만 값이 다를 수도 있다.
    • Period
      • Default는 GE의 Period이지만 다를 수도 있다.
    • 현재 GESpec의 Stack Count
      • 상한은 GE의 설정값을 따른다.
    • GameplayEffectContextHandle
      • 이를 통해 누가 이 GeSpec을 만들었는지 인지할 수 있다.
    • GESpec이 생성 되었을 때 Snapshotting으로 인해 Capture되는 Attribute 들
    • DynamicGrantedTag
      • GESpec이 부여 될 때 GameplayTag에 GE가 추가로 부여해야 하는 GameplayEffectSpec
    • DynamicAssetTag
      • GESpec이 GE와 비교해 추가로 가져야 하는 Tag들
    • SetByCaller

SetByCallers

  • GESpec으로 하여금 GameplayTag나 FName과 관련된 float 값을 전달할 수 있도록 한다.
    • 이들은 GESpec에 TMap 형태로 저장된다.
    • 이 값들은 GE의 Modifier에 사용되거나, 일반적인 의미의 Ferring Floats로 사용될 수 있다.

Modifiers에서 사용

  • GE class보다 앞서 정의되어야만 한다.
  • GameTag 버전만 사용 가능하다.
  • GE class에 하나가 정의 되었는데 GESpec이 대응하는 Tag-float pair가 없는 경우,
    GESpec에서 Runtime Error가 발생하며 0을 반환한다.
  • Divide Operation에서 문제를 야기할 수 있다.

그 외에서 사용

  • 어느 class보다도 앞서 정의 될 필요가 없다.
  • GESpec에 존재하지 않는 SetByCaller를 읽더라도 사용자로 하여금 Default 값과 warning을 반환할 수 있다.

관련 함수

  • 보통 Ability 안에서 생성된 숫자 데이터들은 SetByCallers을 통해
    GameplayEffectExecutionCalculations나 ModifierMagnitudeCalculations로 전달하는 것이 일반적이다.
  • SetByCaller를 BP에서 읽을 수 있으려면 BPLibrary에 Custom Node를 만들어 줘야 한다.
  • GESpec은 SetByCaller 값과 관련된 아래 함수들을 제공하고 있다.
    • 더보기
      /** Sets the magnitude of a SetByCaller modifier */
      void SetSetByCallerMagnitude(FName DataName, float Magnitude);
      
      /** Sets the magnitude of a SetByCaller modifier */
      void SetSetByCallerMagnitude(FGameplayTag DataTag, float Magnitude);
      
      /** Returns the magnitude of a SetByCaller modifier. Will return 0.f and Warn if the magnitude has not been set. */
      float GetSetByCallerMagnitude(FName DataName, bool WarnIfNotFound = true, float DefaultIfNotFound = 0.f) const;
      
      /** Returns the magnitude of a SetByCaller modifier. Will return 0.f and Warn if the magnitude has not been set. */
      float GetSetByCallerMagnitude(FGameplayTag DataTag, bool WarnIfNotFound = true, float DefaultIfNotFound = 0.f) const;
       
    • 이 중 FName을 쓰는 것보다 GameplayTag를 사용하는 것을 권장한다.

GameplayEffectContext

  • GEContext는 GESpec의 Instigator와 TargetData를 가지고 있다.
    • 이는 Subclass를 통해 임시 데이터를 다른 곳으로 전달하는 좋은 구조이다.
    • ModifierMagnitudeCalculations, GameplayEffectExecutionCalculations, AS, GameplayCues 등

사용법

  • FGameplayEffectContext를 상속
  • GetScriptStruct(), Duplicate(), NetSerialize() 함수들을 Override
  • 상위 Class(FGameplayEffectContext)처럼 하위 Class의 TStructOpsTypeTraits를 구현
  • AbilitySystemGlobals::AllocGameplayEffectContext() 함수에서 새로 만든 Class를 반환하도록 override .

ModifierMagnitudeCalculation

  • GE에서 Modifier와 같이 매우 강력한 Class(이하 MMC)
  • 기능은 GameplayEffectExecution Calculations와 비슷하나 덜 강력하고, Predict 가능하다.
    • 유일한 목적은 CalculateBaseMagnitude_Implementation() 함수를 통해 float value를 반환하는 것이다.
  • BP와 C++ 모두에게서 상속 받아 override 할 수 있다.
  • Instant, Duration, Infinite, Periodic GE 모두에게서 사용할 수 있다.
  • MMC의 강점은 GESpec으로부터 GameplayTag나 SetByCaller를 읽을 수 있는 권한을 통해
    Source의 Attribute나 GE의 Target을 캡쳐 할 수 있다는 점이다.
  • 또한 Attribute는 스냅샷이 될 수도 있다.
    • 스냅샷이 되는 Attribute는 GESpec이 생성될 때 캡쳐가 된다.
    • 스냅샷이 되지 않는 Attribute는 Infinite, Duration GE로 인해 자동으로 갱신되어 GESpec이 적용될 때 캡쳐된다.
  • Attribute를 캡쳐하는 것은 ASC에 존재한 mod로부터 CurrentValue를 재계산 한다.
    • 이 재계산은 AbilitySet의 PreAttributeChange() 함수를 호출하지 않기 때문에
      그 과정 속에서 Clamping이 이루어져야 한다.
  • 만약 Duration, Infinite GE에서 Modifier를 다시 계산해야 한다면, SetActiveGameplayEffectLevel 함수를 사용한다.
    • 더보기
      void SetActiveGameplayEffectLevel(FActiveGameplayEffectHandle ActiveHandle, int32 NewLevel);
  • MMC의 결과값은 계수와 계수 전후 추가 값으로 인해 GE의 Modifier에서 수정될 수 있다.

GameplayEffectExecutionCalculation

  • GE로 하여금 ASC에 변화를 가할 수 있는 가장 강력한 방법(이하 ExeCalc)
    • MMC와 같이 Attribute를 캡쳐하고 선택적으로 스냅샷 할 수 있다.
    • MMC와 다르게 1개 이상의 Attribute를 바꿀 수 있고, 무엇보다 Programmer가 원하는 것을 추가할 수 있다.
    • 단점으로는 Predict 할 수 없고 반드시 C++로만 작업이 되어야 한다는 점이다.
  • Instant, Periodic GE에서만 사용할 수 있다.
    • 보통 "Execute"가 붙은 것들은 대부분 Instant, Periodic GE와 관련되어 있다.
  • 스냅샷은 GE가 생성될 때 Attribute를 캡쳐한다.
    • 스냅샷을 하지 않는 것들은 GE가 적용 될 때 Attribute를 캡쳐한다.
  • Attribute를 캡쳐하는 것은 ASC의 mod에 있는 Attribute의 CurrentValue를 재계산한다.
    • 이 재계산은 AbilitySet의 PreAttributeChange 함수를 호출하지 않는다.
    • 때문에 이 과정에서 Clamping이 반드시 이루어져야 한다.
  • Attribute 캡쳐를 설정하기 위해 우리는 Epic의 ActionRPG Sample Project에서 설정한 패턴을 따른다.
    • 이는 Attribute를 저장하고 캡처 방식을 정의하는 구조체를 정의하고, 생성자에서 그 복사본을 생성한다.
    • 또한 각 ExecCalc당 하나씩 있다.
    • 각 구조체는 같은 namespace를 공유하려면 유니크한 이름이 필요하다.
    • 같은 이름의 구조체를 사용하는 것은 Attribute Capture에 있어 모호함을 발생한다.
  • Local Predicted, Server Only, Server Initiated GameplayAbility에서 ExecCalc는 서버에서만 호출된다.
  • Source와 Target의 여러 Attribute로부터 복잡한 연산을 통해 Damage를 계산하는 것이
    가장 대표적인 ExecCalc의 예시이다.

ExeCalc에 Data 보내기

  • Attribute Capture에 더불어 ExeCalc에 정보를 전달하는 몇 가지 방법이 있다.

SetByCaller

  • GESpec에 설정된 SetByCaller는 ExeCalc가 직접 읽을 수 있디ㅏ.
  • 더보기
    const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
    float Damage = FMath::Max<float>(Spec.GetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.Damage")), false, -1.0f), 0.0f);

Attribute Calculation Modifier 데이터 백업

  • 만약 값을 GE에 하드코딩 하고 싶다면,
    백업 데이터로 캡쳐한 Attribute 중 하나를 CalculationModifier를 통해 전달할 수 있다.

Temporary Variable Calculation Modifier 데이터 백업

  • 만약 값을 GE에 하드코딩 하고 싶다면,
    C++에서 호출되는 Temporary Variable이나 Transient Aggregator를 CalculationModifier를 통해 전달할 수 있다.
  • Temporary Variable은 GameplayTag와 연관이 있다.

Gameplay Effect Context

  • GESpec의 Custom GameplayEffectContext를 통해 ExecutionCalculation으로 데이터를 보낼 수 있다.

CustomApplicationRequirement

  • Designer로 하여금 GE의 적용여부를 통제할 수 있는 고급 기능을 제공한다.(이하 CAR)
    • 단순히 GameplayTag 검사보다 GE에 대해 더 복잡한 조건을 설정할 수 있게 한다.
  • C++에서는 CanApplyGameplayEffect_Implementation(), BP에서는 CanApplyGameplayEffect를 override 할 수 있다.
  • 또한 CAR는 좀 더 확장된 기능을 작업할 수 있다.
    • GE Instance가 이미 Target에 있는지 여부 확인
    • 새 Instance를 등록하지 않고 기존 Instance의 Duration을 변경

Executions

더보기
/** 
 * 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;

	/** 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;
};


	/** Array of executions that will affect the target of this effect */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = GameplayEffect)
	TArray<FGameplayEffectExecutionDefinition> Executions;
  • Modifier에서 지원하는 정보 이상의 것이 필요할 때에는 사용
  • UGameplayEffectExecutionCalculation을 사용하면 GE의 Custom Behaviour를 정의
    • Modifier로 커버할 수 없는 복잡한 식을 정의할 때 유용

Application Requirements

  • GE를 적용하는 조건을 처리하는 부분
  • 5.3 버전 기준으로 Deprecated
    • UCustomCanApplyGameplayEffectComponent를 대신 사용

Granted Abilities

  • GE를 통해 부여하는 Tag 혹은 Attribute
  • 5.3 버전 기준으로 Deprecated
    • GetBlockedAbilityTags 함수를 통해 UTargetTagsGameplayEffectComponent를 대신 사용

Overflow Effects 

	/** Effects to apply when a stacking effect "overflows" its stack count through another attempted application. Added whether the overflow application succeeds or not. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Stacking|Overflow", meta = (EditConditionHides, EditCondition = "StackingType != EGameplayEffectStackingType::None"))
	TArray<TSubclassOf<UGameplayEffect>> OverflowEffects;
  • 이미 적용된 GE가 다시 적용되는 Overflow 상황을 처리하는 정책
    • 이미 적용되어 있는 GE가 포화상태가 되면 새로운 GE가 적용되는 것을 지칭
  • System상 다양한 Behaviour를 제공
    • 한계치를 넘을 때까지 중첩
    • 새로 적용될 때마다 늘어나는 Stack 수를 최대 한도까지 유지
    • 시간이 지나명 GE의 시간이 리셋 또는 추가
    • 단순한 GE의 여러 Instance에 개별 Timer를 독립적으로 적용

Gameplay Cue Display

더보기
/**
 * FGameplayEffectCue
 *	This is a cosmetic cue that can be tied to a UGameplayEffect. 
 *  This is essentially a GameplayTag + a Min/Max level range that is used to map the level of a GameplayEffect to a normalized value used by the GameplayCue system.
 */
USTRUCT(BlueprintType)
struct FGameplayEffectCue
{
	GENERATED_USTRUCT_BODY()

	FGameplayEffectCue()
		: MinLevel(0.f)
		, MaxLevel(0.f)
	{
	}

	FGameplayEffectCue(const FGameplayTag& InTag, float InMinLevel, float InMaxLevel)
		: MinLevel(InMinLevel)
		, MaxLevel(InMaxLevel)
	{
		GameplayCueTags.AddTag(InTag);
	}

	/** The attribute to use as the source for cue magnitude. If none use level */
	UPROPERTY(EditDefaultsOnly, Category = GameplayCue)
	FGameplayAttribute MagnitudeAttribute;

	/** The minimum level that this Cue supports */
	UPROPERTY(EditDefaultsOnly, Category = GameplayCue)
	float	MinLevel;

	/** The maximum level that this Cue supports */
	UPROPERTY(EditDefaultsOnly, Category = GameplayCue)
	float	MaxLevel;

	/** Tags passed to the gameplay cue handler when this cue is activated */
	UPROPERTY(EditDefaultsOnly, Category = GameplayCue, meta = (Categories="GameplayCue"))
	FGameplayTagContainer GameplayCueTags;

	float NormalizeLevel(float InLevel)
	{
		float Range = MaxLevel - MinLevel;
		if (Range <= KINDA_SMALL_NUMBER)
		{
			return 1.f;
		}

		return FMath::Clamp((InLevel - MinLevel) / Range, 0.f, 1.0f);
	}
};


	/** Cues to trigger non-simulated reactions in response to this GameplayEffect such as sounds, particle effects, etc */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GameplayCues")
	TArray<FGameplayEffectCue>	GameplayCues;
  • Particle이나 Sound 같은 Decoration Effect를 관리하는 효율적인 Network 방식
    • GAS로 제어할 수 있다.
  • GA나 GE로 트리거 할 수 있다.
  • 모든 Gameplay Cue에는 GameplayCue로 시작하는 Gameplay Tag를 붙여야 한다.
  • Native C++ 혹은 BP로 Override할 수 있고 4개 함수로 작동한다.
    • 더보기
      	/** Called when a GameplayCue is executed, this is used for instant effects or periodic ticks */
      	UFUNCTION(BlueprintNativeEvent, Category = "GameplayCueNotify")
      	bool OnExecute(AActor* MyTarget, const FGameplayCueParameters& Parameters);
      
      	/** Called when a GameplayCue with duration is first activated, this will only be called if the client witnessed the activation */
      	UFUNCTION(BlueprintNativeEvent, Category = "GameplayCueNotify")
      	bool OnActive(AActor* MyTarget, const FGameplayCueParameters& Parameters);
      
      	/** Called when a GameplayCue with duration is first seen as active, even if it wasn't actually just applied (Join in progress, etc) */
      	UFUNCTION(BlueprintNativeEvent, Category = "GameplayCueNotify")
      	bool WhileActive(AActor* MyTarget, const FGameplayCueParameters& Parameters);
      
      	/** Called when a GameplayCue with duration is removed */
      	UFUNCTION(BlueprintNativeEvent, Category = "GameplayCueNotify")
      	bool OnRemove(AActor* MyTarget, const FGameplayCueParameters& Parameters);

GA 와의 Interaction

  • GE가 GA와 상호작용할 때에 AS에서 제공하는 함수를 이용한다.
    • 하지만 이는 기본적인 기능만 제공하고,
      Attribute의 값 변경에 대한 추가 작업이나 범위 지정을 하려면 AS의 함수를 Override 해야 한다.
  •  
  • 더보기
    	/**
    	 *	Called just before modifying the value of an attribute. AttributeSet can make additional modifications here. Return true to continue, or false to throw out the modification.
    	 *	Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
    	 */	
    	virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData &Data) { return true; }
    	
    	/**
    	 *	Called just after a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made.
    	 *	Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
    	 */
    	virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) { }
    
    	/**
    	 *	An "On Aggregator Change" type of event could go here, and that could be called when active gameplay effects are added or removed to an attribute aggregator.
    	 *	It is difficult to give all the information in these cases though - aggregators can change for many reasons: being added, being removed, being modified, having a modifier change, immunity, stacking rules, etc.
    	 */
    
    	/**
    	 *	Called just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify.
    	 *	There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.
    	 *	This function is meant to enforce things like "Health = Clamp(Health, 0, MaxHealth)" and NOT things like "trigger this extra thing if damage is applied, etc".
    	 *	
    	 *	NewValue is a mutable reference so you are able to clamp the newly applied value as well.
    	 */
    	virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) { }
    
    	/** Called just after any modification happens to an attribute. */
    	virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) { }
    
    	/**
    	 *	This is called just before any modification happens to an attribute's base value when an attribute aggregator exists.
    	 *	This function should enforce clamping (presuming you wish to clamp the base value along with the final value in PreAttributeChange)
    	 *	This function should NOT invoke gameplay related events or callbacks. Do those in PreAttributeChange() which will be called prior to the
    	 *	final value of the attribute actually changing.
    	 */
    	virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const { }
    
    	/** Called just after any modification happens to an attribute's base value when an attribute aggregator exists. */
    	virtual void PostAttributeBaseChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) const { }

 

'UE5 > GAS' 카테고리의 다른 글

[GAS] Gameplay Ability  (0) 2024.05.17
[GAS] Ability Task  (0) 2024.05.16
[GAS] Attribute Set  (1) 2024.05.14
[GAS] Gameplay Attribute  (0) 2024.05.13
[GAS] Ability System Component  (0) 2024.05.13

+ Recent posts