Project/Replica

[Project Replica] PoolableInterface / BaseObjectPoolSystem

한돌이 2024. 6. 25. 17:45

https://github.com/onestone3647/Portfolio_ProjectReplica

 

GitHub - onestone3647/Portfolio_ProjectReplica: Seo Won Seok Protfolio

Seo Won Seok Protfolio. Contribute to onestone3647/Portfolio_ProjectReplica development by creating an account on GitHub.

github.com

 

 ObjectPool로 관리하는 오브젝트들을 보다 효율적으로 관리하기 위해 PoolableInterface 인터페이스 클래스를 도입했습니다. 이를 통해 오브젝트 관리를 보다 체계적으로 할 수 있게 되었습니다.

 

기능 설명

  •  BaseObjectPoolSystem 또는 BaseObjectPoolSystem을 상속한 ActorComponent 클래스는 PoolableInterface 인터페이스를 상속하는 오브젝트를 ObjectPool 패턴으로 관리합니다.
  • DataTable의 정보를 기반으로 오브젝트 풀의 제거 및 초기화를 수행합니다.
  • 보관하는 오브젝트의 활성화 및 비활성화를 관리합니다.
  • 동적으로 생성한 오브젝트의 Index와 수명을 관리합니다.

 

PoolableInterface

 BaseObjectPoolSystem 또는 BaseObjectPoolSystem을 상속한 ActorComponent 클래스에서 관리하는 오브젝트들은 PoolableInterface 인터페이스 클래스를 상속하여야 합니다. PoolableInterface는 BaseObjectPoolSystem에서 관리할 수 있는 오브젝트들이 따라야 하는 인터페이스를 정의합니다. PoolableInterface는 다음과 같은 역할을 합니다.

 

인터페이스 정의 및 함수 선언

 모든 함수는 블루프린트에서 호출 및 오버라이딩을 할 수 있도록 UFUNCTION의 BlueprintCallable, BlueprintNativeEvent를 사용하였습니다.

블루프린트에서 인터페이스 함수 호출과 인터페이스 함수의 노드
블루프린트에서 인터페이스 함수의 오버라이딩과 인터페이스 이벤트 노드

 

활성화 여부 확인

	/** 활성화 되었는지 확인하는 함수입니다. */
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Poolable")
	bool IsActivate() const;
	virtual bool IsActivate_Implementation() const = 0;

 

활성화 및 비활성화

	/** 활성화하는 함수입니다. */
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Poolable")
	void Activate();
	virtual void Activate_Implementation() = 0;
    
    	/** 비활성화하는 함수입니다. */
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Poolable")
	void Deactivate();
	virtual void Deactivate_Implementation() = 0;

 

Pool Index

	/** PoolIndex를 반환하는 함수입니다. */
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Poolable")
	int32 GetPoolIndex() const;
	virtual int32 GetPoolIndex_Implementation() const = 0;

 

수명 관리

	/** 수명을 반환하는 함수입니다. */
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Poolable")
	float GetLifespan() const;
	virtual float GetLifespan_Implementation() const = 0;

	/**
	 * 수명을 설정하는 함수입니다.
	 * 
	 * @param NewLifespan 설정할 수명입니다.
	 */
	UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Poolable")
	void SetLifespan(float NewLifespan);
	virtual void SetLifespan_Implementation(float NewLifespan) = 0;

 

BaseObjectPoolSystem

 오브젝트 풀링 시스템을 관리하는 기본 ActorComponent 클래스입니다. 이 클래스는 오브젝트 풀링을 효율적으로 관리하기 위해 설계되었으며, 오브젝트의 활성화, 비활성화, 수명 관리, 그리고 동적 오브젝트 제거 등의 기능을 구현했습니다.

 

주요 변수

// BaseObjectPoolSystem.h

protected:
	/**
	 * 동적으로 생성한 오브젝트의 수명입니다.
	 * 동적으로 생성한 오브젝트가 비활성화 되었을 때, 해당 시간이 지난 후 오브젝트를 제거합니다. 
	 */ 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRBaseObjectPoolSystem")
	float DynamicLifespan;

	/** 동적으로 생성하는 ObjectPool의 PoolSize입니다. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRBaseObjectPoolSystem", meta = (ClampMin = "1"))
	int32 DynamicPoolSize;

 

관련 구조체

// CommonStruct.h

/**
 * 활성화된 Index를 보관하는 구조체입니다.
 */
