0. 개요
이펙트가 발동하는 순간과 제거되는 순간을 직접 관리하기 위해 AuraEffectActor를 수정할 것이다.
1. 이펙트 수명(적용 및 제거) 관리하기
(1) enum class 생성
이제 효과를 적용하는 것과 적용된 효과를 제거하는 것을 enum class로 관리할 것이다.
UENUM(BlueprintType)
enum class EEffectApplicationPolicy
{
ApplyOnOverlap,
ApplyOnEndOverlap,
DoNotApply
};
UENUM(BlueprintType)
enum class EEffectRemovalPolicy
{
// Infinite Effect에만 적용됨
RemoveOnEndOverlap,
DoNotRemove
};
EEffectApplicationPolicy는 액터가 겹침이 시작될 때 / 겹침이 끝날 때 (혹은 적용하지 않음)이펙트를 적용할지에 대한 여부를 선택한다.
EEffectRemovalPolicy는 액터의 겹침이 끝날 때 이펙트를 제거할 지 (혹은 제거하지 않음)에 대한 여부를 선택한다.
Duration Effect에서는 Duration이 종료되면 자동으로 이펙트가 제거되므로, 제거 정책은 Infinite Effect에서만 적용된다.
class AURA_API AAuraEffectActor : public AActor
{
GENERATED_BODY()
public:
AAuraEffectActor();
protected:
virtual void BeginPlay() override;
UFUNCTION(BlueprintCallable)
void ApplyEffectToTarget(AActor* TargetActor, TSubclassOf<UGameplayEffect> GameplayEffectClass);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
TSubclassOf<UGameplayEffect> InstantGameplayEffectClass;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
EEffectApplicationPolicy InstantEffectApplicationPolicy = EEffectApplicationPolicy::DoNotApply;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
TSubclassOf<UGameplayEffect> DurationGameplayEffectClass;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
EEffectApplicationPolicy DurationEffectApplicationPolicy = EEffectApplicationPolicy::DoNotApply;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
TSubclassOf<UGameplayEffect> InfiniteGameplayEffectClass;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
EEffectApplicationPolicy InfiniteEffectApplicationPolicy = EEffectApplicationPolicy::DoNotApply;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
EEffectRemovalPolicy InfiniteEffectRemovalPolicy = EEffectRemovalPolicy::RemoveOnEndOverlap;
}
이제 각 이펙트 클래스에 대해 정책의 기본 값을 할당한다.
(2) 블루프린트 Callable 함수 정의
이제 다른 액터와의 겹침이 발생하면 Application Policy와 Removal Policy에 따라 이펙트가 작동하도록 블루프린트에서 설정하게 할 것이다.
이펙트가 제거되면 액터가 파괴되도록 설정할 수 있는 불리언도 함께 정의한다.
protected:
virtual void BeginPlay() override;
UFUNCTION(BlueprintCallable)
void ApplyEffectToTarget(AActor* TargetActor, TSubclassOf<UGameplayEffect> GameplayEffectClass);
UFUNCTION(BlueprintCallable)
void OnOverlap(AActor* TargetActor);
UFUNCTION(BlueprintCallable)
void OnEndOverlap(AActor* TargetActor);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
bool bDestroyOnEffectRemoval = false;
2. 즉시 적용되는 이펙트(Instant Effect)
즉시 적용되는 이펙트일 때 액터가 접촉하면 어떻게 처리할지 작성한다.
void AAuraEffectActor::OnOverlap(AActor *TargetActor)
{
if (InstantEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
{
ApplyEffectToTarget(TargetActor, InstantGameplayEffectClass);
}
}
void AAuraEffectActor::OnEndOverlap(AActor *TargetActor)
{
if (InstantEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnEndOverlap)
{
ApplyEffectToTarget(TargetActor, InstantGameplayEffectClass);
}
}
즉시 적용되는 이펙트는 겹침이 발생한 직후에 효과를 발생하거나, 겹침이 끝난 이후에 효과가 발생하도록 하면 된다.
3. 지속 시간동안 적용되는 이펙트(Duration Effect)
즉시 적용되는 이펙트와 동일하게 작동한다.
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::ApplyOnEndOverlap)
{
ApplyEffectToTarget(TargetActor, DurationGameplayEffectClass);
}
}
4. 무제한 적용되는 이펙트(Infinite Effect)
무제한 적용되는 이펙트의 경우 EndOverlap 상황에서 이펙트가 해제되어야 한다.
즉, 액터에 적용된 이펙트를 해제해야 하므로 이를 확인할 필요가 있다.
void AAuraEffectActor::ApplyEffectToTarget(AActor *TargetActor, TSubclassOf<UGameplayEffect> GameplayEffectClass)
{
// 타겟의 어빌리티 시스템 인터페이스 가져옴
UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetActor);
if (TargetASC == nullptr)
return;
check(GameplayEffectClass);
// Gameplay Effect Context 생성
FGameplayEffectContextHandle EffectContextHandle = TargetASC->MakeEffectContext();
// 이펙트 발생자에 대한 정보 추가
EffectContextHandle.AddSourceObject(this);
// Gameplay Effect Spec 생성
const FGameplayEffectSpecHandle EffectSpecHandle = TargetASC->MakeOutgoingSpec(GameplayEffectClass, 1.f, EffectContextHandle);
// Gameplay Effect Spec을 본인에게 적용
const FActiveGameplayEffectHandle ActiveEffectHandle = TargetASC->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());
// 이펙트 스펙 핸들을 참조하여 Infinite Effect 확인
const bool bIsInfinite = EffectSpecHandle.Data.Get()->Def.Get()->DurationPolicy == EGameplayEffectDurationType::Infinite;
if (bIsInfinite && InfiniteEffectRemovalPolicy == EEffectRemovalPolicy::RemoveOnEndOverlap)
{
ActiveEffectHandles.Add(ActiveEffectHandle, TargetASC);
}
}
위의 코드를 단계적으로 살펴본다.
(1) ActiveEffectHandle
타겟의 어빌리티 시스템 컴포넌트에게 해당 이펙트를 적용시켰는데, 리턴 값은 FActiveGameplayEffectHandle이다.
즉 활성화된 이펙트 정보를 가지고 있는 핸들이 이것이므로, 이것을 로컬 변수로 저장한다.
(2) bIsInfinite
이제 이펙트의 타입이 Infinite인지 확인(만 하고 변경시키지는 않는다)한다.
이펙트 스펙 핸들의 데이터의 원시 포인터를 타고 들어가면 Def가 존재하며, Def의 원시 포인터를 타고 들어가면 DurationPolicy를 통해 Infinite인지 확인할 수 있다.
(3) Map 생성
헤더 파일 말미에 아래 코드를 추가한다.
TMap<FActiveGameplayEffectHandle, UAbilitySystemComponent*> ActiveEffectHandles;
활성화 중인 Infinite Effect를 관리하는 Map 컨테이너이다.
키로 활성화 중인 이펙트 핸들, 값으로 해당 어빌리티 시스템 컴포넌트 포인터를 나타낸다.
(4) if 문
Infinite Effect가 제거될 필요 없는 DoNotRemove 정책을 가지고 있다면 굳이 활성화 된 이펙트를 맵으로 관리하고 있을 필요가 없으므로, 추후에 제거가 필요한 경우만 추가한다.
(5) OnOverlap 함수
void AAuraEffectActor::OnOverlap(AActor *TargetActor)
{
if (InstantEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
{
ApplyEffectToTarget(TargetActor, InstantGameplayEffectClass);
}
if (DurationEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
{
ApplyEffectToTarget(TargetActor, DurationGameplayEffectClass);
}
if (InfiniteEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
{
ApplyEffectToTarget(TargetActor, InfiniteGameplayEffectClass);
}
}
(6) EndOverlap 함수
void AAuraEffectActor::OnEndOverlap(AActor *TargetActor)
{
if (InstantEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnEndOverlap)
{
ApplyEffectToTarget(TargetActor, InstantGameplayEffectClass);
}
if (DurationEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnEndOverlap)
{
ApplyEffectToTarget(TargetActor, DurationGameplayEffectClass);
}
if (InfiniteEffectApplicationPolicy == EEffectApplicationPolicy::ApplyOnEndOverlap)
{
ApplyEffectToTarget(TargetActor, InfiniteGameplayEffectClass);
}
if (InfiniteEffectRemovalPolicy == EEffectRemovalPolicy::RemoveOnEndOverlap)
{
UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetActor);
if (!isValid(TargetASC))
return;
// Map 컨테이너에서 제거할 핸들
TArray<FActiveGameplayEffectHandle> HandlesToRemove;
// 활성화 된 Infinite 이펙트 Map 컨테이너 순회
for (TPair<FActiveGameplayEffectHandle, UAbilitySystemComponent *> HandlePair : ActiveEffectHandles)
{
// 동일한 ASC이면
if (TargetASC == HandlePair.Value)
{
// 게임플레이 이펙트 제거
TargetASC->RemoveActiveGameplayEffect(HandlePair.Key);
HandlesToRemove.Add(HandlePair.Key);
}
}
// Map 컨테이너에서 이펙트 제거
for (auto& Handle : HandlesToRemove)
{
ActiveEffectHandles.FindAndRemoveChecked(Handle);
}
}
}
이제 ActiveEffectHandles Map에 Infinite Effect의 Handle과 ASC가 키-값으로 저장되어 있을 것이다.
오버렙이 끝난 시점에서 이펙트를 제거하고자 하기 때문에 순회를 통해 동일한 ASC이면 게임플레이 이펙트를 먼저 제거하도록 한다.
순회 중에 값을 제거하면 잘못된 메모리 참조 크래시가 발생할 수 있으므로, 제거할 핸들을 담은 Array에 핸들을 하나씩 추가한다.
이후에 다른 루프를 돌려서 Map 컨테이너에서 해당 요소를 하나씩 제거한다.
(7) BP_FireArea 이벤트 그래프
위에서 작성한 함수와 오버랩 시작 / 종료 시 이벤트를 연결해준다.
5. Infinite Effect 버그 수정
이제 BP_FireArea를 동시에 3개 밟으면 15의 데미지를 입게 된다.
그러나 여러 개의 이펙트가 스택되어 있을 때 한 개의 FireArea 이펙트만 밟으면 HP가 소모되지 않는데 이는 EndOverlap 함수에 의해 이벤트 효과가 제거되었기 때문이다.
// 활성화 된 Infinite 이펙트 Map 컨테이너 순회
for (TPair<FActiveGameplayEffectHandle, UAbilitySystemComponent *> HandlePair : ActiveEffectHandles)
{
// 동일한 ASC이면
if (TargetASC == HandlePair.Value)
{
// 게임플레이 이펙트 제거
TargetASC->RemoveActiveGameplayEffect(HandlePair.Key, 1);
HandlesToRemove.Add(HandlePair.Key);
}
}
RemoveActiveGameplayEffect의 두번째 인자는 제거할 스택의 개수를 가리키며, 작성하지 않으면 디폴트 값으로 -1이 입력된다.
-1이 입력되면 스택 전체를 제거하기 때문에, 스택을 하나씩 제거하고자 한다면 함수의 값을 변경해주자.
이제 정상적으로 스택이 하나씩 제거되는 것을 알 수 있다.
'UE 5 스터디 > Gameplay Ability System(GAS)' 카테고리의 다른 글
4-9. 커브 테이블(Curve Tables) (1) | 2024.08.28 |
---|---|
4-8. 속성 세트 값 클램핑 - PreAttributeChange 함수, PostGameplayEffectExcute 함수 (0) | 2024.08.28 |
4-6. 게임플레이 이펙트 클래스 - (4) 무한 지속(Infinte) (0) | 2024.08.23 |
4-5. 게임플레이 이펙트 클래스 - (3) 스택(Stacking) (0) | 2024.08.23 |
4-4. 게임플레이 이펙트 클래스 - (2) 이펙트 지속 적용(Duration) (0) | 2024.08.23 |