본문 바로가기

게임프로그래밍/실습2

[실습2] 23. C++에서 인피니트 게임플레이 이펙트 제거하기

1. 게임플레이 이펙트 제거하기

 

인피니트 게임플레이 이펙트를 주면 그 효과는 수동으로 제거하기 전까지 계속 지속된다. 이제 이 효과를 취소하는 법을 알아보자.

 

어떤 효과를 제거하고 어떤 효과를 제거하지 않을지 표시할 수 있으면 좋을 것이다. 이를 열거형으로 정의하려고 한다.

 

UENUM(BlueprintType)
enum class EffectApplicationPolicy : uint8
{
    ApplyOnOverlap,
    ApplyOnEndOverlap,
    DoNotApply
};

 

그리고 이제 각각의 게임플레이 이펙트에 대해 정책을 설정해준다.

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
TSubclassOf<UGameplayEffect> InstantGameplayEffectClass;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
EEffectApplicationPolicy InstantEffectApplicationPolicy;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
TSubclassOf<UGameplayEffect> DurationGameplayEffectClass;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
EEffectApplicationPolicy DurationEffectApplicationPolicy;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
TSubclassOf<UGameplayEffect> InfiniteGameplayEffectClass;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
EEffectApplicationPolicy InfiniteEffectApplicationPolicy;

 

이펙트 클래스와 정책들을 설정해주었다. 여기서는 표현 안했지만 정책들의 기본값은 적용하지 않음이다.

 

이제 제거 정책도 추가해보자. 제거 정책은 인피니트 게임플레이 이펙트에만 적용되는 정책이다.

 

UENUM(BlueprintType)
enum class EEffectRemovalPolicy : uint8
{
    RemoveOnEndOverlap,
    DoNotRemove
};

 

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
EEffectRemovalPolicy InfiniteEffectRemovalPolicy;

 

이제 정책들이 있으니 오버랩할때와 엔드 오버랩 할때 일어날 함수를 만들면 된다.

 

UFUNCTION(BlueprintCallable)
void OnOverlap(AActor* TargetActor);

UFUNCTION(BlueprintCallable)
void OnEndOverlap(AActor* TargetActor);

 

매개변수로 게임플레이 이펙트를 넘겨줄 필요는 없다. 멤버변수로 이미 선언되어있으며 이들을 적용할지 안할지는 열거형으로 알 수 있다.

 

이제 실제로 함수를 구현해보자


2. 오버랩, 엔드오버랩 함수 정의하기

 