USTRUCT(Atomic, BlueprintType)
struct FPRActivateIndexList
{
	GENERATED_BODY()

public:
	/** 활성화된 인덱스를 보관하는 Set입니다. */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "ActivateIndexList")
	TSet<int32> Indexes;
};

/**
 * 이전에 사용된 Index를 보관하는 구조체입니다. 
 */
USTRUCT(Atomic, BlueprintType)
struct FPRUsedIndexList
{
	GENERATED_BODY()

public:
	/** 이전에 사용된 Index를 보관하는 Set입니다. */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "UsedIndexList")
	TSet<int32> Indexes;
};
// BaseObjectPoolSystem.h

/**
 * 클래스별로 활성화된 오브젝트들의 Index를 보관하는 구조체입니다.
 */
USTRUCT(Atomic, BlueprintType)
struct FPRActivateObjectIndexList
{
	GENERATED_BODY()

public:
	FPRActivateObjectIndexList()
		: List()
	{}

	FPRActivateObjectIndexList(const TMap<TSubclassOf<UObject>, FPRActivateIndexList>& NewList)
		: List(NewList)
	{}

public:
	/** 클래스 레퍼런스와 활성화된 Index를 보관하는 Map입니다. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRAcitvateObjectIndexList")
	TMap<TSubclassOf<UObject>, FPRActivateIndexList> List;

public:
	/**
	 * 주어진 오브젝트에 해당하는 Indexes를 반환하는 함수입니다.
	 *
	 * @param ObjectToFind Indexes를 찾을 오브젝트입니다.
	 * @return Indexes를 찾을 경우 Indexes를 반환합니다. 못찾았을 경우 nullptr을 반환합니다.
	 */
	TSet<int32>* GetIndexesForObject(UObject* ObjectToFind)
	{
		if(!IsValid(ObjectToFind))
		{
			return nullptr;
		}

		TSubclassOf<UObject> ActorClass = ObjectToFind->GetClass();
		FPRActivateIndexList* ActivateIndexList = List.Find(ActorClass);
		if(ActivateIndexList)
		{
			return &ActivateIndexList->Indexes;
		}
		
		return nullptr;
	}
};

/**
 * 클래스별로 이전에 사용된 오브젝트들의 Index를 보관하는 구조체입니다.
 */
USTRUCT(Atomic, BlueprintType)
struct FPRUsedObjectIndexList
{
	GENERATED_BODY()

public:
	FPRUsedObjectIndexList()
		: List()
	{}

	FPRUsedObjectIndexList(const TMap<TSubclassOf<UObject>, FPRUsedIndexList>& NewList)
		: List(NewList)
	{}

public:
	/** 클래스 레퍼런스와 해당 클래스의 이전에 사용된 Index를 보관하는 Map입니다. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRUsedObjectIndexList")
	TMap<TSubclassOf<UObject>, FPRUsedIndexList> List;
};

/**
 * 동적으로 생성한 오브젝트와 해당 오브젝트를 제거하는 TimerHandle을 관리하는 구조체입니다.
 */
USTRUCT(Atomic, BlueprintType)
struct FPRDynamicDestroyObject
{
	GENERATED_BODY()

public:
	FPRDynamicDestroyObject()
		: TimerHandles()
	{}

	FPRDynamicDestroyObject(const TMap<TObjectPtr<UObject>, FTimerHandle>& NewTimerHandles)
		: TimerHandles(NewTimerHandles)
	{}

public:
	/** 오브젝트와 해당 오브젝트를 제거하는 TimerHandle을 보관하는 Map입니다. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRDynamicDestroyObject")
	TMap<TObjectPtr<UObject>, FTimerHandle> TimerHandles;
};

/**
 * 동적으로 생성한 오브젝트 목록을 클래스별로 보관하는 구조체입니다.
 */
USTRUCT(Atomic, BlueprintType)
struct FPRDynamicDestroyObjectList
{
	GENERATED_BODY()

public:
	FPRDynamicDestroyObjectList()
		: List()
	{}

