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

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gameplay-attributes-and-attribute-sets-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-as

 

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

Definition

  • 하나 이상의 GA로 AS를 구성하고, ASC에 등록을 한다.
    • Owner Actor의 Constructor에서 AttributeSet을 생성하면 ASC에 자동으로 등록이 된다.

Design

  • ASC가 여러 개의 AS를 가질 수는 있지만, 각 AS는 모두 서로 클래스가 달라야 한다.
  • AS는 무시할만한 Memory Overhead를 야기한다.
    • 그렇기에 얼마나 AS를 많이 사용할지는 개발자의 결정에 달려있다.
  • 또한 AS는 Actor가 어떤 Attribute를 가질지 선택지를 제공하는 의미로 SubClass를 생성할 수 있다.
    • Atribute는 내부에서 AttributeSetClassName.AttributeName으로 일컬어진다.
    • 때문에 AS를 상속 받더라도, 상위 AS의 Attribute는 상위 AS의 이름을 그대로 유지한다.

Subcomponents with Individual Attributes

  • 장비 파괴처럼 복수의 Damagable Component를 소유했을 때, Component의 최대 갯수를 알 수 있다면
    AttributeSet에 각 Component에 대응하는 health Attribute를 만드는 것을 권장한다.
    • Damagable Component에는 가상의 Slot 값을 부여
    • 부여된 Slot값을 통해 GameplayAbilities를 읽거나 Attribute에 접근해 데미지 계산이 가능
    • Character/Pawn은 1개 이하의 health Attribute만 소유하는 것을 권장
    • Attribute는 AttributeSet이 가져야 하는거지, Character/Pawn이 가져야만 하는 것이 아니기 때문.
  • 하지만 다음 조건에서는 위와 같은 방식이 기대한 것만큼 잘 동작하지 않는다.
    • Subcomponent가 Runtime 상 상한이 없는 경우
    • Subcomponent가 Detach되어 다른 유저가 사용할 수 있는 경우
  • 이 때에는 Attribute를 사용하지 말고 고전적인 float 타입의 변수를 Component에 직접 부여하는 것을 권장한다.
  • 자세한 사항은 Item Attribute를 확인하길 바란다.

Add/Remove AttributeSet at Runtime

  • AS은 Runtime 상에 ASC에 추가될 수 있다.
    • 물론 ASC로부터 제거도 가능하지만 이는 매우 위험하다.
    • 만약 client에서 server보다 먼저 AS를 제거하고 Attribute 값 변화가 client로 replicate 된다면, 
      Attribute를 찾을 수 없어 Crash가 난다.
  • 그렇기에 Runtime 상에서 AS를 추가/제거를 할 때에는 대상 ASC에 대해 ForceReplication 함수를 호출해줘야 한다.

Item Attribute

  • 장비류 아이템에 대한 Attribute를 구현하는 방식.
    • 이런 방식은 전부 Item에 직접 값을 저장한다.
  • 이 시스템은 1명 이상의 플레이어가 Lifetime동안 장비를 장착/탈착 할 수 있는 경우에 반드시 제공되어야 한다.

