1. 오브젝트풀(ObjectPool) 이란?
유니티 뿐만 아니라 모든 개발 플랫폼에서 인스턴스의 생성과 삭제는 매우 무거운 작업이다.
그리고 C# 기반의 유니티는 메모리 관리를 플랫폼이 담당하므로 가비지 컬렉션의 발생 타이밍을 직접 컨트롤 할 수 없기 때문에 예상치 못한 퍼포먼스 저하가 발생 할 수 있다.
이런 상황을 방지하기 위해 생성 가능한 인스턴스 갯수만큼의 메모리를 미리 할당해 풀에 넣어두고 필요할 때 꺼내고 사용이 끝나면 풀에 다시 반납해 나중에 재사용 할 수 있게 해주는 오브젝트 풀의 존재는 필수이다.
이런 오브젝트 풀을 통해 인스턴스 생성, 삭제 및 가비지 컬렉션 비용에서 매우 큰 이득을 얻을 수 있다.
또한 비슷한 뜻으로 오브젝트풀(ObjectPool) 을 메모리풀(MemoryPool) 이라고도 부르는데 엄밀히 따지면 두가지는 조금 다르다.
둘다 일정한 크기 만큼의 메모리 공간을 미리 생성해서 필요에 따라 꺼내거나 반납해 퍼포먼스를 최적화하는 기법이지만 오브젝트풀은 메모리를 예약할 때 오브젝트 단위로 생성을 하는 것이고 메모리풀은 특정 오브젝트에 상관없이 말 그대로 메모리 공간 자체의 크기를 예약 하는 것이다.
메모리풀의 경우 어떤 종류의 오브젝트든 필요한 만큼 메모리를 떼어내서 오브젝트로 캐스팅 한 후 사용하면 되지만 그만큼 구현에 더 신경을 써줘야 한다.
게임에서는 생성할 객체들이 정해져 있으므로 대체로 오브젝트풀을 사용하는게 더 편리하다.
2. 사용 준비
오픈소스로 공개되어 있는 뛰어난 기능과 안정성의 오브젝트풀인 AutoObjectPool 를 사용하려고 했는데 객체를 꺼내올 때 오브젝트 명이 아닌 원본 프리팹 인스턴스로만 가져올 수 있다거나 하는 등의 불편함이 있어서 좀더 편하게 사용할 수 있도록 수정해 보았다.
Download ObjectPool source code => https://github.com/skuld2000/ObjectPool
위의 깃허브 링크에서 ObjectPool 을 받은 후 유니티 프로젝트의 적당한 위치에 추가해 주자.
이것으로 모든 준비는 끝났다.
아, 잠깐... 소스코드 중 오브젝트풀의 메소드 인터페이스를 담당하는 매니저 클래스인 ObjectPoolManager 는 내가 사용중인 제네릭(Generic) 기반의 싱글톤(Singleton) 으로 되어 있다.
이 부분은 여러분이 사용 중인 싱글톤 방식이 다르다면 거기에 맞추거나 내가 일전에 올린 싱글톤 패턴의 제너릭 클래스 포스팅 의 Singleton.cs 를 가져다 사용하기 바란다.
https://skuld2000.tistory.com/26
3. 오브젝트풀 생성
사용법도 간단하다.
우선 오브젝트풀에 풀링할 프리팹을 등록한다.
등록 과정에서 최종적으로 필요한 인자는 총 7개 이다.
- prefab : 풀링할 프리팹 인스턴스
- addPool : 오브젝트가 minPool 갯수 이상일 경우 풀 확장 시 증가시킬 단위 (1.0f 이하면 현재 풀 크기의 백분율, 이상이면 근접한 정수 단위로 증가.)
- minPool : 오브젝트풀 생성 시 풀에 미리 할당해 둘 최소 갯수
- emptyBehaviour : 풀이 비어 있는 상태에서 객체 요청이 왔을 때 추가 객체를 생성할지 말지 또는 가장 오래전 활성화 된 오브젝트를 다시 재사용 할 지 여부
- maxBehaviour : 풀이 비어 있고 최대 크기에 도달했을 때 추가 객체 생성을 실패하고 끝낼 지, 또는 가장 오래전 활성화 된 오브젝트를 다시 재사용 할 지 여부
- modBehaviour : emptyBehaviour 와 maxBehaviour 설정 값을 해당 풀의 블록에 저장할 지 여부
일반적인 경우 Prefab, addPool, minPool 세가지 값을 인자로 갖는 메소드를 사용하면 된다.
그리고 꺼내올 때는 해당 Prefab 의 이름을 사용하므로 등록에 사용한 Prefab 인스턴스는 별도로 저장해두지 않아도 된다.
3. 오브젝트 스폰(Spawn)
이제 등록한 오브젝트풀에서 새 오브젝트를 꺼내올(Spawn) 차례다.
Spawn 함수가 요구하는 인자들을 살펴보자
- name : 오브젝트풀을 생성할 때 사용했던 Prefab 의 이름
- parent : 오브젝트풀에서 꺼내온 해당 오브젝트 인스턴스가 추가될 부모 오브젝트의 transform
- child : 지정한 인덱스의 자식을 활성화(SetActive)
- pos : 좌표
- rot : 회전값
- usePosRot : 4,5 번 인자에 넘겨준 값을 쓸지, 자체 transform 의 값을 쓸지 여부
일반적으로는 name 과 parent 를 인자로 갖는 메소드를 사용하면 된다.
4. 오브젝트 반납(Despawn)
사용이 끝난 오브젝트는 다시 풀로 반납(Despawn) 한다.
Despawn 은 GameObject 로 반납하는 메소드와 AP_Reference 로 반납하는 두가지 타입이 있다.
대부분의 경우 GameObject 로 사용할 것이므로 이 메소드를 사용하게 될것이다.
반납할 오브젝트와 지연 삭제를 위해 반납할 시간을 입력하는 두가지 메소드를 필요에 따라 적당히 사용하면 된다.
그 외에도 씬 리셋등의 경우를 위해 모든 오브젝트를 반납하는 DespawnAll 이나 풀 자체를 삭제하는 RemovePool, RemoveAll 등의 메소드가 제공된다.
5. 사용 예
실제 사용 샘플은 아래와 같다.
내 샘플 프로젝트에서 사용한 실제 예를 보자
내가 사용한 Singleton 클래스는 DontDetroyOnLoad 에 attach 되는 구조이므로 ObjectPoolManager 역시 DontDestoryOnLoad 하위에 추가된다.
그리고 InitializeSpawn 메소드로 생성한 오브젝트풀은 현재 Card_magician_magic_gear_hand 와 Magician_Back, Character, Warrior_Front, ButtonSelectCard 이렇게 5종류이며 각 풀 별로 아직 사용되지 않은 비활성 오브젝트들이 자식으로 들어있다.
각 풀을 선택해 Instpector 창을 보면 AP_Pool 컴포넌트가 추가 되어 있으며 이 풀의 프리팹과 현재 사이즈, 최대 사이즈 및 갯수 초과 요청 시 어떻게 할 것인지를 런타임 중에 수정할 수 있게 되어 있다.
그리고 디버그 옵션에는 현재 이 풀의 로그를 콘솔창에 출력하는 기능과
강제로 풀의 오브젝트를 Spawn 시키는 기능이 있다.
사용하기도 쉽고 다양한 상황에 맞춰 커스터마이징 할 수 있는 다양한 기능을 제공하므로 쓸만한 오브젝트풀을 찾고 있다면 한번쯤 써보길 권한다.
댓글