오늘 배운 개념
오브젝트 풀링(Object Pooling)은 자주 생성되고 삭제되는 객체를 미리 생성해두고 재사용하는 메모리 및 성능 최적화 기법이다.
게임에서는 총알, 이펙트, 몬스터, UI 같은 객체가 반복적으로 생성되고 제거된다.
이때 매번 Spawn과 Destroy를 반복하지 않고, 비활성화 후 다시 재사용하는 방식으로 동작한다.
왜 사용하는가
게임에서 객체 생성과 삭제는 생각보다 비용이 크다.
객체를 생성할 때는:
- 메모리 할당
- 컴포넌트 초기화
- 등록 처리
등이 필요하다.
삭제 시에도:
- 메모리 정리
- 참조 제거
- Garbage Collection 처리
과정이 발생한다.
이 작업이 반복되면 프레임 드랍이나 순간적인 렉(Stutter)이 발생할 수 있다.
오브젝트 풀링은 이런 비용을 줄이기 위해 사용한다.
어떤 문제를 해결하는가
Spawn / Destroy 반복 문제
총알처럼 짧은 시간에 대량 생성되는 객체는 Spawn과 Destroy 비용이 매우 커질 수 있다.
예시:
- 총알 연사
- 폭발 이펙트
- 몬스터 웨이브
- 데미지 숫자 UI
이런 상황에서 성능 저하가 발생한다.
Garbage Collection 부하 문제
언리얼 엔진은 UObject 기반이라 삭제된 객체를 Garbage Collector가 정리한다.
하지만 객체가 너무 자주 생성되고 삭제되면:
- GC 호출 증가
- CPU 부하 증가
- 프레임 끊김
문제가 발생할 수 있다.
프레임 안정성 문제
실시간 게임에서는 평균 FPS보다 프레임 안정성이 더 중요하다.
Spawn 순간마다 CPU 사용량이 튀면:
- 프레임 드랍
- 입력 지연
- 끊기는 느낌
이 발생할 수 있다.
오브젝트 풀링은 이런 순간적인 비용을 줄여준다.
내부 동작 원리
기본 구조는 다음과 같다.
게임 시작
↓
객체 여러 개 미리 생성
↓
비활성 상태로 저장
↓
필요 시 활성화해서 사용
↓
사용 종료 후 비활성화
↓
다시 풀(Pool)로 반환
즉:
Create → Destroy
구조가 아니라
Create → Reuse
구조로 동작한다.
핵심은 "삭제하지 않고 재사용"하는 것이다.
실제 사용 예시
총알 시스템
기존 방식:
발사
→ Bullet Spawn
→ 충돌
→ Destroy
문제:
- 연사 시 객체 생성 비용 증가
- GC 부하 증가
오브젝트 풀링 방식:
게임 시작 시 총알 100개 생성
↓
발사 시 비활성 총알 가져오기
↓
활성화 후 사용
↓
충돌 시 비활성화
↓
다시 Pool로 반환
장점:
- Spawn 비용 감소
- 프레임 안정화
- GC 감소
파티클 / 이펙트
폭발 효과나 히트 이펙트는 짧은 시간 동안 자주 생성된다.
따라서:
- Explosion Pool
- Hit Effect Pool
형태로 관리하는 경우가 많다.
UI 시스템
언리얼에서 Widget 생성도 비용이 발생한다.
예시:
- 데미지 텍스트
- 채팅 로그
- 알림 메시지
등도 재사용 가능한 구조로 만들 수 있다.
언리얼에서의 일반적인 구현 방식
보통 Pool Manager를 만들어 관리한다.
ObjectPoolManager
├── Bullet Pool
├── Effect Pool
└── Enemy Pool
주로 사용하는 자료구조:
- TArray
- Queue
- ActorComponent
등이 있다.
주요 함수 예시
GetObjectFromPool()
ReturnObjectToPool()
Get 처리 흐름
비활성 객체 탐색
→ 활성화
→ 위치 및 상태 초기화
→ 반환
Return 처리 흐름
사용 종료
→ 비활성화
→ Pool로 반환
장점
성능 향상
가장 큰 장점이다.
- Spawn 비용 감소
- Destroy 비용 감소
- GC 감소
효과를 얻을 수 있다.
프레임 안정화
실시간 게임에서 매우 중요하다.
특히:
- FPS 게임
- 탄막 게임
- 모바일 게임
에서 효과가 크다.
메모리 관리 효율 증가
객체 수를 제한할 수 있다.
예시:
총알 최대 200발
처럼 관리 가능하다.
단점
메모리 사용량 증가
객체를 미리 생성하기 때문에 사용하지 않아도 메모리를 차지한다.
초기 설계가 복잡함
단순 Spawn / Destroy보다 구현 난이도가 높다.
필요한 작업:
- 활성 상태 관리
- 초기화 처리
- 반환 로직 관리
상태 초기화 실수 가능
재사용 구조라 이전 상태가 남을 수 있다.
예시:
- Velocity 초기화 안됨
- HP 남아있음
- 파티클 상태 유지
이런 버그가 자주 발생한다.
헷갈리기 쉬운 부분
비활성화와 삭제는 다르다
SetActorHiddenInGame(true);
SetActorEnableCollision(false);
SetActorTickEnabled(false);
는 단순 비활성화다.
메모리에서 제거된 것이 아니다.
Destroy를 안 한다고 메모리 누수가 아니다
오브젝트 풀링은 의도적으로 객체를 유지하는 구조다.
즉:
- 메모리 누수
- 재사용 구조
는 서로 다른 개념이다.
모든 객체를 풀링할 필요는 없다
생성 빈도가 낮은 객체는 굳이 풀링하지 않아도 된다.
예시:
- 보스 몬스터
- 컷신 오브젝트
- 드물게 생성되는 Actor
실무에서 중요한 이유
실무 게임 개발에서는 프레임 안정성이 매우 중요하다.
특히:
- 모바일 게임
- 대규모 전투
- 멀티플레이
- 탄막 슈팅
에서는 Spawn / Destroy 비용이 치명적일 수 있다.
그래서 실제 프로젝트에서는:
- Bullet Pool
- Effect Pool
- UI Pool
등을 거의 기본적으로 사용한다.
실무에서 자주 하는 추가 최적화
Tick 비활성화
풀에 반환 시:
SetActorTickEnabled(false);
처리해 불필요한 Tick 비용을 제거한다.
Collision 비활성화
SetActorEnableCollision(false);
처리로 충돌 계산 비용을 줄인다.
Lazy Expansion
초기에는 일정 개수만 생성하고 부족할 때만 추가 생성하는 방식이다.
장점:
- 메모리 절약
- 초기 로딩 부담 감소
헷갈리는 개념 비교
Object Pooling vs Garbage Collection
Object Pooling:
- 객체 재사용 목적
- 생성 / 삭제 최소화
Garbage Collection:
- 사용하지 않는 객체 메모리 정리
서로 목적이 다르다.
Object Pooling vs Singleton
Object Pooling:
- 여러 객체 관리
Singleton:
- 객체 하나만 유지
완전히 다른 패턴이다.
마무리
오브젝트 풀링은:
"자주 사용하는 객체를
삭제하지 않고 재사용해
성능을 최적화하는 기법"
핵심은 단순히 "미리 만들어둔다"가 아닌
- 왜 Spawn 비용이 큰지
- 왜 GC가 문제인지
- 왜 프레임이 튀는지
까지 이해하는 것이중요하다는 것 같다. 게임 개발에서는 매우 자주 사용되는 핵심 최적화 기법 중 하나 라고 한다.
'학습일지 > 언리얼' 카테고리의 다른 글
| UE5 패키징 & 로딩 스크린 (0) | 2026.05.27 |
|---|---|
| UE5 실시간 HUD 연동 & 로딩 화면 구현 TIL (0) | 2026.05.26 |
| 차량 AI 카메라 무빙 + 사운드 구현기 (0) | 2026.05.19 |
| 차량 AI 스플라인 추종 + 브레이크 등 + 헤드라이트 + 사이드미러 구현 (0) | 2026.05.18 |
| 언리얼 프레임워크 (0) | 2026.05.04 |