Plain Floats on the Item

  • Attribute 대신 float 변수로 정보를 관리하는 방식
    • Fortnite와 GAS 예시코드가 이런 방식을 채택
  • 총기가 최대 Ammo 수, 현재 Ammo 수, 남은 Ammo 수 등을 저장하고 Client에 Replicate한다.
  • 만약 총기가 남은 Ammo를 공유하며느 그 값을 Character의 Attribute로 가져와 AttributeSet에 저장한다.
  • 다만 총기에서 Attribute를 사용하지 않기 때문에, UGameplayAbility에서 제공하는 일부 함수를 override해야 한다.
    • 총기에서 사용하는 Plain Float에 대한 검증과 계산을 위해
  • 총기를 GameplayAbilitySpec의 SourceObject로 지정하면 접근 권한을 Ability 내에서 처리할 수 있다.
  • 자동사격 과정에서 총기의 Ammo가 Replicate로 인해 덮어씌어지는 Clobbering을 방지하기 위해,
    PreReplication() 함수에서 IsFiring GameplayTag가 있는 동안 Ammo의 Replicate를 비활성화 한다.
    • 더보기
      void AGSWeapon::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker)
      {
      	Super::PreReplication(ChangedPropertyTracker);
      
      	DOREPLIFETIME_ACTIVE_OVERRIDE(AGSWeapon, PrimaryClipAmmo, (IsValid(AbilitySystemComponent) && !AbilitySystemComponent->HasMatchingGameplayTag(WeaponIsFiringTag)));
      	DOREPLIFETIME_ACTIVE_OVERRIDE(AGSWeapon, SecondaryClipAmmo, (IsValid(AbilitySystemComponent) && !AbilitySystemComponent->HasMatchingGameplayTag(WeaponIsFiringTag)));
      }
    • 장점
      • AttributeSet을 사용하면서 발생하는 한계를 피할 수 있음
    • 단점
      • 현존하는 GameplayEffects의 흐름을 사용할 수 없음.
      • UGameplayAbility의 핵심 함수를 ammo 관리를 위해 override 해야 함.

AttributeSet on the Item

  • Item에서 AttributeSet을 사용하고 Player의 ASC를 이용하는 방법
  • 하지만 근본적인 한계가 존재한다.
    • Weapon이 가지고 있는 고유한 Attribute를 경우에 따라 Character로 옮겨와야 한다.
    • 만약 Weapon을 Inventory에 넣는다면,
      Weapon의 AttributeSet을 Character가 가진 ASC의 SpawnedAttributes에 옮겨와야 한다.
  • 만약 AttributeSet이 OwnerActor가 아닌 곳에 존재하면 Compile Error가 발생한다.
  • 이를 수정하기 위해 다음 작업이 진행되어야 한다.
    • AttributeSet을 Constructor가 아닌 BeginPlay()에서 Construct
    • IAbilitySystemInterface를 Implement
  • 장점
    • 제공되는 GameplayAbility와 GameplayEffect를 그대로 사용 가능
    • Item에 간단한 세팅으로 기능 구현 가능
  • 단점
    • 각 무기 타입에 맞는 AttributeSet을 생성해야 한다.
      • ASC는 기능적으로 Class당 하나의 AttributeSet을 가질 수 밖에 없다.
      • ASC가 Attribute 변경사항을 적용할 때 SpawnedAttributes에서 조건에 맞는 첫번째 AS만 탐색하기 때문.
      • 나머지 AttributeSet은 항상 무시 됨
    • 위와 같은 이유로 플레이어 당 동일한 무기를 2개 이상 소지할 수 없음
    • AttributeSet을 Runtime상에서 제거하는 것에서 오는 근본적인 리스크
      • 예를 들어 탄속이 있는 무기로 적을 처치 했는데 발사와 처치 사이에 무기가 제거가 되면
        관련 AttributeSet을 찾지 못해 Crash가 발생한다.

ASC on the Item

  • ASC를 통째로 아이템에 박아 넣는 극단적인 방법.
  • 장점
    • GameplayAbility와 GameplayEffect의 모든 기능을 오롯이 사용 가능
    • Attributeset Class를 재사용 할 수 있음
      • ASC에서 1개만 존재하니까
  • 단점
    • 개발 비용이 까마득하고 검증되지 않음