	FPRDynamicDestroyObjectList(const TMap<TSubclassOf<UObject>, FPRDynamicDestroyObject>& NewList)
		: List(NewList)
	{}
	
public:
	/** 클래스 레퍼런스와 동적으로 생성한 오브젝트와 해당 오브젝트를 제거하는 TimerHandle을 보관한 Map입니다. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PRDynamicDestroyObjectPool")
	TMap<TSubclassOf<UObject>, FPRDynamicDestroyObject> List;

public:
	/**
	 * 주어진 오브젝트에 해당하는 TimerHandle을 반환하는 함수입니다.
	 *
	 * @param ObjectToFind TimerHandle을 찾을 오브젝트입니다.
	 * @return TimerHandle을 찾았을 경우 TimerHandle을 반환합니다. 못 찾았을 경우 nullptr을 반환합니다.
	 */
	FTimerHandle* FindTimerHandleForObject(UObject& ObjectToFind)
	{
		if(!IsValid(&ObjectToFind))
		{
			return nullptr;
		}

		FPRDynamicDestroyObject* DestroyObjects = List.Find(ObjectToFind.GetClass());
		if(DestroyObjects)
		{
			FTimerHandle* FoundTimerHandle = DestroyObjects->TimerHandles.Find(ObjectToFind);
			if(FoundTimerHandle)
			{
				return FoundTimerHandle;
			}
		}

		return nullptr;
	}
};

 

주요 역할 및 기능

오브젝트 풀 초기화 및 제거

// BaseObjectPoolSystem.h

public:
	/** 기존의 ObjectPool을 제거하고, 새로 ObjectPool을 생성하여 초기화하는 함수입니다. */
	UFUNCTION(Blueprintable, Category = "PRBaseObjectPoolSystem")
	virtual void InitializeObjectPool();
// BaseObjectPoolSystem.cpp

void UPRBaseObjectPoolSystemComponent::InitializeObjectPool()
{
	// 자식 클래스에서 오버라이딩하여 사용합니다.
}

 InitializeObjectPool 함수는 기존의 오브젝트 풀을 제거하고 새로 오브젝트 풀을 생성하여 초기화하는 함수입니다. BaseObjectPoolSystem을 상속하는 자식 클래스에서 오버라이딩하여 사용합니다.

 

// BaseObjectPoolSystem.h

public:
	/** 모든 ObjectPool을 제거하는 함수입니다. */
	UFUNCTION(Blueprintable, Category = "PRBaseObjectPoolSystem")
	virtual void ClearAllObjectPool();
// BaseObjectPoolSystem.cpp

void UPRBaseObjectPoolSystemComponent::ClearAllObjectPool()
{
	// 자식 클래스에서 오버라이딩하여 사용합니다.
}

 ClearAllObjectPool 함수는 모든 오브젝트 풀을 제거하는 함수입니다. BaseObjectPoolSystem을 상속하는 자식 클래스에서 오버라이딩하여 사용합니다.

 

풀링 가능한 오브젝트 확인

// BaseObjectPoolSystem.h

public:
	/**
	 * 주어진 객체가 유효한 풀링 가능한 객체인지 확인하는 함수입니다.
	 *
	 * @param PoolableObject 확인할 객체입니다.
	 * @return 객체가 유효하고 PRPoolableInterface를 구현하는 경우 true를 반환합니다. 그렇지 않으면 false를 반환합니다.
	 */
	UFUNCTION(BlueprintCallable, Category = "PRBaseObjectPoolSystem")
	bool IsPoolableObject(UObject* PoolableObject) const;

	/**
	 * 주어진 오브젝트의 클래스가 유효한 풀링 가능한 오브젝트 클래스인지 확인하는 함수입니다.
	 *
	 * @param PoolableObjectClass 확인할 오브젝트 클래스입니다.
	 * @return 오브젝트의 클래스가 유효하고 PRPoolableInterface를 구현하는 경우 true를 반환합니다. 그렇지 않으면 false를 반환합니다.
	 */
	UFUNCTION(BlueprintCallable, Category = "PRBaseObjectPoolSystem")
	bool IsPoolableObjectClass(TSubclassOf<UObject> PoolableObjectClass) const;
// BaseObjectPoolSystem.cpp

bool UPRBaseObjectPoolSystemComponent::IsPoolableObject(UObject* PoolableObject) const
{
	return IsValid(PoolableObject) && PoolableObject->GetClass()->ImplementsInterface(UPRPoolableInterface::StaticClass());
}

bool UPRBaseObjectPoolSystemComponent::IsPoolableObjectClass(TSubclassOf<UObject> PoolableObjectClass) const
{
	return IsValid(PoolableObjectClass) && PoolableObjectClass->ImplementsInterface(UPRPoolableInterface::StaticClass());
}

