PART 2 · 강의 3/4

Object Pooling 패턴

스폰/파괴 비용을 최소화하여 프레임 스파이크를 방지합니다

01

Object Pooling 개념

왜 풀링이 필요한가

Object Pooling은 게임플레이 중 액터 스폰 비용을 줄이기 위해 미리 일정 수의 액터를 스폰해두는 기법입니다.

🔄 Object Pooling 작동 원리
🏗️ 레벨 로드
미리 N개 스폰
📦 Pool
비활성 대기
🎮 Game
활성화/비활성화

풀링이 해결하는 문제

⚡ 일반 스폰 vs Object Pooling
❌ 일반 스폰
SpawnActor → Destroy

🐢 스폰 시 랙 스파이크
🗑️ GC 오버헤드
📊 메모리 단편화

✅ Object Pooling
Get → Return

⚡ 즉시 활성화
♻️ 재사용 (GC 없음)
📦 연속 메모리

핵심 원칙

"스폰하지 말고, 활성화하라. 파괴하지 말고, 비활성화하라."

02

Poolable Actor 인터페이스

풀링 가능한 액터 설계

📋 IPoolableActor 인터페이스
1
OnPooled()
풀에서 꺼낼 때
2
ResetForPool()
상태 초기화
3
OnUnpooled()
풀로 반환할 때
⭐ 인터페이스 정의
코드 숨기기
// IPoolableActor.h UINTERFACE(MinimalAPI) class UPoolableActor : public UInterface { GENERATED_BODY() }; class IPoolableActor { GENERATED_BODY() public: // 풀에서 꺼낼 때 호출 virtual void OnPooled() = 0; // 풀로 반환할 때 호출 virtual void OnUnpooled() = 0; // 리셋 (재사용 전 초기화) virtual void ResetForPool() = 0; };
Poolable Actor 구현 예시 (Projectile)
코드 보기
// AProjectile.h UCLASS() class AProjectile : public AActor, public IPoolableActor { GENERATED_BODY() public: virtual void OnPooled() override; virtual void OnUnpooled() override; virtual void ResetForPool() override; }; // AProjectile.cpp void AProjectile::OnPooled() { // 활성화 SetActorHiddenInGame(false); SetActorEnableCollision(true); SetActorTickEnabled(true); } void AProjectile::OnUnpooled() { // 비활성화 SetActorHiddenInGame(true); SetActorEnableCollision(false); SetActorTickEnabled(false); SetActorLocation(FVector(0, 0, -10000)); // 화면 밖으로 } void AProjectile::ResetForPool() { // 상태 초기화 Velocity = FVector::ZeroVector; Damage = DefaultDamage; LifeTime = DefaultLifeTime; }
03

Pool Manager 구현

World Subsystem 기반 풀 관리

🏗️ Pool Manager 구조

InitializePool()

레벨 로드 시 미리 스폰

GetFromPool()

풀에서 액터 가져오기

ReturnToPool()

사용 후 풀로 반환