Defining Attributes

  • 더보기
     UCLASS()
     class MYPROJECT_API UMyAttributeSet : public UAttributeSet
     {
         GENERATED_BODY()
     
         public:
         /** 퍼블릭 액세스 가능한 샘플 어트리뷰트 'Health'*/
         UPROPERTY(EditAnywhere, BlueprintReadOnly)
         FGameplayAttributeData Health;
     };

     

    class MyAbilitySystemComponent : public UAbilitySystemComponent
    {
    	....
    	/** 샘플 어트리뷰트 세트. */
    	UPROPERTY()
    	const UMyAttributeSet* AttributeSet;
    	....
    }
     // 이런 코드는 일반적으로 BeginPlay()에 나타나지만,
     // 적절한 어빌리티 시스템 컴포넌트 구하기일 수 있습니다. 다른 액터에 있을 수도 있으므로 GetAbilitySystemComponent를 사용하여 결과가 유효한지 확인하세요.
     AbilitySystemComponent* ASC = GetAbilitySystemComponent();
     // AbilitySystemComponent가 유효한지 확인합니다. 실패를 허용할 수 없다면 if() 조건문을 check() 구문으로 대체합니다.
     if (IsValid(ASC))
     {
         // 어빌리티 시스템 컴포넌트에서 UMyAttributeSet를 구합니다. 필요한 경우 어빌리티 시스템 컴포넌트가 UMyAttributeSet를 생성하고 등록할 것입니다.
         AttributeSet = ASC->GetSet<UMyAttributeSet>();
     
         // 이제 새 UMyAttributeSet에 대한 포인터가 생겨 나중에 사용할 수 있습니다. 초기화 함수가 있는 경우 여기서 호출하면 좋습니다.
     }


    1.  Attribute는 FGameplayAttributeData 타입으로 Attribute를 선언한다.
    2. 위 코드에서는 Attribute Set 안의 값이 코드에서 직접 수정하지 않도록 const로 선언을 정의한다.
    3. AttributeSet은 Actor Construct에서 Instance화 하는 시점에 함수가 유효한 ASC를 반환하는 한,
      이 시점 혹은 BeginPlay 도중에 자동으로 등록된다.
    4. BP를 편집하여 Attribute Set Type을 ASC의 Default Start Data로 추가할 수 있다.
    5. ASC가 없는 GA를 수정하는 Gameplay Effect가 적용된다면, ASC는 일치하는 GA를 자동으로 생성한다.
      1. 하지만 이 Methond는 AS를 생성하거나, 기존 AS에 GA를 추가하지 않는다.

