23-7. 디버프 - (7) 디버프 게임플레이 이펙트(C++)
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) 캐릭터의 사망 상태를 확인하고 사망 상태라면 리턴한다.