void AAuraEffectActor::OnOverlap(AActor* TargetActor)
{
    if (InstantEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
    {
       ApplyEffectToTarget(TargetActor, InstantGameplayEffectClass);
    }
    if (DurationEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
    {
       ApplyEffectToTarget(TargetActor, DurationGameplayEffectClass);
    }
}

void AAuraEffectActor::OnEndOverlap(AActor* TargetActor)
{
    if (InstantEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnEndOverlap)
    {
       ApplyEffectToTarget(TargetActor, InstantGameplayEffectClass);
    }
    if (DurationEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
    {
       ApplyEffectToTarget(TargetActor, DurationGameplayEffectClass);
    }
}

 

기본적으로 위와 같이 구현할 수 있다. 정책에 해당하면 해당 효과를 발동한다.

 

이제 무한효과를 어떻게 적용할지 알아보자. 위의 두 효과와 달리 오버랩시 발동하고 엔드오버랩시 제거해야할 것이다. 그리고 그렇게 하기 위해서는 현재 어떤 인피니트 효과가 적용되고 있는지를 알아야 제거가 가능할 것이다.


3. 인피니트 이펙트 적용 및 제거

 

ApplyGameplayEffectSpecToSelf

 

위의 함수는 이펙트를 적용하는 함수인데 해당 함수는 엑티브 게임플레이 이펙트 핸들을 반환한다. 이를 이용하면 현재 적용 중인 이펙트가 무엇인지 알 수 있을 것이고 그렇기에 이 핸들을 저장할 수 있어야 한다.

 

여기서 주의할 점이 있다. 이 핸들을 단순히 클래스에 저장하고 이를 활용하면 끝일까? 답은 No이다. 이펙트 액터 클래스에는 다양한 타겟 액터들이 접근할 수 있고 단순히 이를 한 변수에 저장하면 마지막에 들어온 액터의 값만 덮혀씌어질 것이다.

 

그렇다면 배열에 저장하면? 단순한 배열에 저장하면 어떤 핸들이 어떤 액터의 것인지 알 방법이 없다.그렇다면 이 두가지를 연결할 수 있으면 된다.

 

바로 TMap을 이용하는 것이다.

 

현재 적용하고 있는 게임플레이 이펙트가 인피니트인지 아닌지는 어떻게 알 수 있을까? 제거 정책을 확인하고 제거 정책이 존재하면 무한 효과이다. 다만 그런 방법 말고 열거형 없이도 확인하는 방법은 아래와 같다.

const bool bIsInfinite = EffectSpecHandle.Data.Get()->Def.Get()->DurationPolicy == EGameplayEffectDurationType::Infinite;

 

스펙 핸들에서 Def는 게임플레이 이펙트 그 자체이다. 여기서 DurationPolicy를 가져오면 된다. 참고로 C++에서 이 값을 바꿀 생각이 없기 때문에 주의하도록 하자.

 

이제 액티브 게임플레이 이펙트 핸들을 로컬 변수로 저장하자.

FActiveGameplayEffectHandle ActiveEffectHandle = TargetASC->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());

 

헤더 파일에서 이를 저장할 Map을 만들자

TMap<FActiveGameplayEffectHandle, UAbilitySystemComponent*> ActiveEffectHandles;

 

그리고 이를 저장하면 된다. 저장하기 전에 딱 하나 제거할지 안할지만 체크하면 된다.

const bool bIsInfinite = EffectSpecHandle.Data.Get()->Def.Get()->DurationPolicy == EGameplayEffectDurationType::Infinite;
if (bIsInfinite && InfiniteEffectRemovalPolicy == EEffectRemovalPolicy::RemoveOnEndOverlap)
{
    ActiveEffectHandles.Add(ActiveEffectHandle, TargetASC);
}
 

이제 엔드오버랩에서 이를 처리하자.

 

if (InfiniteEffectRemovalPolicy == EEffectRemovalPolicy::RemoveOnEndOverlap)
{
    UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetActor);
    if (!IsValid(TargetASC)) return;

    for (TTuple<FActiveGameplayEffectHandle, UAbilitySystemComponent*> HandlePair : ActiveEffectHandles)
    {
       if (TargetASC == HandlePair.Value)
       {
          TargetASC->RemoveActiveGameplayEffect(HandlePair.Key);
       }
    }
}

 

엔드오버랩 하는 액터의 ASC와 맵에 저장되어있는 ASC가 같은지 확인하고 같으면 게임플레이 이펙트를 제거한다.

 

이제 제거가 완료되었으면 맵에서도 제거해주어야 한다. 하지만 for문에서 바로 제거하면 무한 루프가 일어날 것이다. 그러니 제거된 요소를 저장할 다른 배열을 임시로 만들어 이와 비교하여 제거하는 방식으로 가야한다.

 

TArray<FActiveGameplayEffectHandle> HandlesToRemove;
for (TTuple<FActiveGameplayEffectHandle, UAbilitySystemComponent*> HandlePair : ActiveEffectHandles)
{
    if (TargetASC == HandlePair.Value)
    {
       TargetASC->RemoveActiveGameplayEffect(HandlePair.Key);
       HandlesToRemove.Add(HandlePair.Key);
    }
}

for (FActiveGameplayEffectHandle& Handle : HandlesToRemove)
{
    ActiveEffectHandles.FindAndRemoveChecked(Handle);
}

 


제거될 이펙트를  저장할 배열을 만들고,

TArray<FActiveGameplayEffectHandle> HandlesToRemove;

 

이를 순환하여 이펙트를 제거하는 함수를 호출하면 된다.

for (FActiveGameplayEffectHandle& Handle : HandlesToRemove)
{
    ActiveEffectHandles.FindAndRemoveChecked(Handle);
}

 

이제 컴파일 하고 에디터에서 수정사항들을 고치면 된다.


4. 버그 수정하기

 

에디터에서 확인한 결과 문제가 하나 있다. 무한 효과를 주는 액터를 여러개가 겹쳐있을때 한 구역에서만 나가도 모든 효과가 제거되 더이상 효과가 적용되지 않는다. 이를 개선해보자.

 

TargetASC->RemoveActiveGameplayEffect(HandlePair.Key, 1);

 

이펙트를 제거하는 부분에서 이렇게 제거할 스택의 갯수를 넘기면 끝이다. 그리고 한개의 액터는 한개의 효과만 줄 예정이니 한개만 제거하면 된다.