object pool 패턴은 게임을 제작할 때 자주 사용하는 패턴 중 하나입니다. 프로그램에서 인스턴스를 생성할 때에는 생각보다 많은 비용이 듭니다. 특히, 큰 크기의 Prefab들을 그때그때 새로 생성해서 사용하는 것들이 그렇지요.
큰 용량의 Prefab을 한 프레임 안에서 여러 번 생성하게 되면, 한 프레임 안에서 많은 CPU 자원을 사용하게 된다는 의미이고, 이것은 프레임 저하와 잦은 생성(Instantiate)과 파괴(Destroy)를 통한 빈번한 GC 호출을 의미합니다.
물론, 작은 프로젝트나 취미 수준의 게임 개발에서는 필요 없는 기능일 수 있지만, 그런 와중에 성능을 올려야 한다면 가장 먼저 도입을 고려해야 할 만한 패턴입니다.
이번 포스팅에서는 UniRx를 이용해 ObjectPool 패턴을 제작하는 방법을 공유할까 합니다.
Object Pool 이란?
앞에서 오브젝트의 잦은 생성과 파괴는 게임 성능에 안 좋은 영향을 미친 다고 했습니다. 이런 상황을 해결하기 위한 솔루션은 간단합니다. 바로 "생성과 파괴를 하지 않는 것"입니다.
별것 아니지만 성능에 매우 긍정적인 영향을 미칩니다.
Object Pool의 역할은 객체를 미리 생성해두고 다른 대상이 요청을 했을 때 미리 저장해둔 객체를 전달하고 다 쓴 객체는 반환받아 저장해두는 역할을 합니다. 우리는 이 패턴을 UniRx를 이용해 구현해볼 텐데, 다행스럽게도 UniRx에서 이에 대한 툴들을 제공하고 있습니다.
UniRx.Toolkit.ObjectPool
UniRx에서 Toolkit 네임스페이스 안에 ObjectPool이라는 친구가 있습니다. UniRx에서 제공하는 ObjectPool 템플릿입니다. 기본적인 선언 방법은 다음과 같습니다.
위처럼 Generic으로 풀링 할 객체의 타입을 정합니다. 이때 타입은 UnityEngine.Component를 상속받는 클래스여야 합니다.
오버라이딩해야 하는 함수들
UniRx는 소스를 직접 뜯어볼 수 있는 장점을 가지고 있습니다. ObjectPool이 어떻게 생겨먹었는지 살펴볼 수 있죠, 아래 코드는 ObjectPool 클래스 중 핵심적인 코드만 몇 개 발취한 것입니다.
그중에서 Rent()와 Return()을 살펴보겠습니다. ObjectPool을 사용하는 핵심적인 메소드이기 때문입니다. 잘 보면, Rent와 Return을 하기 전에 각각 OnBeforeRent()와 OnBeforeReturn()을 호출하고 있습니다. 이 함수들은 Virtual로 선언되어있고, 기본적으로 오브젝트를 켜고 끄는 기능을 하고 있습니다. 이 부분을 오버라이딩하면 좀 더 디테일하게 Pooling 되는 객체를 관리할 수 있죠.
저는 Pooling 한 오브젝트를 Rent()하고 활성화하기 전에 몇 가지 변수들을 인젝션 해야 했고, OnEnable타이밍에 사용해야 하기 때문에 Rent 한 순간이 아닌 인젝션 후에 SetActive 해야 했습니다.
따라서, 다음과 같이 함수를 오버라이딩해서 사용했죠.
특별히 Rent/Return후 전처리를 해야한다하면 이 부분을 오버라이딩해서 사용하면 됩니다. 특히 객체를 Pooling 할 경우 Rent후 세팅을 해주거나 Return 하기 전에 데이터를 리셋하는 로직이 필요한 경우가 많은데 그때 주로 이용하면 좋을 것 같습니다.