Go Back

Object Pooling System

My take on a dynamic object pool that works with any actors. The pool is written in C++ and utilizes interfaces. Custom behaviors can be defined for activation and deactivation offering flexibility as needed.

by Shahin Mohseni, Nov 30, 2024 (updated: Dec 15, 2024)
C++Unreal Engine

This blog is still being written and is yet not finished

The issue

While developing a zombie survival game, I encountered a persistent issue causing significant stuttering during gameplay.

I later identified the root cause: frequent spawning and destruction of actors within the level. This problem arose during key gameplay events such as shooting, enemy spawns, gem spawns, and the subsequent removal of these objects, all of which occurred frequently and contributed to the performance degradation.

The solution

To resolve this issue, I implemented an object pooling system. Instead of spawning actors dynamically during gameplay, all required actors were pre-allocated at the start of the level. These actors remained in memory and were activated or deactivated as needed. Since the objects were already initialized and managed within the pool, this approach eliminated the performance stutter caused by frequent spawning and destruction.

The logic for object pooling was within a static component class. This class provided functions to initialize and manage actors in the pool. To track the pooled actors, the class maintained a Map as a member variable.

Initializing the pool

The actor initialization process was implemented as follows:

  • Loop over each key of the map
  • Emplace the actor into the Pool Map and get the pool reference back
    • Create a for loop with the range being the value of the current map iteration
    • Spawn the actor
    • Set it to deactivated
    • Add it to the Pool Map
void UObjectPoolManager::InitPool()
{
    for (const auto& [Actor, Size] : ActorsToSpawn)
    {
        auto& [Actors] = Pools.Emplace(Actor);

        for (int _ = 0; _ < Size; _++)
        {
            AActor* SpawnedActor = CreateActor(Actor);
            SetIsActive(SpawnedActor, false);
            Actors.Add(SpawnedActor);
        }
    }

    UE_LOG(LogTemp, Warning, TEXT("UObjectPoolManager::InitPool"));
}

Activating / deactivating actors

To activate and deactivate actors both in and outside the pool, a static method was created. This method takes in a pointer to the actor you’re trying to modify and a boolean whether you want to activate or deactivate it.

void UObjectPoolManager::SetIsActive(AActor* Actor, bool NewActive)
{
	Actor->SetActorTickEnabled(NewActive);
	Actor->SetActorHiddenInGame(!NewActive);
}

First iteration of the object pool (only hides the actor and disables it by setting its tick to false)


Getting an actor from the pool

To fetch an actor from the pool, a call to the GetActor method is made. This method is quite complex as it does a lot of things to streamline the process of fetching an actor, even if it hasn’t been initialized in the pool. This decision was made to make it easier to test out the pool before specifying how many actors were needed. If a pool does not exist, it will throw a warning notifying the developer that said pool was not initialized from start, the developer can then add that actor to the pool when time fits.

AActor* UObjectPoolManager::GetActor(TSubclassOf<AActor> Actor)
{
	FActorPool* Pool = Pools.Find(Actor);

	if (Pool == nullptr)
	{
		FActorPool* NewPool = &Pools.Emplace(Actor);

		AActor* SpawnedActor = CreateActor(Actor);
		SetIsActive(SpawnedActor, false);
		NewPool->Actors.Add(SpawnedActor);

		UE_LOG(LogTemp, Error, TEXT("No pool found for %s, generating one"), *Actor->GetName());
		Pool = NewPool;
	}

	if (AActor* FoundActor = Pool->GetAvailableActor())
	{
		return FoundActor;
	}

	AActor* SpawnedActor = CreateActor(Actor);
	Pool->Actors.Emplace(SpawnedActor);
	return SpawnedActor;
}

GetActor function

To view the full source of the object pool, you can follow this link to get to the Github repository.