
게임은 사람이 동시에 즐기는 경우가 많아 서버와 네트워크 지식도 중요합니다. 과정 후반에는 여러 명이 접속하는 간단한 멀티플레이 기능을 구현해 보며 클라이언트…
만들고 부수는 대신 빌려 쓰고 돌려주는 방식
슈팅 게임에서 플레이어가 1초에 열 발씩 총을 쏜다고 해 봅시다. 가장 단순한 방법은 쏠 때마다 총알을 새로 만들고, 화면 밖으로 나가면 지우는 것입니다. 처음엔 문제가 없어 보이지만, 총알과 적과 이펙트가 동시에 쏟아지는 장면에서는 게임이 갑자기 끊기기 시작합니다.
오브젝트를 새로 만드는 일은 생각보다 무겁습니다. 메모리를 새로 잡고, 구성 요소를 붙이고, 초기 설정을 해야 합니다. 지울 때도 정리 작업이 따릅니다. 이 비용이 한 프레임에 몰리면 그 프레임만 길어져 화면이 툭 끊깁니다.
오브젝트 풀링은 도서관과 비슷합니다. 책이 필요할 때마다 새로 인쇄했다가 다 읽으면 버리는 대신, 도서관에서 빌려 읽고 반납합니다. 다음 사람은 반납된 책을 다시 빌려 갑니다. 책을 처음 마련하는 비용은 들지만, 이후로는 빌리고 돌려주기만 하면 됩니다. 여기서 책을 모아 둔 서가가 바로 풀(pool)입니다.
풀링이 특히 효과를 보는 대상
모든 오브젝트를 풀링할 필요는 없습니다. 효과가 큰 것은 다음 조건을 갖춘 것들입니다.
- 짧은 시간에 많이 생겼다 사라짐: 총알, 화살, 탄피, 파편
- 모양과 구성이 같아 재사용이 쉬움: 같은 종류의 잡몹, 코인, 체력 구슬
- 전투 중처럼 끊기면 안 되는 순간에 생김: 타격 이펙트, 데미지 숫자, 사운드 재생용 오브젝트
- 목록 화면에서 반복되는 칸: 스크롤되는 인벤토리 칸, 채팅 메시지 줄
반대로 게임 시작 때 한 번 만들고 끝까지 쓰는 오브젝트, 보스처럼 한 번만 등장하는 오브젝트는 풀링해도 얻는 것이 거의 없습니다. 오히려 코드만 복잡해집니다. 풀링을 넣기 전에 프로파일러로 생성과 삭제가 정말 부담이 되는지 먼저 확인하는 편이 좋습니다. 문제가 없는 곳까지 미리 최적화하면 수정할 때마다 신경 쓸 곳만 늘어납니다.
오브젝트 풀의 기본 구조와 동작 순서
풀을 만드는 방법은 엔진과 언어에 따라 다르지만, 큰 흐름은 같습니다.
- 준비: 게임이나 스테이지 시작 시 오브젝트를 일정 개수 만들어 두고 모두 비활성화합니다.
- 꺼내기: 필요할 때 풀에서 쉬고 있는 오브젝트 하나를 찾아 위치와 방향을 정하고 활성화합니다.
- 사용: 평소처럼 날아가고, 부딪히고, 애니메이션을 재생합니다.
- 반납: 역할이 끝나면 삭제하지 않고 비활성화해 풀로 돌려보냅니다.
- 부족할 때: 쉬는 오브젝트가 없으면 새로 하나 만들어 풀에 추가하거나, 가장 오래된 것을 회수해 씁니다.
준비 단계의 개수는 실제 플레이에서 동시에 필요한 최대치를 보고 정합니다. 너무 적으면 결국 실행 중에 새로 만들게 되고, 너무 많으면 쓰지도 않는 오브젝트가 메모리를 차지합니다. 테스트하며 동시에 몇 개가 쓰이는지 기록해 두면 적절한 크기를 정하기 쉽습니다.
로딩 화면에서 풀을 미리 채워 두는 경우가 많은 것도 이 때문입니다. 만드는 비용을 플레이어가 기다려도 괜찮은 순간으로 옮겨 두는 셈입니다.
엔진에서 풀링을 다루는 방식
Unity에서는 오브젝트를 켜고 끄는 SetActive로 활성 상태를 바꾸며 풀을 구현하는 방식이 널리 쓰입니다. 최근 버전에는 풀링을 돕는 기본 클래스도 들어 있어 직접 다 짜지 않아도 됩니다. Unreal Engine에서도 액터를 숨기고 충돌과 Tick을 끈 상태로 보관했다가 다시 켜는 방식으로 풀을 만듭니다. 파티클이나 나이아가라 같은 이펙트 시스템은 내부적으로 비슷한 재사용 기능을 갖추기도 합니다.
어떤 방식이든 핵심은 같습니다. '없애지 말고 꺼 두었다가 다시 켠다'는 것입니다. 엔진이 제공하는 기능을 쓰더라도 이 원리를 알아야 문제가 생겼을 때 원인을 찾을 수 있습니다.
풀링을 처음 쓸 때 생기는 버그
가장 흔한 문제는 상태 초기화를 빼먹는 것입니다. 적을 풀에서 꺼냈는데 이전 판에서 깎인 체력 그대로 등장하거나, 총알이 이전 속도를 기억해 엉뚱한 방향으로 날아가는 식입니다. 꺼낼 때마다 체력, 속도, 타이머, 애니메이션 상태를 처음 값으로 돌려놓는 초기화 함수를 반드시 두어야 합니다.
두 번째는 반납한 오브젝트를 다른 코드가 계속 붙잡고 있는 경우입니다. 유도 미사일이 이미 반납된 적을 계속 쫓아가다가, 그 적이 다른 위치에서 다시 꺼내지면 갑자기 방향을 꺾습니다. 반납할 때 그 오브젝트를 참조하던 곳에 알려 주거나, 대상이 여전히 유효한지 확인하는 코드가 필요합니다.
세 번째는 같은 오브젝트를 두 번 반납하는 것입니다. 충돌과 시간 초과가 동시에 일어나 반납이 두 번 호출되면, 풀 안에 같은 오브젝트가 두 개 들어간 것처럼 꼬입니다. 이미 반납된 상태인지 확인하는 장치를 넣어 두면 막을 수 있습니다.
작은 슈팅 게임으로 연습해 보기
풀링은 직접 차이를 확인해 볼 때 가장 잘 이해됩니다. 총알을 매번 생성·삭제하는 버전과 풀링 버전을 각각 만들고, 엔진의 프로파일러로 프레임 시간이 어떻게 다른지 비교해 보세요. 총알 수를 크게 늘릴수록 차이가 분명해집니다. 이 비교 결과를 정리해 두면 포트폴리오에서 최적화를 고민한 근거로 보여 줄 수 있습니다.
기능은 돌아가는데 전투 장면에서만 끊긴다면, 풀링 같은 구조 개선을 배울 때가 된 것입니다. Unreal Engine으로 액션 게임을 만들어 보며 이런 최적화까지 익히고 싶다면, SBS아카데미게임학원 언리얼엔진(Unreal) 과정 상담에서 프로젝트 수업이 어떤 결과물로 이어지는지 알아보셔도 좋습니다.
자주 묻는 질문
풀링을 쓰면 메모리를 더 많이 쓰지 않나요?
미리 만들어 두는 만큼 메모리를 더 차지할 수 있습니다. 대신 실행 중 끊김을 줄이는 대가라서, 풀 크기를 실제 필요량에 맞추는 것이 중요합니다.
풀링과 가비지 컬렉션은 어떤 관계가 있나요?
C#처럼 가비지 컬렉션을 쓰는 언어에서는 오브젝트를 자주 만들고 버리면 메모리 정리가 자주 일어납니다. 풀링은 버려지는 오브젝트를 줄여 이 정리 작업이 덜 일어나게 돕습니다.
가까운 캠퍼스에서 게임프로그래밍 알아보기
사는 곳과 가까운 캠퍼스를 고르면 수업 내용, 오시는 길, 상담 신청을 한 번에 볼 수 있습니다.