Helper Funcion Macro

  • GAS에서는 GA와 상호작용할 기본 헬퍼 함수를 추가할 수 있다.
    • GA 자체는 protected/private 접근자로 설정하고, 상호작용 함수를 public으로 설정하는 것이 좋다.
  • 사용이 필수 사항은 아니지만, 모범 사례로 간주한다.
  • 더보기
    #define GAMEPLAYATTRIBUTE_REPNOTIFY(ClassName, PropertyName, OldValue) \
    { \
    	static FProperty* ThisProperty = FindFieldChecked<FProperty>(ClassName::StaticClass(), GET_MEMBER_NAME_CHECKED(ClassName, PropertyName)); \
    	GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication(FGameplayAttribute(ThisProperty), PropertyName, OldValue); \
    }
    
    #define GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
    	static FGameplayAttribute Get##PropertyName##Attribute() \
    	{ \
    		static FProperty* Prop = FindFieldChecked<FProperty>(ClassName::StaticClass(), GET_MEMBER_NAME_CHECKED(ClassName, PropertyName)); \
    		return Prop; \
    	}
    
    #define GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
    	FORCEINLINE float Get##PropertyName() const \
    	{ \
    		return PropertyName.GetCurrentValue(); \
    	}
    
    #define GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
    	FORCEINLINE void Set##PropertyName(float NewVal) \
    	{ \
    		UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent(); \
    		if (ensure(AbilityComp)) \
    		{ \
    			AbilityComp->SetNumericAttributeBase(Get##PropertyName##Attribute(), NewVal); \
    		}; \
    	}
    
    #define GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName) \
    	FORCEINLINE void Init##PropertyName(float NewVal) \
    	{ \
    		PropertyName.SetBaseValue(NewVal); \
    		PropertyName.SetCurrentValue(NewVal); \
    	}

Initialize

  • AttributeMetaData라는 GAS TableRow로 초기화 할 수 있다.
    • 이는 외부 파일에서 Import할 수도 있고, Editor에서 수동으로 채워줄 수도 있다.
  •  
    DataTable Asset 생성 할 때 AttributeMetaData를 선택한다.
  • 위와 같이 선언된 AttributeMetaData Table Asset에 추가 Row를 덧붙여 AS를 다수의 GA로 지원할 수 있다.
    • 예를 들어 MyAttributeSet.Health 외에 MyAttributeSet.Attack Row를 추가하면 한 AS에서 제공하는 다수의 GA를 하나의 Table Asset으로 Initialize 할 수 있다.
  • Column에 MinValue, MaxValue가 지원되지만 GA와 AS에 범위 제한 행동이 없으므로 이 항목들은 무효하다.

Gameplay Attribute Access 제어

  • GA에 대한 직접 Access를 지어하는 것은 AS 통해 수행되며, FGameplayAttributeData를 확장하지 않는다.
    • FGameplayAttributeData는 Data에 대한 Access만 저장하고 제공한다.
  • GAS에서 제공하는 Macro를 사용하지 않고, 동일한 Macro에 구현부를 직접 작업해 값의 범위를 제한할 수도 있다.
  • 더보기
    GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UMyAttributeSet, Health);
    float GetHealth() const;
    void SetHealth(float NewVal);
    GAMEPLAYATTRIBUTE_VALUE_INITTER(Health);

     

    float UMyAttributeSet::GetHealth() const
    {
        // Health의 현재 값을 반환하지만, 0 미만의 값은 반환하지 않습니다.
        // 이 값은 Health에 영향을 미치는 모든 모디파이어가 고려된 후의 값입니다.
        return FMath::Max(Health.GetCurrentValue(), 0.0f);
    }
    
    void UMyAttributeSet::SetHealth(float NewVal)
    {
        // 0 미만의 값을 받지 않습니다.
        NewVal = FMath::Max(NewVal, 0.0f);
    
        // 어빌리티 시스템 컴포넌트 인스턴스가 있는지 확인합니다. 항상 인스턴스가 있어야 합니다.
        UAbilitySystemComponent* ASC = GetOwningAbilitySystemComponent();
        if (ensure(ASC))
        {
            // 적절한 함수를 통해 현재 값이 아닌 베이스 값을 설정합니다.
            // 그러면 적용한 모디파이어가 계속해서 적절히 작동합니다.
            ASC->SetNumericAttributeBase(GetHealthAttribute(), NewVal);
        }
    }
    
    AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSet->GetHealthAttribute()).AddUObject(this, &AGASAbilityDemoCharacter::OnHealthChangedInternal);

Gameplay Effect와의 Interaction

  • GA의 값을 제어하는 가장 일반적인 방법은 관련 Gameplay Effect를 처리하는 것이다.
  •  
    • PostGameplayEffectExecute는 Attribute의 Base 값을 변경되는 GameplayEffect가 호출되기 전에 호출된다.
      • GameplayEffect가 호출(Execute) 되기 전에만 호출되고, 직접 접근해 수정할 때에는 호출되지 않는다.
      • 또한 BaseValue가 아닌 current 값에 영향을 주는 GameplayEffect에서도 호출되지 않는다.
    • 위 예시에서 PostGameplayEffectExecute 함수는 public 접근자가 지정되어야 한다.
    • 함수 내에서 새로운 Body를 작성하되, Super::PostGameplayEffectExecute가 확실히 호출되어 있어야 한다.
  • 더보기
    class UMyAttributeSet : public AttributeSet
    {
    	.....
    public:
    	void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;
    	.....
    }

     

    void UMyAttributeSet::PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data)
    {
    	// 잊지 말고 부모 구현을 호출하세요.
    	Super::PostGameplayEffectExecute(Data);
    
    	// 프로퍼티 게터를 사용하여 이 호출이 Health에 영향을 미치는지 확인합니다.
    	if (Data.EvaluatedData.Attribute == GetHealthAttribute())
    	{
    		// 이 게임플레이 이펙트는 Health를 변경합니다. 적용하되 우선 값을 제한합니다.
    		// 이 경우 Health 베이스 값은 음수가 아니어야 합니다.
    		SetHealth(FMath::Max(GetHealth(), 0.0f));
    	}
    }

