본문 바로가기
[Unity]

[Unity] 오브젝트 풀링을 간단히 구현해보자(ObjectPool)

by 김기승 2024. 5. 24.

다수의 오브젝트를 생성하고, 파괴하는 과정을 반복하면,

GC가 빈번하게 작동하여, 성능 저하(또는 프레임 드랍)가 발생한다.

 

만약, 사용한 오브젝트를 재사용한다면

CPU 성능 소모를 줄일 수 있을 것이다.

 

이를 오브젝트 풀링이라고 하는데, 유명한 최적화 기법 중 하나이다.

유니티에서는 이 오브젝트 풀링을 간단히 구현할 수 있는

ObjectPool 클래스를 지원한다.

 

예시를 간략히 살펴보도록 하자.


 

결과물

위 이미지는 오브젝트 풀링의 결과를 보여준다.

한번 사용한 큐브를 잠시 비활성화하고, 필요할 때에 다시 사용하는 것을 볼 수 있다.

 

핵심은 생성된 오브젝트가 파괴되지 않고 남아있다는 것이다.


예시 코드

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Pool;
using System.Collections.Generic;

public class ObjectPoolExample : MonoBehaviour
{
    [Header("UI")]
    [Tooltip("아이템을 가져오는 버튼")]
    public Button getButton;
    [Tooltip("아이템을 반환하는 버튼")]
    public Button releaseButton;
    [Tooltip("아이템을 해제하는 버튼")]
    public Button clearButton;

    [Header("Container")]
    [Tooltip("Item들을 담을 컨테이너")]
    public Transform container;

    // Pool
    private ObjectPool<GameObject> _pool; // 오브젝트 풀
    private Queue<GameObject> _usedItem = new Queue<GameObject>(); // 가져온 아이템들을 담을 큐

    private void Awake()
    {
        // 오브젝트 풀을 생성함과 동시에 이벤트 함수들을 등록한다.
        _pool = new ObjectPool<GameObject>(onCreatePoolItem, onGetPoolItem, onReleasePoolItem, onDestroyPoolItem);

        // 버튼들을 클릭하면 실행될 이벤트 함수들을 등록한다.
        getButton.onClick.AddListener(onClickGetButton);
        releaseButton.onClick.AddListener(onClickReleaseButton);
        clearButton.onClick.AddListener(onClickClearButton);
    }

    //__________________________________________________________________________ Pool
    private GameObject onCreatePoolItem() // 아이템을 생성하는 함수
    {
        // 큐브를 생성하고, 컨테이너에 담아둔다.
        GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
        obj.transform.SetParent(container);
        return obj;
    }
    private void onGetPoolItem(GameObject obj) // 아이템을 가져올 때 실행되는 함수
    {
        // 활성화하고,
        obj.SetActive(true);

        // 위치와 색상을 무작위로 선정한다.
        Vector3 pos = new Vector3(Random.Range(-5f, 5f), 0.5f, Random.Range(-5f, 5f));
        obj.transform.position = pos;

        Material material = obj.GetComponent<MeshRenderer>().material;
        material.color = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f));
    }
    private void onReleasePoolItem(GameObject obj) // 아이템을 반환할 때 실행되는 함수
    {
        // 비활성화한다.
        obj.SetActive(false);
    }
    private void onDestroyPoolItem(GameObject obj) // 아이템을 파괴할 때 실행되는 함수
    {
        // 파괴한다.
        Destroy(obj);
    }

    //__________________________________________________________________________ UI
    private void onClickGetButton() // Get 버튼 클릭 시 실행되는 함수
    {
        // 오브젝트 풀에서 가져온 아이템을 큐에 담아준다.
        _usedItem.Enqueue(_pool.Get());
    }
    private void onClickReleaseButton() // Release 버튼 클릭 시 실행되는 함수
    {
        // 큐로부터 아이템을 빼내어 오브젝트 풀에 반환한다.
        if (_usedItem.TryDequeue(out GameObject obj))
            _pool.Release(obj);
    }
    private void onClickClearButton() // Clear 버튼 클릭 시 실행되는 함수
    {
        // 오브젝트 풀에서 사용되지 않는 아이템들을 모두 파괴한다.
        _pool.Clear();
    }
}

 

ObjectPool<T> 형태로 선언하면 오브젝트 풀이 만들어진다.

T는 where T로써 참조 형식이면 모두 가능하다.

위 코드에서는 GameObject 타입으로 선언했다.

 

오브젝트 풀을 초기화할 때, 이벤트 함수를 등록할 수 있다.

아래는 ObjectPool의 생성자 선언부이다.

 public ObjectPool(Func<T> createFunc, Action<T> actionOnGet = null
 Action<T> actionOnRelease = null, Action<T> actionOnDestroy = null
 bool collectionCheck = true, int defaultCapacity = 10, int maxSize = 10000); 

createFunc는 아이템을 생성하는 함수이다.

오브젝트 풀에 재사용할 아이템이 없으면 이 함수를 통해 새로운 아이템을 생성한다.

 

actionOnGet은 아이템을 가져올 때 실행되는 함수이다.

ObjectPool의 Get 함수가 호출되면, 가져올 아이템에 대해 실행된다.

 

actionOnRelease는 아이템을 반환할 때 실행되는 함수이다.

ObjectPool의 Release 함수가 호출되면, 반환할 아이템에 대해 실행된다.

 

actionOnDestroy는 아이템을 파괴할 때 실행되는 함수이다.

ObjectPool의 Clear(또는 Dispose) 함수가 호출되면,

사용하지 않는 아이템에 대해 실행된다.

 

예시 코드에는 가져온 아이템들을 (Queue)에 담아두었다가,

반환할 때 꺼내어 돌려준다.


오브젝트 풀링이 실시간 성능에서는 강점을 보이지만,

메모리 공간을 계속 차지해야한다는 약점을 지니고 있다.

 

따라서, 적절한 메모리 해제가 매우 중요할 것으로 보인다.

댓글