Post

Unreal: Gameplay Ability System(GAS) - Gameplay Effect (GE) 2

Unreal: Gameplay Ability System(GAS) - Gameplay Effect (GE) 2

Gameplay Effect (GE) 내용 보충

  • Set By Caller를 이용하면 외부에서 전해주는 값에 따라 어트리뷰트를 변경하는 것도 가능하다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
      // 예: 트랩, 투사체, 스킬 등 외부 객체가 데미지 값을 정해서 GE에 전달하는 경우
    
      FGameplayEffectContextHandle Context = ASC->MakeEffectContext();
      FGameplayEffectSpecHandle SpecHandle = ASC->MakeOutgoingSpec(DamageGEClass, 1.f, Context);
    
      if (SpecHandle.IsValid())
      {
          // GE 안에서 같은 GameplayTag를 Set By Caller Magnitude로 사용해야 한다.
          SpecHandle.Data->SetSetByCallerMagnitude(
              FGameplayTag::RequestGameplayTag(TEXT("Data.Damage")),
              DamageAmount
          );
    
          ASC->ApplyGameplayEffectSpecToTarget(
              *SpecHandle.Data.Get(),
              TargetASC
          );
      }
    
    • 더 어렵거나 복잡한 계산을 필요로 할경우 ExecutionCalculator를 생성해 사용할 수 있다.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
        // 예: 공격력, 방어력, 치명타, 태그 상태 등을 조합해 최종 데미지를 계산하는 구조  
      		  
        void UDamageExecution::Execute_Implementation(const  FGameplayEffectCustomExecutionParameters& Params, FGameplayEffectCustomExecutionOutput& OutOutput) const  
        {  
        float  AttackPower = 0.f;  
        float  Defense = 0.f;  
      		  
        // 실제 구현에서는 CaptureDef를 통해 Source/Target Attribute를 가져온다.  
        // 여기서는 계산 흐름만 예시로 둔다.  
      		  
        float  RawDamage = AttackPower  -  Defense;  
        float  FinalDamage = FMath::Max(RawDamage, 0.f);  
      		  
        OutOutput.AddOutputModifier(  
        FGameplayModifierEvaluatedData(  
        UMyAttributeSet::GetHealthAttribute(),  
        EGameplayModOp::Additive,  
        -FinalDamage  
        )  
        );  
        }
      
  • 클램핑을 이용해 어트리뷰트를 제한/보호 가능하다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    void UMyAttributeSet::PreAttributeChange(
      const FGameplayAttribute& Attribute,
      float& NewValue
    )
    {
      Super::PreAttributeChange(Attribute, NewValue);
    
      if (Attribute == GetHealthAttribute())
      {
          NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
      }
      else if (Attribute == GetStaminaAttribute())
      {
          NewValue = FMath::Clamp(NewValue, 0.f, GetMaxStamina());
      }
    }
    
  • 필요시에는 PostGameplayEffectExecute()에서 SetHealth(GetHealth());같은 방식으로 CurrentValue(버프등에 의해 변화한 보정값)가 아닌 BaseValue(원본 기준 값)까지 변화시켜줄 수 있다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    void UMyAttributeSet::PostGameplayEffectExecute(
      const FGameplayEffectModCallbackData& Data
    )
    {
      Super::PostGameplayEffectExecute(Data);
    
      if (Data.EvaluatedData.Attribute == GetHealthAttribute())
      {
          // GetHealth()로 현재 계산된 값을 가져오고,
          // SetHealth()로 BaseValue 쪽에도 보정된 값을 다시 반영한다.
          SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
      }
    
      if (Data.EvaluatedData.Attribute == GetStaminaAttribute())
      {
          SetStamina(FMath::Clamp(GetStamina(), 0.f, GetMaxStamina()));
      }
    }
    
  • ASC를 별도로 만들어서 사용할 수도 있다.
    • 블루프린트에서 구현하던 것 대신, ApplyInitEffects(), ApplyGameplayEffectSpecToSelf()등을 코드상에서 구현할 수 있다.
    • AttributeSet내부 생성자에서 직접 초기화하지 않고, ASC를 통해 GE를 호출해 초기화할 수 있다. ```c++ // Character BeginPlay 등에서 호출

    void AMyCharacter::BeginPlay() { Super::BeginPlay();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
      if (MyASC)
      {
          // GE_Init, GE_FullStat 같은 초기화용 GE가 InitEffects에 들어있다고 가정
          MyASC->ApplyInitEffects();
      }   }   ```   * 어빌리티의 초기화 및 ID를 통한 관리도 ASC에서 진행할 수 있다. map구조로 열거형과 어빌리티를 조합할 수 있는 구조로 만들면,   에디터에서도 간단하게 어빌리티를 여러종류 할당할 수 있는 구조로 만들 수 있다.   ```c++   void UMyAbilitySystemComponent::GiveInitAbilities()   {
      if (!GetOwner())
      {
          return;
      }
    		
          //열거열 EAbilityID와 어빌리티 클래스에 따른 어빌리티 부여
      for (const TPair<EAbilityID, TSubclassOf<UGameplayAbility>>& Pair : InitAbilities)
      {
          const EAbilityID InputID = Pair.Key;
          TSubclassOf<UGameplayAbility> AbilityClass = Pair.Value;
    
          if (!AbilityClass)
          {
              continue;
          }
    
          FGameplayAbilitySpec AbilitySpec(
              AbilityClass,
              1,          // 어빌리티 레벨
              InputID,    // 입력ID
              nullptr
          );
    
          GiveAbility(AbilitySpec);
      }   }   ```
    
This post is licensed under CC BY 4.0 by the author.