Replication

  • Multiplayer의 경우, 여타 Property Replicate와 유사하게 AS을 통해 GA Replicate를 할 수 있다.
  • 더보기
    class UMyAttributeSet : public UAttributeSet
    {
    	.....
    
        /** 네트워크를 통해 새 Health 값이 도착할 때 호출됨 */
        UFUNCTION()
        virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);
        
        /** 리플리케이트할 프로퍼티 표시 */
        virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
    
    protected:
        /** 샘플 어트리뷰트 'Health'*/
        UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Health)
        FGameplayAttributeData Health;
        
        ....
    };

     

    void UMyAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth)
    {
    	// 디폴트 게임플레이 어트리뷰트 시스템의 repnotify 행동을 사용합니다.
    	GAMEPLAYATTRIBUTE_REPNOTIFY(UMyAttributeSet, Health, OldHealth);
    }
    
    void UMyAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
    {
    	// 부모 함수를 호출합니다.
             Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
    	// Health에 대한 리플리케이션을 추가합니다.
    	DOREPLIFETIME_CONDITION_NOTIFY(UMyAttributeSet, Health, COND_None, REPNOTIFY_Always);
    }
    • Replicate 할 Attribute에 PROPERTY에 ReplicatedUsing 지정자를 추가하고, Callback 함수를 선언한다.
    • Source File에서 Callback 함수를 정의한다.
    • Callback 함수 내에는 GAMEPLAYATTRIBUTE_REPNOTIFY 매크로로
      Replicate 되는 Attribute에 대한 RepNotify 행동을 실행한다.
    • 만약 이 AS가 처음 Replicate되는 Attribute라면, GetLifetimeReplicatedProps에 추가한다.

주요 함수

PreAttributeChange

	/**
	 *	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) { }
  • Attribute의 CurrentValue가 바뀌기 전에 호출되는 함수
    • NewValue를 이용해 CurrentValue를 Clamp한 상태로 적용할 때 사용 함.
    • 더보기
      if (Attribute == GetMoveSpeedAttribute())
      {
      	// Cannot slow less than 150 units/s and cannot boost more than 1000 units/s
      	NewValue = FMath::Clamp<float>(NewValue, 150, 1000);
      }
  • 단, Attribute에 직접 접근하여 변경하는 것이 아닌 Macro에서 제공하는 Setter 함수나 GameplayEffects로만 호출된다.
  • 그리고 Epic에서는 이 함수를 Attribute 변경에 대한 Delegate로 사용을 권장하지 않는다.
    • 그저 Attribute 값의 보정(Clamp) 용도로 사용을 권장한다.

PostGameplayEffectExecute

	/**
	 *	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) { }
  • Attribute의 BaseValue가 Instant GameplayEffect로 인해 변경될 때 호출되는 함수
    • GameplayEffect로 인해 Attribute가 변동될 때 좀 더 복잡한 작업을 수행할 수 있다.
  • 함수가 호출된 시점에는 이미 Attribute 값이 변경이 된 후다.
    • 하지만 아직 Client에는 Replicate 되지 않았다.
  • 때문에 여기서 Clamping을 하는 것은 Client에서는 발생하지 않고, 그 결과만 받는다.

OnAttributeAggregatorCreate

	/** Callback for when an FAggregator is created for an attribute in this set. Allows custom setup of FAggregator::EvaluationMetaData */
	virtual void OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const { }
  • Attribute가 set에서 생성되어 Aggregator가 생성될 때.
  • FAggregatorEvaluateMetaData에 대한 Custom Setup을 할 수 있다.
    • Attribute에서 Modifier로 적용하는데 가장 작은 음수 값을 가져올 수 있다.
    • Paragon에서 여러 개의 감속 디버프가 걸릴 때 가장 낮은 비율로 적용할 때 이 기능을 사용
  • 더보기
    void UGSAttributeSetBase::OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const
    {
    	Super::OnAttributeAggregatorCreated(Attribute, NewAggregator);
    
    	if (!NewAggregator)
    	{
    		return;
    	}
    
    	if (Attribute == GetMoveSpeedAttribute())
    	{
    		NewAggregator->EvaluationMetaData = &FAggregatorEvaluateMetaDataLibrary::MostNegativeMod_AllPositiveMods;
    	}
    }

 

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