 각 함수는 입력 받은 인자가 유효하고, PoolableInterface를 상속하고 있는지 ImplementsInterface 함수로 확인하여 풀링 가능한지 확인하는 함수입니다.

 

오브젝트 활성화 및 비활성화

// BaseObjectPoolSystem.h

public:
	/**
	 * 주어진 객체를 활성화하는 함수입니다.
	 * 
	 * @param PoolableObject 활성화할 객체입니다.
	 */
	UFUNCTION(BlueprintCallable, Category = "PRBaseObjectPoolSystem")
	void ActivateObject(UObject* PoolableObject);

	/**
	 * 주어진 객체를 비활성화하는 함수입니다.
	 * 
	 * @param PoolableObject 비활성화할 객체입니다.
	 */
	UFUNCTION(BlueprintCallable, Category = "PRBaseObjectPoolSystem")
	void DeactivateObject(UObject* PoolableObject);
// BaseObjectPoolSystem.cpp

void UPRBaseObjectPoolSystemComponent::ActivateObject(UObject* PoolableObject)
{
	if(IsPoolableObject(PoolableObject))
	{
		IPRPoolableInterface::Execute_Activate(PoolableObject);
	}
}

void UPRBaseObjectPoolSystemComponent::DeactivateObject(UObject* PoolableObject)
{
	if(IsPoolableObject(PoolableObject))
	{
		IPRPoolableInterface::Execute_Deactivate(PoolableObject);
	}
}

 주어진 객체를 활성화하거나 비활성화하는 함수입니다. 주어진 객체가 유효한 풀링 가능한 오브젝트인지 확인한 후, 조건이 충족되면 PoolableInterface의 Activate 함수 또는 Deactivate 함수를 실행합니다.

 

오브젝트 속성 관리

// BaseObjectPoolSystem.h

public:
	/**
 	 * 주어진 객체의 PoolIndex를 반환하는 함수입니다.
 	 * 
 	 * @param PoolableObject PoolIndex를 반환할 객체입니다.
 	 * @return 주어진 객체가 유효하고 풀링 가능한 객체일 경우 객체의 PoolIndex를 반환합니다.
 	 */
	UFUNCTION(BlueprintCallable, Category = "PRBaseObjectPoolSystem")
	int32 GetPoolIndex(UObject* PoolableObject) const;

	/**
	  * 주어진 객체의 Lifespan을 반환하는 함수입니다.
	  * 
	  * @param PoolableObject Lifespan을 반환할 객체입니다.
	  * @return 주어진 객체가 유효하고 풀링 가능한 객체일 경우 객체의 Lifespan을 반환합니다.
	  */
	UFUNCTION(BlueprintCallable, Category = "PRBaseObjectPoolSystem")
	float GetLifespan(UObject* PoolableObject) const;

	/**
	  * 주어진 객체의 Lifespan을 설정하는 함수입니다.
	  * 
	  * @param PoolableObject Lifespan을 설정할 객체입니다.
	  */
	UFUNCTION(BlueprintCallable, Category = "PRBaseObjectPoolSystem")
	void SetLifespan(UObject* PoolableObject, float NewLifespan);
// BaseObjectPoolSystem.cpp

float UPRBaseObjectPoolSystemComponent::GetLifespan(UObject* PoolableObject) const
{
	if(IsPoolableObject(PoolableObject))
	{
		return IPRPoolableInterface::Execute_GetLifespan(PoolableObject);
	}
	
	return INDEX_NONE;
}

void UPRBaseObjectPoolSystemComponent::SetLifespan(UObject* PoolableObject, float NewLifespan)
{
	if(IsPoolableObject(PoolableObject))
	{
		IPRPoolableInterface::Execute_SetLifespan(PoolableObject, NewLifespan);
	}
}

int32 UPRBaseObjectPoolSystemComponent::GetPoolIndex(UObject* PoolableObject) const
{
	if(IsPoolableObject(PoolableObject))
	{
		return IPRPoolableInterface::Execute_GetPoolIndex(PoolableObject);
	}

	// 유효하지 않은 Index를 반환합니다.
	return INDEX_NONE;
}

