UE 5 스터디/Gameplay Ability System(GAS)

23-7. 디버프 - (7) 디버프 게임플레이 이펙트(C++)

Crat3 2025. 2. 26. 19:53

0. 개요

C++ 내에서 동적으로 디버프를 적용하게 하기 위해 게임플레이 이펙트를 생성하고 적용한다.

 

 

1. 게임플레이 이펙트의 C++ 버전

(1) AuraAttributeSet의 Debuff 함수

일반적으로 게임플레이 이펙트는 블루프린트로 생성하지만, 제한적으로 C++에서 생성하여 사용할 수도 있다.


- C++ 코드로 생성한 게임플레이 이펙트는 - 메타 속성이든 아니든 - 복제를 지원하지 않기 때문에 서버에서 처리하여야 한다.


void UAuraAttributeSet::Debuff(const FEffectProperties& Props)
{
    const FAuraGameplayTags& GameplayTags = FAuraGameplayTags::Get();
    // 게임플레이 이펙트 컨텍스트 핸들 생성 및 정보 추가
    FGameplayEffectContextHandle EffectContext = Props.SourceASC->MakeEffectContext();
    EffectContext.AddSourceObject(Props.SourceAvatarActor);

    // 디버프 정보 가져오기
    const FGameplayTag DamageType = UAuraAbilitySystemLibrary::GetDamageType(Props.EffectContextHandle);
    const float DebuffDamage = UAuraAbilitySystemLibrary::GetDebuffDamage(Props.EffectContextHandle);
    const float DebuffDuration = UAuraAbilitySystemLibrary::GetDebuffDuration(Props.EffectContextHandle);
    const float DebuffFrequency = UAuraAbilitySystemLibrary::GetDebuffFrequency(Props.EffectContextHandle);

    FString DebuffName = FString::Printf(TEXT("Dynamic Debuff_%s"), *DamageType.ToString());

    // 게임플레이 이펙트 생성
    UGameplayEffect* Effect = NewObject<UGameplayEffect>(GetTransientPackage(), FName(DebuffName));
    
    // 이펙트 설정
    Effect->DurationPolicy = EGameplayEffectDurationType::HasDuration;  // 지속형
    Effect->Period = DebuffFrequency;                                                   // 주기
    Effect->DurationMagnitude = FScalableFloat(DebuffDuration);             // 지속 시간
    
    // 데미지 타입에 따른 디버프의 태그 가져오기
    Effect->InheritableOwnedTagsContainer.AddTag(GameplayTags.DamageTypesToDebuff[DamageType]);

    // 디버프 스택 설정
    Effect->StackingType = EGameplayEffectStackingType::AggregateBySource;
    Effect->StackLimitCount = 1;

    // 수정자 가져오기
    const int32 Index = Effect->Modifiers.Num();
    Effect->Modifiers.Add(FGameplayModifierInfo());
    FGameplayModifierInfo& ModifierInfo = Effect->Modifiers[Index];

    // 수정자 설정
    ModifierInfo.ModifierMagnitude = FScalableFloat(DebuffDamage);
    ModifierInfo.ModifierOp = EGameplayModOp::Additive;
    ModifierInfo.Attribute = UAuraAttributeSet::GetIncomingDamageAttribute();

    // 디버프 레벨 설정
    FGameplayEffectSpec* MutableSpec = new FGameplayEffectSpec(Effect, EffectContext, 1.f/*Debuff Level*/);
    if (MutableSpec)
    {
        FAuraGameplayEffectContext* AuraContext = static_cast<FAuraGameplayEffectContext*>(EffectContext.Get());
        TSharedPtr<FGameplayTag> DebuffDamageType = MakeShareable(new FGameplayTag(DamageType));
        AuraContext->SetDamageType(DebuffDamageType);
    }
    
    // 게임플레이 이펙트 적용
    Props.TargetASC->ApplyGameplayEffectSpecToSelf(*MutableSpec);
}

- 디버프가 다른 디버프를 발생시키는 루프 상황을 만들지 않도록 조심한다.


(2) 단계별 분석

(2-1) 이펙트 컨텍스트 핸들 생성 및 데이터 추가

    // 게임플레이 이펙트 컨텍스트 핸들 생성 및 정보 추가
    FGameplayEffectContextHandle EffectContext = Props.SourceASC->MakeEffectContext();
    EffectContext.AddSourceObject(Props.SourceAvatarActor);

게임플레이 이펙트 스펙을 생성하기 위한 맥락인 이펙트 컨텍스트 핸들을 생성한다.

이펙트 발생자인 소스의 아바타 액터를 소스 오브젝트로 추가한다.

 

 

(2-2) 디버프 정보 가져오기

    // 디버프 정보 가져오기
    const FGameplayTag DamageType = UAuraAbilitySystemLibrary::GetDamageType(Props.EffectContextHandle);
    const float DebuffDamage = UAuraAbilitySystemLibrary::GetDebuffDamage(Props.EffectContextHandle);
    const float DebuffDuration = UAuraAbilitySystemLibrary::GetDebuffDuration(Props.EffectContextHandle);
    const float DebuffFrequency = UAuraAbilitySystemLibrary::GetDebuffFrequency(Props.EffectContextHandle);

    FString DebuffName = FString::Printf(TEXT("Dynamic Debuff_%s"), *DamageType.ToString());