[GAS] Ability Task  (0) 2024.05.16
[GAS] Gameplay Effect  (0) 2024.05.15
[GAS] Gameplay Attribute  (0) 2024.05.13
[GAS] Ability System Component  (0) 2024.05.13
[GAS] Gameplay Ability System 소개  (0) 2024.05.10

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gameplay-attributes-and-attribute-sets-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-a

 

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

 

  • FGameplayAttribute를 사용해 Gameplay 관련 소수값을 저장/계산 수정하는 기능
    • 남은 생명력
    • Vehicle의 최고 속력
    • 아이템의 사용 가능 횟수
  • GAS의 Actor는 Gameplay Attribute(이하 GA)는 Attribute Set(이하 AS)에 저장한다.
    • 이는 Attribute를 Actor의 ASC에 등록하고, GA와 시스템의 나머지 부분간의 상호작용 관리에 유리하다.
    • 상호작용에는 범위 제한, 일시적 값 변경을 위한 계산, 영구적 값 변경을 하는 이벤트에 대한 반응이 있다.
  • GA는 Gameplay Effect로만 수정되어야 한다.
    • 그래야 ASC가 변화를 예측할 수 있다.

Gameplay Attribute

  • 생성하려면 우선 Attribute Set을 만들어야 한다.
    • 경우에 따라서는 AS가 없이도 GA가 존재할 수 있다.
    • 보통은 적절한 GA Type을 포함하는 AS가 없는 ASC에 GA가 저장된 경우
    • 하지만 이 케이스는 GA가 ASC와 상호작용하는 그 어떠한 행동도 정의되지 않고 값만 저장한다.
    • 때문에 권장되지 않는 방식이다.
  • 만약 GA를 Editor에 노출하고 싶지 않다면, UPROPERTY 지정자에 Meta=(HideInDetailsView)를 추가하면 된다.
  • GA 값은 CurrentValue와 BaseValue로 구분된다.

CurrentValue

  • BaseValue에 더불어 Gameplay Effects로 발생한 일시적인 변화를 합친 값
  • Duration, Infinite Gameplay Effects에 의해 값이 변경된다.
    • Duration Gameplay Effects: 미리 지정된 시간 동안만 효과가 적용되는 Gameplay Effects
    • Infinite Gameplay Effects: 특정 조건을 만족하기 전까지 계속 효과가 유지되는 Gameplay Effects
  • PreAttributeChange() 함수에서 CurrentValue 값의 Clamping 작업을 적용할 수 있다.

BaseValue

  • Attribute의 영구적인 값
  • Instant 혹은 Periodic Gameplay Effects에 의해 값이 변경된다.
    • Instant Gameplay Effects: 즉시 발동되지만 그 효과가 영구적인 Gameplay Effects
    • Periodic Gameplay Effects: 주기적으로 반복해서 효과가 발동하는 Gameplay Effects
  • 간혹 BaseValue Attribute의 최대값하고 혼동하지만 이는 잘못된 사용법이다.
    • 현재는 Attribute의 최대값은 별도의 Attribute로 분리되어야 한다.
    • FAttributeMetaData에 Min/Max Value Column이 있지만, Epic에서 WIP라고 코멘트 해두었다.
  • PostGameplayEffectExecute()에서 BaseValue 값의 Clamp를 적용할 수 있다.