 주어진 객체가 유요한 풀링 가능한 오브젝트인지 확인한 후, 조건이 충족되면 해당 PoolableInterface의 함수를 실행하는 함수들입니다.

 

사용 가능한 Index 찾기

// BaseObjectPoolSystem.h

public:
	/**
	 * 사용 가능한 Index를 찾아 반환하는 함수입니다.
	 * 
	 * @param UsedIndexes 이미 사용 중인 Index 목록입니다.
	 * @return 사용 가능한 Index를 반환합니다.
	 */
	UFUNCTION(BlueprintCallable, Category = "PRBaseObjectPoolSystem")
	int32 FindAvailableIndex(const TSet<int32>& UsedIndexes);
// BaseObjectPoolSystem.cpp

int32 UPRBaseObjectPoolSystemComponent::FindAvailableIndex(const TSet<int32>& UsedIndexes)
{
	int32 NewIndex = 0;
	while(UsedIndexes.Contains(NewIndex))
	{
		NewIndex++;
	}

	return NewIndex;
}

 주어진 Set을 순회하며 사용 가능한 Index를 찾아 반환하는 함수입니다.

 

동적으로 생성한 오브젝트 관리

// BaseObjectPoolSystem.h

protected:
	/**
	 * 동적으로 생성한 오브젝트을 제거하는 함수입니다.
	 * 
	 * @param TargetDynamicDestroyObjectList 제거할 동적으로생성한 오브젝트의 목록입니다.
	 */
	UFUNCTION(Blueprintable, Category = "PRBaseObjectPoolSystem")
	virtual void ClearDynamicDestroyObjectList(FPRDynamicDestroyObjectList& TargetDynamicDestroyObjectList);
    
 protected:
	/**
	 * Template 함수를 사용하여 동적으로 생성한 오브젝트들을 제거하는 함수입니다.
	 *
	 * @tparam KeyType Map의 키 값의 유형입니다. ObjectPoolSystem마다 다를 수 있습니다.
	 * @param List 동적으로 생성한 오브젝트의 목록입니다.
	 */
	template <typename KeyType>
	void ClearDynamicDestroyObjects(TMap<KeyType, FPRDynamicDestroyObject>& List)
	{
		for(auto& ListEntry : List)
		{
			FPRDynamicDestroyObject& DynamicDestroyObject = ListEntry.Value;
			if(&DynamicDestroyObject)
			{
				// DynamicDestroyObject의 모든 타이머를 해제하고 오브젝트를 제거합니다.
				for(auto& TimerEntry : DynamicDestroyObject.TimerHandles)
				{
					if(IsValid(TimerEntry.Key))
					{
						// 타이머를 해제합니다.
						GetWorld()->GetTimerManager().ClearTimer(TimerEntry.Value);

						// 오브젝트를 제거합니다.
						TimerEntry.Key->ConditionalBeginDestroy();		// 오브젝트를 안전하게 제거하는 함수입니다. 가비지 컬렉션 대상이 되기 전에 수동으로 메모리에서 해제합니다.
						TimerEntry.Key = nullptr;
					}
				}

				DynamicDestroyObject.TimerHandles.Empty();
			}
		}

		List.Empty();
	}
// BaseObjectPoolSystem.cpp

void UPRBaseObjectPoolSystemComponent::ClearDynamicDestroyObjectList(FPRDynamicDestroyObjectList& TargetDynamicDestroyObjectList)
{
	ClearDynamicDestroyObjects(TargetDynamicDestroyObjectList.List);
}

 동적으로 생성된 오브젝트들은 활성화된 후 비활성화 상태가 되면 각 ObjectPoolSystem의 동적으로 생성된 오브젝트와 TimerHandle을 보관하는 Map에 저장하게 됩니다. 이 Map의 키 값은 ObjectPoolSystem마다 유형이 다를 수 있기 때문에, Template 함수를 사용하여 동적으로 생성된 오브젝트들을 제거하는 로직을 구현하였습니다.