라이브러리의 Getter 함수를 호출하여 데미지 타입(Fire, Lightning, Arcane, Physical)을 가져온다.

디버프 데미지, 지속시간, 주기를 가져와 멤버 변수로 저장한다.

DebuffName은 게임플레이 이펙트의 제목을 짓기 위해 사용한다.

 

 

(2-3) 게임플레이 이펙트 생성 및 데이터 추가

 // 게임플레이 이펙트 생성
    UGameplayEffect* Effect = NewObject<UGameplayEffect>(GetTransientPackage(), FName(DebuffName));
    
    // 이펙트 설정
    Effect->DurationPolicy = EGameplayEffectDurationType::HasDuration;  // 지속형
    Effect->Period = DebuffFrequency;                                                   // 주기
    Effect->DurationMagnitude = FScalableFloat(DebuffDuration);             // 지속 시간
    
    // 데미지 타입에 따른 디버프의 태그 가져오기
    Effect->InheritableOwnedTagsContainer.AddTag(GameplayTags.DamageTypesToDebuff[DamageType]);

    // 디버프 스택 설정
    Effect->StackingType = EGameplayEffectStackingType::AggregateBySource;
    Effect->StackLimitCount = 1;

이 프로젝트의 디버프는 모두 지속형(Duration)이다.

주기와 Magnitude 지속 시간을 설정한다.

 

이후에는 이전에 AuraGameplayTags에서 데미지와 디버프를 연결한 TMap을 가져와 데미지 타입에 해당하는 디버프를 꺼내와 대상에게 디버프 태그를 부여한다.

 

디버프는 스택형으로 설정한다.


- 스태킹 유형(Stacking Type)


 

 

(2-4) 수정자를 추가하고 가져와 설정하기

    // 수정자 추가
    const int32 Index = Effect->Modifiers.Num();
    Effect->Modifiers.Add(FGameplayModifierInfo());
    FGameplayModifierInfo& ModifierInfo = Effect->Modifiers[Index];

    // 수정자 설정
    ModifierInfo.ModifierMagnitude = FScalableFloat(DebuffDamage);
    ModifierInfo.ModifierOp = EGameplayModOp::Additive;
    ModifierInfo.Attribute = UAuraAttributeSet::GetIncomingDamageAttribute();

모든 게임플레이 이펙트는 다양한 수정자를 추가할 수 있다.

(디버프의 경우 하나의 디버프가 체력을 닳게 하면서 마나를 닳게 하는 식)

 

새로 수정자를 하나 추가하고, IncomingDamage 메타 속성을 가져와 입는 데미지가 누적되도록 설정할 것이다.

 

 

(2-5) 게임플레이 이펙트 스펙을 생성하고 적용하기

    // 이펙트 스펙 생성
    FGameplayEffectSpec* MutableSpec = new FGameplayEffectSpec(Effect, EffectContext, 1.f/*Debuff Level*/);
    if (MutableSpec)
    {
        FAuraGameplayEffectContext* AuraContext = static_cast<FAuraGameplayEffectContext*>(EffectContext.Get());
        TSharedPtr<FGameplayTag> DebuffDamageType = MakeShareable(new FGameplayTag(DamageType));
        AuraContext->SetDamageType(DebuffDamageType);
    }
    
    // 게임플레이 이펙트 적용
    Props.TargetASC->ApplyGameplayEffectSpecToSelf(*MutableSpec);

게임플레이 이펙트 스펙을 게임플레이 이펙트와 이펙트 컨텍스트를 사용하여 동적으로 생성한다.

 

기존의 이펙트 컨텍스트를 커스텀된 AuraGameplayEffectContext로 캐스팅하여 사용한다.

캐스팅을 통해 직접 선언했던 Setter를 사용할 수 있게 된다.

 

커스텀 이펙트 컨텍스트에 디버프 데미지 타입을 추가해 넣는다.

(디버프의 데미지 타입은 피격된 어빌리티의 데미지 타입과 동일하다)

 

 

2. 디버그

(1) 디버프가 캐릭터의 사후에도 게임플레이 이펙트가 적용되는 버그

void UAuraAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData & Data)
{
    // 게임플레이 이펙트 효과가 실행된 이후에 발동되는 함수
    Super::PostGameplayEffectExecute(Data);

    // 소스가 캐릭터가 아닐 경우에 컨트롤러를 얻지 못할 수 있음!!
    FEffectProperties Props;
    SetEffectProperties(Data, Props);

    // 캐릭터 사망 이후 이펙트 실행 방지
    if (Props.TargetCharacter->Implements<UCombatInterface>())
    {
        if (ICombatInterface::Execute_IsDead(Props.TargetCharacter))
            return;
    }

이펙트를 적용 받는 대상(Target) 캐릭터의 사망 상태를 확인하고 사망 상태라면 리턴한다.