Meta Attributes

  • Attributes들과 상호작용을 목적으로 일시적인 값을 담고 있는 임시 Attribute를 지칭
    • 예를 들어 damage의 경우, GameplayEffect로 health Attribute에 직접 영향을 주는 대신
      damage라는 Meta Attribute를 임시선언으로 사용할 수 있다.
    • 이 때 Meta Attribute는 health Attribute에 도달하기 전에 여러 변화를 가할 수 있다.
      • GameplayEffectExecutionCalculation 함수를 통해 Buff/Debuff 적용
      • Shield Attribute에 의해 발생하는 damage의 감소
  • Meta Attribute는 Gameplay Effect간의 영속성이 없고 모두에 의해 override될 수 있다.
    • 또한 일반적으로 Replicate 되지 않는다.
  • Meta Attribute는 적용 대상 Attribute의 수치 변화와 그에 따른 상호작용을 논리적으로 분리할 수 있다.
    • Gameplay Effect가 "얼마나 수치를 변경할 것인가?"를 결정한다면,
      Attribute Set은 "수치가 바뀐 뒤 어떻게 반응할 것인가?"를 결정한다.
    • 하지만 상위 Attribute Set에서 제공하는 Attribute를 대상으로 한다 해도,
      하위 Attribute Set의 구성에 따라 실제 변화량과 그 반응은 다를 수 있다.
    • Meta Attribute는 이렇게 다양한 경우에 대해 유연하게 대응할 수 있다.
  • 물론 Meta Attribute는 좋은 패턴이지만 필수적인 것은 아니다.
    • 만약 Attribute에 대해 하나의 ExecutionCalculation만 제공되고 모든 Actor가 그 Attribute Set을 사용한다면,
      관련된 값들을 직접 수정하는 방식으로 작업하는 것이 더 좋을 수 있다.

Responding to Attribute Changes

  • Attribute의 변화를 UI나 다른 Gameplay에 전달하려면,
    UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute)를 쓴다.
  • 더보기
    typedef TMulticastDelegate_OneParam< void, const FOnAttributeChangeData & > FOnGameplayAttributeValueChange
    
    FOnGameplayAttributeValueChange & GetGameplayAttributeValueChangeDelegate
    (
        FGameplayAttribute Attribute
    )

     

    AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSetBase->GetHealthAttribute()).AddUObject(this, &AGDPlayerState::HealthChanged);
    
    virtual void HealthChanged(const FOnAttributeChangeData& Data);

     

    • 이 때 Attribute 변수는 Server에서만 값이 채워진다.
  • BP에서는 이 함수가 AsyncTask로 감싸져서 제공된다.
    • 보통 UI_HUD에서 사용되며 해당 Widget의 Destruct Event에서 EndTask()가 호출되기 전까지 죽지 않는다.

Derived Attributes

  • 하나의 Attribute가 복수의 Attribute로부터 값이 정해질 때, Infinite Gameplay Effect를 여러 개의 Modifier Magnitude Calculation(이하 MMC)와 함께 사용한다.
  • Derived Attributes는 해당 Attribute에 의존성을 갖는 Attribute들을 자동으로 갱신해준다.
  • Derived Attribute의 연산자 공식은 Modifier Aggregator와 동일한 방식으로 동작한다.
    • 만약 저해진 순서대로 계산이 되어야 한다면, MMC 내부에서 시행하면 된다.
  • PIE에서 Multi Client로 실행한 경우, Editor Preferences -> Run Under One Process 옵션을 비활성화 해야한다.
    • 안 그러면 Derived Attributes가 각각의 Attribute가 client에서 첫번째로 update되지 않으면 갱신하지 않을 것이다.

예시

  • 다음과 같이 값이 정해지는 TestAttrA가 있다고 가정하자.
    • TestAttrA = (TestAttrA + TestAttrB) * (2 * TestAttrC) .
  • 이 때 TestAttrA에 대한 Derived Attributes 설정은 다음과 같다.
  •  

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

[GAS] Ability Task  (0) 2024.05.16
[GAS] Gameplay Effect  (0) 2024.05.15
[GAS] Attribute Set  (1) 2024.05.14
[GAS] Ability System Component  (0) 2024.05.13
[GAS] Gameplay Ability System 소개  (0) 2024.05.10

+ Recent posts