다수의 오브젝트를 생성하고, 파괴하는 과정을 반복하면,
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)에 담아두었다가,
반환할 때 꺼내어 돌려준다.
오브젝트 풀링이 실시간 성능에서는 강점을 보이지만,
메모리 공간을 계속 차지해야한다는 약점을 지니고 있다.
따라서, 적절한 메모리 해제가 매우 중요할 것으로 보인다.
'[Unity]' 카테고리의 다른 글
[Unity] URP 스텐실(Stencil) 활용하기 (1) | 2024.09.05 |
---|---|
[Unity] Input System으로 입력 처리하기 (35) | 2024.06.08 |
[Unity] 코루틴을 대체하자(UniTask) (43) | 2024.05.15 |
[Unity] 선을 그리는 UI를 만들어보자 (60) | 2024.04.28 |
[Unity] UI 모양을 내 맘대로 바꿔보자(VertexHelper) (50) | 2024.04.21 |
댓글