Object Pooling

언리얼 거토
|2025. 3. 28. 21:03

개요

Object Pooling은 게임 개발에서 자주 사용되는 메모리 관리 기법 중 하나입니다. 이는 객체를 반복적으로 생성하고 삭제하는 비용을 줄이고 성능을 향상시키기 위해 미리 일정 개수의 객체를 생성하여 재사용하는 방식입니다. Unreal Engine에서는 Actor를 생성하고 삭제하는 비용이 크기 때문에, Object Pooling을 활용하면 퍼포먼스를 최적화할 수 있습니다. 이번 글에서는 Unreal Engine C++을 사용하여 Object Pooling을 구현하는 방법을 소개하겠습니다.


Object Pooling 개념

Object Pooling은 다음과 같은 원리로 작동합니다:

  1. 객체 미리 생성 - 특정 개수의 객체를 미리 생성하여 Pool에 저장합니다.
  2. 재사용 - 필요할 때 Pool에서 객체를 가져와 활성화합니다.
  3. 반환 - 사용이 끝난 객체를 다시 Pool에 반환하여 재사용할 수 있도록 합니다.

이 방식은 특히 다음과 같은 경우 유용합니다:

  • 총알, 아이템, 이펙트 등 짧은 시간 동안 반복적으로 생성 및 제거되는 객체 관리
  • 성능 최적화가 필요한 모바일 및 콘솔 게임
  • 네트워크 동기화가 중요한 멀티플레이어 게임

1. Object Pool Manager 생성하기

Object Pool을 관리하는 UItemPoolManager 클래스를 작성합니다. 이 클래스는 아이템을 미리 생성하고 필요할 때 재사용할 수 있도록 관리합니다.

#include "Items/Manager/ItemPoolManager.h"
#include "Items/Class/SpawnableItem.h"
#include "Engine/World.h"

void UItemPoolManager::InitializePool(TSubclassOf<ASpawnableItem> ItemClass, int32 PoolSize)
{
    if (!GetWorld() || ItemPool.Num() > 0) return;  // 이미 풀 초기화됨

    SpawnableItemClass = ItemClass;

    for (int32 i = 0; i < PoolSize; ++i)
    {
        ASpawnableItem* NewItem = GetWorld()->SpawnActor<ASpawnableItem>(ItemClass);
        if (NewItem)
        {
            NewItem->SetActorHiddenInGame(true);
            NewItem->SetActorEnableCollision(false);
            ItemPool.Add(NewItem);
        }
    }
}

ASpawnableItem* UItemPoolManager::GetItemFromPool()
{
    for (ASpawnableItem* Item : ItemPool)
    {
        if (Item && Item->IsHidden())
        {
            Item->SetActorHiddenInGame(false);
            Item->SetActorEnableCollision(true);
            return Item;
        }
    }
    return nullptr;
}

void UItemPoolManager::ReturnItemToPool(ASpawnableItem* Item)
{
    if (Item)
    {
        Item->SetActorHiddenInGame(true);
        Item->SetActorEnableCollision(false);
        ItemPool.Add(Item);
    }
}

2. 아이템 클래스 구현하기

아이템 클래스 ASpawnableItem을 작성하여 오버랩 이벤트와 상호작용 로직을 구현합니다.

#include "Items/Class/SpawnableItem.h"
#include "Components/SphereComponent.h"
#include "Kismet/GameplayStatics.h"
#include "NiagaraFunctionLibrary.h"

ASpawnableItem::ASpawnableItem()
{
    PrimaryActorTick.bCanEverTick = false;

    Scene = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
    SetRootComponent(Scene);

    Collision = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
    Collision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
    Collision->SetupAttachment(Scene);
    
    StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ItemMesh"));
    StaticMesh->SetupAttachment(Collision);
    StaticMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);

    Collision->OnComponentBeginOverlap.AddDynamic(this, &ASpawnableItem::OnItemOverlap);
}

void ASpawnableItem::OnItemOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
                                   UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
                                   bool bFromSweep, const FHitResult& SweepResult)
{
    if (OtherActor && OtherActor->ActorHasTag("Player"))
    {
        Interact(OtherActor);
    }
}

void ASpawnableItem::Interact(AActor* Activator)
{
    if (HasAuthority() && Activator)
    {
        Multicast_OnInteract();
        ResetItem();
    }
}

void ASpawnableItem::ResetItem()
{
    SetActorHiddenInGame(true);
    SetActorEnableCollision(false);

    UItemPoolManager* PoolManager = GetGameInstance()->GetSubsystem<UItemPoolManager>();
    if (PoolManager)
    {
        PoolManager->ReturnItemToPool(this);
        OnItemResetDelegate.Broadcast();
    }
}

3. 아이템 스포너 구현하기

아이템을 생성하고 일정 시간 후 다시 풀로 반환하는 역할을 수행하는 AItemSpawner 클래스를 작성합니다.

#include "Items/Class/ItemSpawner.h"
#include "Items/Manager/ItemPoolManager.h"

AItemSpawner::AItemSpawner()
{
    PrimaryActorTick.bCanEverTick = false;

    Scene = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
    SetRootComponent(Scene);
}

void AItemSpawner::BeginPlay()
{
    Super::BeginPlay();

    UItemPoolManager* PoolManager = GetGameInstance()->GetSubsystem<UItemPoolManager>();
    if (PoolManager && ItemClass)
    {
        PoolManager->InitializePool(ItemClass, 4);
        SpawnItem();
    }
}

void AItemSpawner::SpawnItem()
{
    UItemPoolManager* PoolManager = GetGameInstance()->GetSubsystem<UItemPoolManager>();
    if (PoolManager)
    {
        ASpawnableItem* Item = PoolManager->GetItemFromPool();
        if (Item)
        {
            FVector SpawnLocation = GetActorLocation();
            SpawnLocation.Z += 100.0f;

            Item->SetActorLocation(SpawnLocation);
            Item->SetActorHiddenInGame(false);
            Item->SetActorEnableCollision(true);

            Item->OnItemResetDelegate.AddDynamic(this, &AItemSpawner::OnItemReset);
            bIsItemActive = true;
        }
    }
}

void AItemSpawner::OnItemReset()
{
    GetWorldTimerManager().SetTimer(SpawnTimerHandle, this, &AItemSpawner::SpawnItem, SpawnCooldown, false);
    bIsItemActive = false;
}

 

'TIL > C++와 UE' 카테고리의 다른 글

언리얼엔진 멀티플레이에서의 PlayerState  (0) 2025.04.08
GameInstanceSubsystem  (0) 2025.04.02
언리얼 엔진 레플리케이션(Replication)  (0) 2025.03.11
네트워킹 및 멀티 플레이어 개요  (0) 2025.03.10
작성중  (0) 2025.02.26