Refactoring and Object Pooling | DevLog_015

As of a month until submission, my Hypercasual project is hitting its final stages. Assets are added (some finishing touches needed), balancing of powerups/mechanics as I have people playtesting, and some code refactoring is taking place. One major point of efficiency is in how I am generating the level. In a previous update, the level was being generated procedurally and Actors were being spawned into the appropriate locations as the player traversed through the level. Although the game scale is small and efficiency wasn’t hindering the project in its current state, I wanted to retake a look at my SpawnHandler to implement a better system.

Object Pooling

Spawning and Deleting actors is not ideal if it is going to have endless behaviour. In theory, the actors can be spawned and updated if need be. So, I am refactoring some of that logic from a state of endless spawning, to drawing from a pool of actors I spawn in one time and update if need be. I did this in pseudocode before hopping in to do a massive refactor and noticed a lot of the bones for the pooling was already there. I would only need an update function to track and call the correct index information for the pooled objects. Those pooled objects I spawn in now on BeginPlay. A one-time spawning of all the objects at the beginning means I cut drastically down on the amount of spawning of Grids that I was doing before. Each having a dense property and logic set.

// ---------------------------------------------------------------------------------------------------------------
// -------------------------------------- Grid Spawning / Generation ---------------------------------------------
// ---------------------------------------------------------------------------------------------------------------
// Spawn a new Grid template when player passes spawn collider. Move Collider to Further position on Grid
// tempLoc will take previous Grid Template and add the size of grid to vector to place new Grid in line
// Collider will move in position onto new Grid Template
void ASpawnHandler::SpawnGridOnCollision(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if ((OtherActor == PlayerRef) && OtherComp)
	{
		UE_LOG(LogTemp, Warning, TEXT("Current Grid Direction: %d"), (uint8)ActiveGrids[CurrentGridIndex]->Layout.Direction);

		// Update Indices of temp and previous Active grids
		UpdateIndex();

		// Properties of New Grid Spawn Location / Rotation
		tempLoc = ActiveGrids[PreviousGridIndex]->GetActorLocation();
		
		// NewGrid = Cast<AGridTemplate>(GetWorld()->SpawnActor<AGridTemplate>(UpdateGridSpawnLocation((uint8)ActiveGrids[0]->Layout.Direction), Rotation, SpawnInfo));
		ActiveGrids[CurrentGridIndex]->SetActorLocationAndRotation(UpdateGridSpawnLocation((uint8)ActiveGrids[PreviousGridIndex]->Layout.Direction), Rotation);
		
		// Initialize new Grid
		ActiveGrids[CurrentGridIndex]->Layout.init(PlayerRef->GetCurrentDirection());
		ActiveGrids[CurrentGridIndex]->Init();

		// Move Spawn collider to next correct position 
		SpawnCollider->SetWorldLocationAndRotation(UpdateSpawnColliderLocation(tempLoc, (uint8)ActiveGrids[PreviousGridIndex]->Layout.Direction),
			UpdateSpawnColliderRotation((uint8)ActiveGrids[PreviousGridIndex]->Layout.Direction));

		// ------------------ TODO --------------
		// Update location of both Fog Walls

		// Set Players current direction
		PlayerRef->SetCurrentDirection((uint8)ActiveGrids[CurrentGridIndex]->Layout.Direction);
		UE_LOG(LogTemp, Warning, TEXT("Current: %d Previous: %d"), CurrentGridIndex, PreviousGridIndex);
	}
}

In the above updated function, I am no longer spawning Actors from the collision call. I am now using the pooled objects…

// Spawn All poolable grids into scene on Begin Play. Will be updated in spawn handling collision event
void ASpawnHandler::InitializeGridPool()
{
	for (int i = 0; i < 4; i++)
	{
		if (i == 0) tempLoc = FVector(1200.0f, 1200.0f, -20.f);
		else tempLoc = FVector(-1000.f, -1000.f, 0.f);
		ActiveGrids.Push(Cast<AGridTemplate>(GetWorld()->SpawnActor<AGridTemplate>(tempLoc, Rotation, SpawnInfo)));
	}
	ActiveGrids[CurrentGridIndex]->Layout.Direction = EGridDirection::FORWARD;
	// CurrentGridIndex = 1;
}
Index Tracking

With these objects always in play, I only need update the Layout struct attached to the actors and change their location. The layout functionality worked well with this new implementation, and nothing was needing refactoring now. The only real refactoring took place in the indexing of these pooled objects. Before I was always pushing the newly spawned actors into the front of an array of ActiveGrids. Meaning I always knew the currentGrid was at position [0] in the array. With the pooled actors, I would need to track and update the current and previous indices of the array to keep track of which direction the player is traveling and where NOT to spawn a new grid. For example, don’t spawn a new grid behind itself.

This new implementation works well and essentially functions as it does before. Although no glaring efficiency benefits are visible immediately, I know I can extend this iteration to other projects that would need it. I could event extend it within this project further to pool all objects (vehicles, powerups, etc.). At this point I will iterate, if need be, in the future or have time…

Leave a comment

Your email address will not be published. Required fields are marked *