Object Pool Subsystem 헤더
코드 보기
// UObjectPoolSubsystem.h UCLASS() class UObjectPoolSubsystem : public UWorldSubsystem { GENERATED_BODY() private: // 클래스별 풀 TMap<TSubclassOf<AActor>, TArray<AActor*>> Pools; // 풀 설정 TMap<TSubclassOf<AActor>, int32> PoolSizes; public: // 풀 초기화 void InitializePool(TSubclassOf<AActor> ActorClass, int32 Size); // 풀에서 가져오기 AActor* GetFromPool(TSubclassOf<AActor> ActorClass, const FTransform& SpawnTransform); // 풀로 반환 void ReturnToPool(AActor* Actor); };
⭐ Pool Manager 구현
코드 숨기기
void UObjectPoolSubsystem::InitializePool( TSubclassOf<AActor> ActorClass, int32 Size) { TArray<AActor*>& Pool = Pools.FindOrAdd(ActorClass); PoolSizes.Add(ActorClass, Size); // 미리 스폰 for (int32 i = 0; i < Size; ++i) { AActor* Actor = GetWorld()->SpawnActor<AActor>(ActorClass); if (IPoolableActor* Poolable = Cast<IPoolableActor>(Actor)) { Poolable->OnUnpooled(); // 비활성 상태로 시작 } Pool.Add(Actor); } } AActor* UObjectPoolSubsystem::GetFromPool( TSubclassOf<AActor> ActorClass, const FTransform& SpawnTransform) { TArray<AActor*>* Pool = Pools.Find(ActorClass); if (Pool && Pool->Num() > 0) { AActor* Actor = Pool->Pop(); Actor->SetActorTransform(SpawnTransform); if (IPoolableActor* Poolable = Cast<IPoolableActor>(Actor)) { Poolable->ResetForPool(); Poolable->OnPooled(); } return Actor; } // 풀이 비어있으면 새로 스폰 (fallback) return GetWorld()->SpawnActor<AActor>(ActorClass, SpawnTransform); } void UObjectPoolSubsystem::ReturnToPool(AActor* Actor) { if (!Actor) return; if (IPoolableActor* Poolable = Cast<IPoolableActor>(Actor)) { Poolable->OnUnpooled(); } TArray<AActor*>& Pool = Pools.FindOrAdd(Actor->GetClass()); Pool.Add(Actor); }
04

주의사항

풀링 구현 시 고려사항

200개 이상 프리스폰 시

사운드나 VFX가 자동 활성화되지 않도록 주의하세요. 프리스폰 시점에 모든 이펙트가 동시에 재생될 수 있습니다.

수동 파괴 금지

Destroy() 대신 ReturnToPool()을 사용하세요. 실수로 파괴하면 풀 크기가 줄어들어 결국 스폰 비용이 발생합니다.

ResetForPool() 체크리스트

✅ 반드시 초기화해야 할 항목

🔢 상태 변수

HP, 속도, 데미지 등 모든 게임플레이 변수

🧩 컴포넌트

자식 컴포넌트 상태도 함께 리셋

⏱️ 타이머

활성 타이머 모두 ClearTimer

📢 델리게이트

바인딩된 델리게이트 정리

⚡ 물리

Velocity, AngularVelocity 초기화

🎭 애니메이션

애니메이션 상태 리셋

SUMMARY

핵심 요약

  • Object Pooling: 스폰/파괴 대신 활성화/비활성화로 비용 절감
  • IPoolableActor: OnPooled, OnUnpooled, ResetForPool 인터페이스
  • World Subsystem: 레벨 단위 풀 관리, 클래스별 풀 분리
  • Destroy() 금지: 항상 ReturnToPool() 사용
  • 상태 초기화: 재사용 전 모든 변수와 컴포넌트 리셋 필수
다음 단계

다음 강의에서는 캐시 친화적 데이터 구조Data Oriented Design으로 CPU 캐시 효율을 극대화하는 방법을 다룹니다.

PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 기본 오브젝트 풀 구현

총알 Actor를 50개 미리 스폰하여 풀에 보관하는 시스템을 구현하세요. Deactivate/Activate 패턴으로 재사용하고, SpawnActor 대비 프레임 히칭 감소를 측정합니다.

실습 2: 자동 확장 풀

풀이 고갈되면 자동으로 10개씩 추가 생성하는 확장 풀을 구현하세요. SetActorHiddenInGame(), SetActorEnableCollision(), SetActorTickEnabled()를 활용하여 비활성화/활성화를 처리합니다.

심화 과제

TSubclassOf를 받아 어떤 Actor든 풀링할 수 있는 템플릿 기반 UObjectPoolSubsystem을 구현하세요. 워밍업, 자동 축소(Shrink), 풀 통계 로깅 기능을 포함합니다.