https://www.youtube.com/watch?v=aYSHRXrJcyg
https://github.com/onestone3647/Portfolio_ProjectReplica.git
https://lykanstudio.tistory.com/109
이전에 구현한 EffectSystem에 PoolableInterface와 BaseObjectPoolSystem을 적용하였습니다. EffectSystem은 크게 NiagaraEffect와 ParticleEffect(Cascade)로 구분하여 ObjectPool을 생성합니다. 각 ObjectPool은 해당 이펙트의 포인터 값을 Key로, 이펙트 액터를 보관한 TArray를 Value로 가지는 TMap으로 구성되었습니다.
기능 설명
- 이펙트를 종류별로 ObjectPool 패턴으로 관리합니다.
- DataTable의 정보를 기반으로 EffectPool의 제거 및 초기화를 수행합니다.
- 보관한 이펙트의 활성화 및 비활성화를 관리합니다.
- 동적으로 생성한 이펙트의 Index와 수명을 관리합니다.
부모 이펙트 액터 클래스
PoolableInterface
부모 이펙트 액터 클래스는 PoolableInterface 인터페이스를 구현하여 이펙트 액터를 EffectSystem에서 효율적으로 관리하고 재사용할수 있도록 하였습니다. 인터페이스를 통해 이펙트 액터를 표준화된 방식으로 초기화, 활성화, 비활성화하며, 수명을 관리할 수 있습니다.
// PREffect.h
protected:
/** 오브젝트가 활성화 되었는지 확인하는 함수입니다. */
virtual bool IsActivate_Implementation() const override;
/** 오브젝트를 활성화하는 함수입니다. */
virtual void Activate_Implementation() override;
/** 오브젝트를 비활성화하는 함수입니다. */
virtual void Deactivate_Implementation() override;
/** 오브젝트의 PoolIndex를 반환하는 함수입니다. */
virtual int32 GetPoolIndex_Implementation() const override;
/** 수명을 반환하는 함수입니다. */
virtual float GetLifespan_Implementation() const override;
/**
* 수명을 설정하는 함수입니다.
*
* @param NewLifespan 설정할 수명입니다.
*/
virtual void SetLifespan_Implementation(float NewLifespan) override;
bool APREffect::IsActivate_Implementation() const
{
return bActivate;
}
void APREffect::Activate_Implementation()
{
ActivateEffect();
}
void APREffect::Deactivate_Implementation()
{
DeactivateEffect();
}
int32 APREffect::GetPoolIndex_Implementation() const
{
return PoolIndex;
}
float APREffect::GetLifespan_Implementation() const
{
return GetEffectLifespan();
}
void APREffect::SetLifespan_Implementation(float NewLifespan)
{
SetEffectLifespan(NewLifespan);
}
수명 관리
이펙트 액터는 활성화되는 동시에 수명만큼 타이머를 시작하며, 타이머가 만료되면 이펙트 액터를 비활성화하는 함수를 호출합니다. 이를 통해 이펙트 액터의 수명을 자동으로 관리합니다.
// PREffect.cpp
void APREffect::ActivateEffect(bool bReset)
{
bActivate = true;
SetActorHiddenInGame(!bActivate);
UKismetSystemLibrary::DrawDebugSphere(GetWorld(), GetActorLocation(), 50.0f, 12, FLinearColor::White, 3.0f);
// 이펙트의 수명을 설정합니다. 이펙트의 수명이 끝나면 이펙트를 비활성화합니다.
SetEffectLifespan(EffectLifespan);
}
void APREffect::SetEffectLifespan(float NewLifespan)
{
EffectLifespan = NewLifespan;
if(bActivate)
{
if(NewLifespan > 0.0f)
{
// 수명이 0보다 클 경우, 즉 새로운 수명이 설정된 경우 타이머를 설정합니다.
GetWorldTimerManager().SetTimer(EffectLifespanTimerHandle, this, &APREffect::OnDeactivate, NewLifespan);
}
else
{
// 수명이 0보다 작거나 같을 경우, 즉 이펙트의 수명이 무한대인 경우 이전에 설정한 타이머를 지워 제한된 수명을 가지지 않게 합니다.
GetWorldTimerManager().ClearTimer(EffectLifespanTimerHandle);
}
}
}
void APREffect::OnDeactivate()
{
IPRPoolableInterface::Execute_Deactivate(this);
}
자식 이펙트(NiagaraEffect, ParticleEffect) 액터 클래스
Effect 관리
자식 이펙트 액터 클래스에서는 무보 클래스의 활성화 함수인 ActivateEffect 함수와 비활성화 함수인 DeactivateEffect 함수를 오버라이드하여 각 이펙트 컴포넌트의 활성화 또는 비활성화를 구현하였습니다. 이를 통해 각 이펙트 컴포넌트가 부모 클래스의 활성화 및 비활성화 메커니즘을 따르면서도 개별적인 이펙트 관리가 가능해졌습니다.
// PRNiagaraEffect.cpp
APRNiagaraEffect::APRNiagaraEffect()
{
NiagaraEffect = CreateDefaultSubobject<UNiagaraComponent>(TEXT("NiagaraEffect"));
SetRootComponent(NiagaraEffect);
}
void APRNiagaraEffect::ActivateEffect(bool bReset)
{
Super::ActivateEffect();
if(IsValid(NiagaraEffect))
{
NiagaraEffect->Activate(bReset);
}
}
void APRNiagaraEffect::DeactivateEffect()
{
Super::DeactivateEffect();
if(IsValid(NiagaraEffect))
{
NiagaraEffect->Deactivate();
}
}
// PRParticleEffect.cpp
APRParticleEffect::APRParticleEffect()
{
ParticleEffect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleEffect"));
SetRootComponent(ParticleEffect);
}
void APRParticleEffect::ActivateEffect(bool bReset)
{
Super::ActivateEffect();
if(IsValid(ParticleEffect))
{
ParticleEffect->Activate(bReset);
}
}
void APRParticleEffect::DeactivateEffect()
{
Super::DeactivateEffect();
if(IsValid(ParticleEffect))
{
ParticleEffect->Deactivate();
}
}
EffectSystem
이펙트 액터 클래스를 ObjectPool 패턴으로 관리하는, BaseObjectPoolSystem을 상속하는 ActorComponent 클래스입니다.
Effect 관리
EffectPool은 각 이펙트의 종류별로 구분된 2차원 컨테이너 형식으로 구현했습니다. 각 이펙트의 종류에 따라 이펙트의 포인터 값을 Key로, 해당 이펙트 액터들을 담은 TArray를 Value로 하는 TMap을 사용하여 관리합니다. C++에서 TMap의 Key나 Value로 TArray를 직접 사용할 수 없기 때문에, 구조체를 만들어 TArray를 포함한 해당 구조체를 Value로 사용했습니다. 구현한 구조체는 다음과 같습니다.
/**
* NiagaraEffect를 보관하는 Pool을 나타내는 구조체입니다.
*/
USTRUCT(Atomic, BlueprintType)
struct FPRNiagaraEffectPool
{
GENERATED_BODY()
public:
FPRNiagaraEffectPool()
: PooledEffects()
{}
FPRNiagaraEffectPool(const TArray<TObjectPtr<APRNiagaraEffect>>& NewPooledEffects)
: PooledEffects(NewPooledEffects)
{}
public:
/** Pool에 보관된 NiagaraEffect들의 Array입니다. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRNiagaraSystemPool")
TArray<TObjectPtr<APRNiagaraEffect>> PooledEffects;
};
/**
* 여러 NiagaraEffect Pool을 보관하는 구조체입니다.
*/
USTRUCT(Atomic, BlueprintType)
struct FPRNiagaraEffectObjectPool
{
GENERATED_BODY()
public:
FPRNiagaraEffectObjectPool()
: Pool()
{}
FPRNiagaraEffectObjectPool(const TMap<TObjectPtr<UNiagaraSystem>, FPRNiagaraEffectPool>& NewPool)
: Pool(NewPool)
{}
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRNiagaraEffectObjectPool")
TMap<TObjectPtr<UNiagaraSystem>, FPRNiagaraEffectPool> Pool;
};
EffectPool 초기화
초기화 함수는 ObjectPoolSystem의 InitializeObjectPool 함수를 오버라이드하여 구현했습니다. 이 함수는 각 이펙트에 해당하는 EffectPool을 초기화하는 함수를 호출합니다.
// PREffectPoolSystemComponent.cpp
void UPREffectSystemComponent::InitializeObjectPool()
{
InitializeNiagaraPool();
InitializeParticlePool();
}
void UPREffectSystemComponent::InitializeNiagaraPool()
{
ClearAllNiagaraPool();
// NiagaraSystemPoolSettings 데이터 테이블을 기반으로 NiagaraSystemObjectPool을 생성합니다.
if(NiagaraPoolSettingsDataTable)
{
TArray<FName> RowNames = NiagaraPoolSettingsDataTable->GetRowNames();
for(const FName& RowName : RowNames)
{
FPRNiagaraEffectPoolSettings* NiagaraSettings = NiagaraPoolSettingsDataTable->FindRow<FPRNiagaraEffectPoolSettings>(RowName, FString(""));
if(NiagaraSettings)
{
CreateNiagaraPool(*NiagaraSettings);
}
}
}
}
초기화 전, 기존의 EffectPool을 모두 제거합니다. 이후, DataTable의 정보를 기반으로 새로운 EffectPool을 생성합니다. DataTable의 행 구조를 구성하는 구조체는 다음과 같습니다.
/**
* NiagaraEffectPool의 설정 값을 나타내는 구조체입니다.
*/
USTRUCT(Atomic, BlueprintType)
struct FPRNiagaraEffectPoolSettings : public FTableRowBase
{
GENERATED_BODY()
public:
FPRNiagaraEffectPoolSettings()
: NiagaraSystem(nullptr)
, PoolSize(0)
, EffectLifespan(0.0f)
{}
FPRNiagaraEffectPoolSettings(TObjectPtr<UNiagaraSystem> NewNiagaraSystem, int32 NewPoolSize, float NewEffectLifespan)
: NiagaraSystem(NewNiagaraSystem)
, PoolSize(NewPoolSize)
, EffectLifespan(NewEffectLifespan)
{}
public:
/** Pool에 넣을 NiagaraSystem입니다. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRNiagaraEffectPoolSettings")
TObjectPtr<UNiagaraSystem> NiagaraSystem;
/** Pool의 크기입니다. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRNiagaraEffectPoolSettings")
int32 PoolSize;
/** 이펙트의 수명입니다. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRNiagaraEffectPoolSettings")
float EffectLifespan;
public:
/**
* 주어진 NiagaraEffectPoolSettings와 같은지 확인하는 ==연산자 오버로딩입니다.
*
* @param TargetNiagaraEffectPoolSettings 비교하는 NiagaraEffectPoolSettings와 같은지 확인할 NiagaraEffectPoolSettings입니다.
* @return 주어진 NiagaraEffectPoolSettings와 같을 경우 true를 반환합니다. 그렇지 않을 경우 false를 반환합니다.
*/
FORCEINLINE bool operator==(const FPRNiagaraEffectPoolSettings& TargetNiagaraEffectPoolSettings) const
{
return this->NiagaraSystem == TargetNiagaraEffectPoolSettings.NiagaraSystem
&& this->PoolSize == TargetNiagaraEffectPoolSettings.PoolSize
&& this->EffectLifespan == TargetNiagaraEffectPoolSettings.EffectLifespan;
}
/**
* 주어진 NiagaraEffectPoolSettings와 같지 않은지 확인하는 !=연산자 오버로딩입니다.
*
* @param TargetNiagaraEffectPoolSettings 비교하는 NiagaraEffectPoolSettings와 같지 않은지 확인할 NiagaraEffectPoolSettings입니다.
* @return 주어진 NiagaraEffectPoolSettings와 같지 않을 경우 true를 반환합니다. 그렇지 않을 경우 false를 반환합니다.
*/
FORCEINLINE bool operator!=(const FPRNiagaraEffectPoolSettings& TargetNiagaraEffectPoolSettings) const
{
return this->NiagaraSystem != TargetNiagaraEffectPoolSettings.NiagaraSystem
|| this->PoolSize != TargetNiagaraEffectPoolSettings.PoolSize
|| this->EffectLifespan != TargetNiagaraEffectPoolSettings.EffectLifespan;
}
};
구조체는 생성할 이펙트, Pool의 크기, 이펙트의 수명으로 구성되며, 구조체 간 비교르 위한 연산자 오버로딩도 구현했습니다.
EffectPool 제거
제거 함수는 ObjectPoolSystem의 ClearAllObjectPool 함수를 오버라이드하여 구현하였습니다. 이 함수는 각 이펙트에 해당하는 EffectPool을 제거하는 함수를 호출합니다.
// PREffectSystemComponent.cpp
void UPREffectSystemComponent::ClearAllObjectPool()
{
ClearAllNiagaraPool();
ClearAllParticlePool();
}
void UPREffectSystemComponent::ClearAllNiagaraPool()
{
ActivateNiagaraIndexList.List.Empty();
UsedNiagaraIndexList.List.Empty();
ClearDynamicDestroyNiagaraList(DynamicDestroyNiagaraList);
ClearNiagaraPool(NiagaraPool);
}
활성화된 이펙트의 Index 목록과 사용된 이펙트의 Index 목록을 제거하고, 동적으로 생성된 이펙트르 제거하기 위한 TimerHandle 목록도 제거합니다. 이후, EffectPool을 순회하며 존재하는 이펙트 액터를 제거합니다.
EffectPool 생성
EffectPool은 이펙트 액터의 설정 값인 EffectPoolSettings 구조체를 인자로 받아 생성하게 됩니다. 주어진 설정값을 기반으로 여러개의 이펙트 액터를 생성하여 풀에 보관합니다.
// PREffectSystem.h
private:
/**
* 주어진 NiagaraPool의 설정 값을 바탕으로 NiagaraPool을 생성하는 함수입니다.
*
* @param NiagaraPoolSettings NiagaraPool을 생성할 설정 값입니다.
*/
UFUNCTION(BlueprintCallable, Category = "PRObjectPoolSystem|NiagaraSystem")
void CreateNiagaraPool(const FPRNiagaraEffectPoolSettings& NiagaraPoolSettings);
/**
* 주어진 NiagaraSystem을 월드에 PRNiagaraEffect로 Spawn하는 함수입니다.
*
* @param NiagaraSystem 월드에 Spawn할 NiagaraSystem입니다.
* @param PoolIndex 월드에 Spawn한 NiagaraSystem이 NiagaraPool에서 사용하는 Index 값입니다.
* @param Lifespan PRNiagaraEffect의 수명입니다.
* @return 월드에 Spawn PRNiagaraEffect입니다.
*/
UFUNCTION(BlueprintCallable, Category = "PRObjectPoolSystem|NiagaraSystem")
APRNiagaraEffect* SpawnNiagaraEffectInWorld(UNiagaraSystem* NiagaraSystem, int32 PoolIndex, float Lifespan);
// PREffectSystem.cpp
void UPREffectSystemComponent::CreateNiagaraPool(const FPRNiagaraEffectPoolSettings& NiagaraPoolSettings)
{
if(GetWorld() && NiagaraPoolSettings.NiagaraSystem)
{
FPRNiagaraEffectPool NewNiagaraEffectPool;
// PoolSize만큼 NiagaraEffect를 월드에 Spawn한 후 NewNiagaraEffectPool에 보관합니다.
for(int32 Index = 0; Index < NiagaraPoolSettings.PoolSize; Index++)
{
APRNiagaraEffect* SpawnNiagaraEffect = SpawnNiagaraEffectInWorld(NiagaraPoolSettings.NiagaraSystem, Index, NiagaraPoolSettings.EffectLifespan);
if(IsValid(SpawnNiagaraEffect))
{
NewNiagaraEffectPool.PooledEffects.Emplace(SpawnNiagaraEffect);
}
}
// 초기화된 NewNiagaraEffectPool를 NiagaraPool에 추가하고 ActivateNiagaraIndexList를 생성합니다.
NiagaraPool.Pool.Emplace(NiagaraPoolSettings.NiagaraSystem, NewNiagaraEffectPool);
}
}
APRNiagaraEffect* UPREffectSystemComponent::SpawnNiagaraEffectInWorld(UNiagaraSystem* NiagaraSystem, int32 PoolIndex, float Lifespan)
{
if(!GetWorld() || !NiagaraSystem || !GetPROwner())
{
return nullptr;
}
// NiagaraEffect를 생성합니다.
APRNiagaraEffect* NiagaraEffect = GetWorld()->SpawnActor<APRNiagaraEffect>(APRNiagaraEffect::StaticClass());
if(!IsValid(NiagaraEffect))
{
// NiagaraEffect 생성에 실패하면 함수를 종료하고 nullptr을 반환합니다.
return nullptr;
}
// NiagaraEffect를 초기화합니다.
NiagaraEffect->InitializeNiagaraEffect(NiagaraSystem, GetPROwner(), PoolIndex, Lifespan);
// NiagaraEffect의 OnEffectDeactivateDelegate 이벤트에 대한 콜백 함수를 바인딩합니다.
NiagaraEffect->OnEffectDeactivateDelegate.AddDynamic(this, &UPREffectSystemComponent::OnNiagaraEffectDeactivate);
return NiagaraEffect;
}
지정한 위치에 이펙트 생성 / 특정 Component에 이펙트 부착
// PREffectSystem.h
/**
* NiagaraEffect를 지정한 위치에 Spawn하는 함수입니다.
*
* @param SpawnEffect Spawn할 NiagaraEffect
* @param Location NiagaraEffect를 생성할 위치
* @param Rotation NiagaraEffect에 적용한 회전 값
* @param Scale NiagaraEffect에 적용할 크기
* @param bEffectAutoActivate true일 경우 NiagaraEffect를 Spawn하자마다 NiagaraEffect를 실행합니다. false일 경우 NiagaraEffect를 실행하지 않습니다.
* @param bReset 처음부터 다시 재생할지 여부
* @return 지정한 위치에 Spawn한 NiagaraEffect입니다.
*/
UFUNCTION(BlueprintCallable, Category = "PREffectSystem|NiagaraEffect")
APRNiagaraEffect* SpawnNiagaraEffectAtLocation(UNiagaraSystem* SpawnEffect, FVector Location, FRotator Rotation = FRotator::ZeroRotator, FVector Scale = FVector(1.0f), bool bEffectAutoActivate = true, bool bReset = false);
/**
* NiagaraEffect를 지정한 Component에 부착하여 Spawn하는 함수입니다.
*
* @param SpawnEffect Spawn할 NiagaraEffect
* @param Parent NiagaraEffect를 부착할 Component
* @param AttachSocketName 부착할 소켓의 이름
* @param Location NiagaraEffect를 생성할 위치
* @param Rotation NiagaraEffect에 적용한 회전 값
* @param Scale NiagaraEffect에 적용할 크기
* @param bEffectAutoActivate true일 경우 NiagaraEffect를 Spawn하자마다 NiagaraEffect를 실행합니다. false일 경우 NiagaraEffect를 실행하지 않습니다.
* @param bReset 처음부터 다시 재생할지 여부
* @return 지정한 Component에 부착하여 Spawn한 NiagaraEffect입니다.
*/
UFUNCTION(BlueprintCallable, Category = "PREffectSystem|NiagaraEffect")
APRNiagaraEffect* SpawnNiagaraEffectAttached(UNiagaraSystem* SpawnEffect, USceneComponent* Parent, FName AttachSocketName, FVector Location, FRotator Rotation, FVector Scale = FVector(1.0f), bool bEffectAutoActivate = true, bool bReset = false);
// PREffectSystem.cpp
APRNiagaraEffect* UPREffectSystemComponent::SpawnNiagaraEffectAtLocation(UNiagaraSystem* SpawnEffect, FVector Location, FRotator Rotation, FVector Scale, bool bEffectAutoActivate, bool bReset)
{
APRNiagaraEffect* ActivateableNiagaraEffect = InitializeNiagaraEffect(SpawnEffect);
if(IsValid(ActivateableNiagaraEffect))
{
// NiagaraEffect를 활성화하고 Spawn할 위치와 회전값, 크기, 자동실행 여부를 적용합니다.
ActivateableNiagaraEffect->SpawnEffectAtLocation(Location, Rotation, Scale, bEffectAutoActivate, bReset);
return ActivateableNiagaraEffect;
}
return nullptr;
}
APRNiagaraEffect* UPREffectSystemComponent::SpawnNiagaraEffectAttached(UNiagaraSystem* SpawnEffect, USceneComponent* Parent, FName AttachSocketName, FVector Location, FRotator Rotation, FVector Scale, bool bEffectAutoActivate, bool bReset)
{
APRNiagaraEffect* ActivateableNiagaraEffect = InitializeNiagaraEffect(SpawnEffect);
if(IsValid(ActivateableNiagaraEffect))
{
// NiagaraEffect를 활성화하고 Spawn하여 부착할 Component와 위치, 회전값, 크기, 자동실행 여부를 적용합니다.
ActivateableNiagaraEffect->SpawnEffectAttached(Parent, AttachSocketName, Location, Rotation, Scale, EAttachLocation::KeepWorldPosition, bEffectAutoActivate, bReset);
return ActivateableNiagaraEffect;
}
return nullptr;
}
월드에 Spawn한 이펙트 액터를 지정한 위치 혹은 특정 Component에 부착을 하기전 지정한 이펙트에 해당하는 이펙트 액터를 초기화합니다.
Effect 초기화
활성화 가능한 이펙트 액터를 가져옵니다. 만약 활성화 가능한 이펙트 액터가 동적으로 생성된 경우, 이를 제거하기 위한 타이머가 작동 중이라면 타이머를 정지합니다. 그런 다음 이펙트 액터의 Index를 ActivateIndexList에 추가합니다.
// PREffectSystem.cpp
APRNiagaraEffect* UPREffectSystemComponent::InitializeNiagaraEffect(UNiagaraSystem* SpawnEffect)
{
APRNiagaraEffect* ActivateableNiagaraEffect = GetActivateableNiagaraEffect(SpawnEffect);
// 유효하지 않는 NiagaraEffect이거나 풀링 가능한 객체가 아니면 nullptr를 반환합니다.
if(!IsValid(ActivateableNiagaraEffect) || !IsPoolableObject(ActivateableNiagaraEffect))
{
return nullptr;
}
// 동적으로 생성한 NiagaraEffect일 경우 DynamicObjectDestroyTimer가 작동 중이라면 DynamicObjectDestroyTimer를 정지합니다.
FTimerHandle* DynamicObjectDestroyTimer = DynamicDestroyNiagaraList.FindTimerHandleForNiagaraEffect(*ActivateableNiagaraEffect);
if(DynamicObjectDestroyTimer)
{
GetWorld()->GetTimerManager().ClearTimer(*DynamicObjectDestroyTimer);
}
// 해당 NiagaraSystem를 처음 활성화하는 경우 ActivateNiagaraEffectIndexList를 생성합니다.
if(!IsCreateActivateNiagaraIndexList(SpawnEffect))
{
CreateActivateNiagaraIndexList(SpawnEffect);
}
// 활성화된 NiagaraEffect의 Index를 ActivateNiagaraIndexList에 저장합니다.
const int32 PoolIndex = GetPoolIndex(ActivateableNiagaraEffect);
ActivateNiagaraIndexList.GetIndexesForNiagaraSystem(*SpawnEffect)->Add(PoolIndex);
return ActivateableNiagaraEffect;
}
활성화 가능한 Effect 반환
주어진 NiagaraSystem에 해당하는 Pool이 생성되었는지 확인합니다. 생성되지 않았다면, nullptr을 반환합니다. 생성된 Pool에서 해당 NigaraSystem의 Pool 내에서 활성화되지 않은 이펙트 액터를 찾으면 이를 반환합니다. Pool 내 모든 이펙트 액터가 활성화된 경우, 새로운 이펙트 액터를 동적으로 생성하여 반환합니다.
// PREffectSystem.cpp
APRNiagaraEffect* UPREffectSystemComponent::GetActivateableNiagaraEffect(UNiagaraSystem* NiagaraSystem)
{
// NiagaraSystem이 유효하지 않을 경우 nullptr을 반환합니다.
if(!NiagaraSystem)
{
return nullptr;
}
// 해당 NiagaraSystem에 해당하는 Pool이 생성되었는지 확인합니다.
if(!IsCreateNiagaraPool(NiagaraSystem))
{
// 지정된 NiagaraSystem에 해당하는 Pool이 없으면 nullptr을 반환합니다.
return nullptr;
}
// NiagaraPool에서 해당 NiagaraSystem의 Pool을 얻습니다.
FPRNiagaraEffectPool* PoolEntry = NiagaraPool.Pool.Find(NiagaraSystem);
if(!PoolEntry)
{
// 지정된 NiagaraSystem가 없으면 nullptr을 반환합니다.
return nullptr;
}
// 활성화할 NiagaraEffect입니다.
APRNiagaraEffect* ActivateableNiagaraEffect = nullptr;
// PoolEntry에서 활성화되지 않은 NiagaraEffect를 찾습니다.
for(const auto& NiagaraEffect : PoolEntry->PooledEffects)
{
if(!IsActivateNiagaraEffect(NiagaraEffect))
{
ActivateableNiagaraEffect = NiagaraEffect;
break;
}
}
// PoolEntry의 모든 NiagaraEffect가 활성화되었을 경우 새로운 NiagaraEffect를 생성합니다.
if(!ActivateableNiagaraEffect)
{
ActivateableNiagaraEffect = SpawnDynamicNiagaraEffectInWorld(NiagaraSystem);
}
return ActivateableNiagaraEffect;
}
Effect 동적 생성
// PREffectSystem.cpp
APRNiagaraEffect* UPREffectSystemComponent::SpawnDynamicNiagaraEffectInWorld(UNiagaraSystem* NiagaraSystem)
{
if(!NiagaraSystem)
{
return nullptr;
}
APRNiagaraEffect* DynamicNiagaraEffect = nullptr;
// Critical Section 시작
FCriticalSection CriticalSection;
CriticalSection.Lock();
// 해당 NiagaraSystem의 UsedNiagaraIndexList가 생성되었는지 확인하고, 없으면 생성합니다.
if(!IsCreateUsedNiagaraIndexList(NiagaraSystem))
{
CreateUsedNiagaraIndexList(NiagaraSystem);
}
// UsedNiagaraIndexList에서 해당 NiagaraSystem의 UsedIndexList를 얻습니다.
FPRUsedIndexList* UsedIndexList = UsedNiagaraIndexList.List.Find(NiagaraSystem);
if(UsedIndexList == nullptr)
{
// 지정된 NiagaraSystem이 없습니다.
return nullptr;
}
// 사용 가능한 Index를 구합니다.
const int32 NewIndex = FindAvailableIndex(UsedIndexList->Indexes);
// 사용 가능한 Index를 UsedIndexList에 추가합니다.
UsedIndexList->Indexes.Add(NewIndex);
// Critical Section 끝
CriticalSection.Unlock();
// 새로운 NiagaraEffect를 생성하고 초기화합니다.
const FPRNiagaraEffectPoolSettings NiagaraEffectSettings = GetNiagaraEffectPoolSettingsFromDataTable(NiagaraSystem);
if(NiagaraEffectSettings != FPRNiagaraEffectPoolSettings())
{
// 데이터 테이블에 NiagaraEffect의 설정 값을 가지고 있을 경우 설정 값의 Lifespan을 적용합니다.
DynamicNiagaraEffect = SpawnNiagaraEffectInWorld(NiagaraSystem, NewIndex, NiagaraEffectSettings.EffectLifespan);
}
else
{
// 데이터 테이블에 NiagaraEffect의 설정 값을 가지고 있지 않을 경우 DynamicLifespan을 적용합니다.
DynamicNiagaraEffect = SpawnNiagaraEffectInWorld(NiagaraSystem, NewIndex, DynamicLifespan);
}
// OnDynamicNiagaraEffectDeactivate 함수를 바인딩합니다.
DynamicNiagaraEffect->OnEffectDeactivateDelegate.AddDynamic(this, &UPREffectSystemComponent::OnDynamicNiagaraEffectDeactivate);
// NiagaraPool에서 해당 NiagaraSystem의 Pool을 얻습니다.
FPRNiagaraEffectPool* PoolEntry = NiagaraPool.Pool.Find(NiagaraSystem);
if(!PoolEntry)
{
// Pool이 없을 경우 생성한 NiagaraEffect를 제거하고 nullptr을 반환합니다.
DynamicNiagaraEffect->ConditionalBeginDestroy();
return nullptr;
}
// 새로 생성한 NiagaraEffect를 PoolEntry에 추가합니다.
PoolEntry->PooledEffects.Emplace(DynamicNiagaraEffect);
return DynamicNiagaraEffect;
}
이펙트 액터를 동적으로 생성할 때 FCirticalSection을 사용하여 다중 그레드 환경에서의 안정성을 보장합니다. 이는 이펙트 액터를 생성하고 Index를 부여하는 과정에서 문제가 발생하지 않도록 하기 위함입니다. 크리티컬 섹션 내부에서 이펙트 액터에 Index를 부여한 후, 크리티컬 섹션을 종료합니다.
그 후, 주어진 이펙트에 해당하는 설정 값을 DataTable에서 가져와 이를 기반으로 이펙트 액터를 생성하고 초기화합니다. DataTable에 설정 값이 존재하면 해당 값을 사용하고, 존재하지 않으면 기본적으로 DynamicLifespan 값을 사용합니다.
새로 생성된 이펙트 액터의 OnEffectDeativateDelegate 델리게이트에 OnDynamicEffectDeactivate 함수를 바인딩합니다. 이 함수는 이펙트 액터가 비활성화된 후 타이머를 실행하여 일정 시간이 지난 후 이펙트 액터를 월드에서 제거하는 역할을 합니다.
그 다음, EffectPool에서 해당 이펙트 액터의 Pool을 찾습니다. 만약 Pool 존재하지 않을 경우, 생성된 이펙트 액터를 제거하고 nullptr을 반환합니다. 이는 모든 이펙트 액터는 DataTable의 설정 값에 의해 생성되기 때문에, DataTable에 없는 이펙트 액터는 생성하지 않기 위해서 입니다.
Effect 비활성화
이펙트 액터가 비활성화되면 OnEffectDeactivateDelegate 델리게이트가 실행됩니다. 이 델리게이트는 이펙트 액터를 월드에 생성할 때 바인딩된 OnEffectDeactivate 함수를 호출합니다.
OnEffectDeactivate 함수는 EffectSystem의 활성화된 이펙트 액터의 Index를 보관하는 ActivateIndexList에서 비활성화된 이펙트 액터의 Index를 제거합니다.
// PREffect.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEffectDeactivate, APREffect*, Effect);
public:
/** 이펙트가 비활성화될 때 실행하는 델리게이트입니다. */
FOnEffectDeactivate OnEffectDeactivateDelegate;
// PREffect.cpp
void APREffect::DeactivateEffect()
{
bActivate = false;
SetActorHiddenInGame(!bActivate);
// 이펙트에 설정된 모든 타이머를 초기화합니다.
GetWorldTimerManager().ClearAllTimersForObject(this);
// 비활성화 델리게이트를 호출합니다.
OnEffectDeactivateDelegate.Broadcast(this);
}
// EffectSystem.cpp
void UPREffectSystemComponent::OnNiagaraEffectDeactivate(APREffect* TargetEffect)
{
APRNiagaraEffect* TargetNiagaraEffect = Cast<APRNiagaraEffect>(TargetEffect);
// 유효하지 않는 NiagaraEffect이거나 풀링 가능한 NiagaraEffect가 아니거나 NiagaraComponent가 없으면 반환합니다.
if(!IsValid(TargetNiagaraEffect)
|| !IsPoolableObject(TargetNiagaraEffect)
|| !TargetNiagaraEffect->GetNiagaraEffect())
{
return;
}
// TargetNiagaraEffect가 활성화된 상태라면 비활성화합니다.
if(IsActivateNiagaraEffect(TargetNiagaraEffect))
{
FPRActivateIndexList* ActivateIndexList = ActivateNiagaraIndexList.List.Find(TargetNiagaraEffect->GetNiagaraEffectAsset());
if(ActivateIndexList)
{
// 비활성화된 TargetNiagaraEffect의 Index를 제거합니다.
ActivateIndexList->Indexes.Remove(GetPoolIndex(TargetNiagaraEffect));
}
}
}
void UPREffectSystemComponent::OnDynamicNiagaraEffectDeactivate(APREffect* TargetEffect)
{
APRNiagaraEffect* TargetNiagaraEffect = Cast<APRNiagaraEffect>(TargetEffect);
// 유효하지 않는 NiagaraEffect이거나 풀링 가능한 NiagaraEffect가 아니거나 NiagaraComponent가 없으면 반환합니다.
if(!IsValid(TargetNiagaraEffect)
|| !IsPoolableObject(TargetNiagaraEffect)
|| !TargetNiagaraEffect->GetNiagaraEffect())
{
return;
}
if(DynamicLifespan > 0.0f)
{
// 동적 수명이 끝난 후 NiagaraEffect를 제거하도록 타이머를 설정합니다.
FTimerHandle DynamicLifespanTimerHandle;
FTimerDelegate DynamicLifespanDelegate = FTimerDelegate::CreateUObject(this, &UPREffectSystemComponent::OnDynamicNiagaraEffectDestroy, TargetNiagaraEffect);
GetWorld()->GetTimerManager().SetTimer(DynamicLifespanTimerHandle, DynamicLifespanDelegate, DynamicLifespan, false);
// TimerHandle을 추가합니다.
FPRDynamicDestroyObject* DynamicDestroyObject = DynamicDestroyNiagaraList.List.Find(TargetNiagaraEffect->GetNiagaraEffectAsset());
if(DynamicDestroyObject)
{
DynamicDestroyObject->TimerHandles.Emplace(TargetNiagaraEffect, DynamicLifespanTimerHandle);
}
else
{
FPRDynamicDestroyObject NewDynamicDestroyObject;
NewDynamicDestroyObject.TimerHandles.Emplace(TargetNiagaraEffect, DynamicLifespanTimerHandle);
DynamicDestroyNiagaraList.List.Emplace(TargetNiagaraEffect->GetNiagaraEffectAsset(), NewDynamicDestroyObject);
}
}
else
{
// 동적 수명이 없을 경우 타이머를 실행하지 않고 바로 NiagaraEffect를 제거합니다.
OnDynamicNiagaraEffectDestroy(TargetNiagaraEffect);
}
}
void UPREffectSystemComponent::OnDynamicNiagaraEffectDestroy(APRNiagaraEffect* TargetNiagaraEffect)
{
// DynamicObjectDestroyTimer를 제거합니다.
FPRDynamicDestroyObject* DynamicDestroyObject = DynamicDestroyNiagaraList.List.Find(TargetNiagaraEffect->GetNiagaraEffectAsset());
if(DynamicDestroyObject)
{
FTimerHandle* TimerHandle = DynamicDestroyObject->TimerHandles.Find(TargetNiagaraEffect);
GetWorld()->GetTimerManager().ClearTimer(*TimerHandle);
DynamicDestroyObject->TimerHandles.Remove(TargetNiagaraEffect);
}
// NiagaraSystem의 UsedObjectIndex를 얻습니다.
FPRUsedIndexList* UsedIndexList = UsedNiagaraIndexList.List.Find(TargetNiagaraEffect->GetNiagaraEffectAsset());
if(UsedIndexList)
{
// 사용 중인 Index를 제거합니다.
UsedIndexList->Indexes.Remove(GetPoolIndex(TargetNiagaraEffect));
}
// NiagaraSystem의 Pool이 생성되었는지 확인합니다.
if(IsCreateNiagaraPool(TargetNiagaraEffect->GetNiagaraEffectAsset()))
{
// NiagaraPool에서 NiagaraEffect를 제거합니다.
FPRNiagaraEffectPool* PoolEntry = NiagaraPool.Pool.Find(TargetNiagaraEffect->GetNiagaraEffectAsset());
if(PoolEntry)
{
PoolEntry->PooledEffects.Remove(TargetNiagaraEffect);
}
}
TargetNiagaraEffect->ConditionalBeginDestroy();
}
동적으로 생성된 이펙트 액터의 경우, OnEffectDeactivateDelegate 델리게이트에 추가로 바인딩된 OnDynamicEffectDeactivate 함수도 호출됩니다. OnDynamicEffectDeactivate 함수는 다음 작업을 수행합니다.
DynamicLifespan이 0보다 큰 경우
- 동적 수명이 끝난 후 이펙트 액터를 제거하도록 타이머를 설정합니다.
- 타이머 델리게이트를 생성하고 타이머를 설정한 후, DynamicDestroyList에 이펙트 액터와 타이머 핸들을 추가합니다.
- 타이머가 만료되면 OnDynamicEffectDestroy 함수를 호출합니다.
DynamicLifespan이 0 이하인 경우 타이머를 사용하지 않고 즉시 OnDynamicEffectDestroy 함수를 호출하여 이펙트 액터를 제거합니다.
OnDynamicEffectDestroy 함수는 다음 작업을 수행합니다.
- 주어진 이펙트 액터의 타이머 핸들을 제거하고 타이머를 클리어합니다.
- UsedIndexList에서 해당 이펙트 액터의 Index를 제거합니다.
- 해당 이펙트 액터를 EffectPool에서 제거합니다.
- 이펙트 액터를 월드에서 제거합니다.
'Project > Replica' 카테고리의 다른 글
[Project Replica] PoolableInterface / BaseObjectPoolSystem (0) | 2024.06.25 |
---|---|
[Project Replica] EffectSystem (0) | 2024.04.30 |
[Project Replica] Vaulting / Vault / 장애물 뛰어넘기 (0) | 2024.04.15 |
[Project Replica] DamageSystem 대미지 시스템 (0) | 2024.01.11 |
[Project Replica] AI 생성 시스템 AISpawnSystem (1) | 2023.12.04 |