Table of Contents
개요
Unity 프로젝트를 처음 생성할 때
꼭 하는 우선순위들이 있다.
1. DoTween에셋 임포트
2. 오딘 인스펙터 에셋 임포트(유료)
3. 오브젝트 풀링 구현
이 글에서는 오브젝트 풀링 구현을 기록할려고 한다.
오브젝트 풀링은 메모리 최적화를 위해 빼놓을 수 없는 시스템이라고 생각한다.
Instantiate와 Destroy를 쓸때마다 괜히 죄책감이들고 그렇기 때문에 항상 구현해놓고 시작하는데
이 기회에 블로그에 적어놓을려고 한다.
Pool을 관리하는 PoolManager클래스
먼저 풀을 관리하며 Push와 Pop을 해주는 PoolManager 클래스이다.
딕셔너리와 스택으로 풀을 관리하고
Pop을 할때는 오브젝트나 String키값을 인수로 받아서 오브젝트를 꺼내준다.
poolableList에는 풀링 가능한 오브젝트 프리팹들을 넣어서
Awake()에서 딕셔너리에 미리 자리를 만들고 오브젝트의 인스턴스를 미리 생성한다.
만약 딕셔너리에 자리가 미리 없는 오브젝트를 Pop요청해도
오브젝트를 파라미터로 넘겼다면
딕셔너리에 즉시 자리를 만들고 그 오브젝트를 인스턴싱해서 리턴한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
public static PoolManager instance;
public List<Poolable> poolableList;
[SerializeField]
private List<Poolable> currentActivePoolables;
private Dictionary<string, Stack<Poolable>> poolDictionary;
private void Awake()
{
if (instance == null)
instance = this;
poolDictionary = new Dictionary<string, Stack<Poolable>>();
foreach(var poolObj in poolableList)
{
if (poolObj == null)
continue;
poolDictionary.Add(poolObj.id, new Stack<Poolable>());
for (int i = 0; i < poolObj.createCountInAwake; i++)
{
Poolable clone = Instantiate(poolObj.gameObject, transform).GetComponent<Poolable>();
clone.gameObject.SetActive(false);
poolDictionary[poolObj.id].Push(clone);
}
}
}
private void OnDestroy()
{
if (instance == this)
instance = null;
}
/* 프리팹 또는 같은 오브젝트를 파라미터로 받아서
* 풀러블 오브젝트를 찾아줌
*/
public Poolable Pop(Poolable poolObj)
{
// 풀에 존재한다면 찾음
if(poolDictionary.ContainsKey(poolObj.id))
{
Poolable returnObj = null;
while (poolDictionary[poolObj.id].Count > 0 && returnObj == null)
{
returnObj = poolDictionary[poolObj.id].Pop();
if (returnObj.isUsing)
{
returnObj = null;
continue;
}
returnObj.isUsing = true;
returnObj.onPop?.Invoke();
}
if (returnObj == null)
{
returnObj = Instantiate(poolObj.gameObject, transform).GetComponent<Poolable>();
returnObj.gameObject.SetActive(false);
returnObj.isUsing = true;
returnObj.onPop?.Invoke();
}
currentActivePoolables.Add(returnObj);
return returnObj;
}
// 풀에 없다면 풀을 만들고 새로생성해서 반환해줌
else
{
poolableList.Add(poolObj);
poolDictionary.Add(poolObj.id, new Stack<Poolable>());
Poolable clone = Instantiate(poolObj, transform).GetComponent<Poolable>();
clone.gameObject.SetActive(false);
clone.isUsing = true;
if (clone.isAutoPooling)
clone.ResetAutoPoolingTimer();
clone.onPop?.Invoke();
currentActivePoolables.Add(clone);
return clone;
}
}
public Poolable Pop(string id)
{
if(!poolDictionary.ContainsKey(id))
{
Debug.LogError("ID에 해당하는 풀링된 오브젝트 없음");
return null;
}
// 생성한 풀러블 오브젝트가 있다면 탐색
if (poolDictionary[id].Count > 0)
{
Poolable returnObj = null;
while (poolDictionary[id].Count > 0 && returnObj == null)
{
returnObj = poolDictionary[id].Pop();
if (returnObj.isUsing)
{
returnObj = null;
continue;
}
returnObj.isUsing = true;
returnObj.onPop?.Invoke();
}
// 미사용중인 풀러블 오브젝트가 없다면 새로생성
if (returnObj == null)
{
returnObj = Instantiate(poolDictionary[id].Peek().gameObject, transform).GetComponent<Poolable>();
returnObj.gameObject.SetActive(false);
returnObj.isUsing = true;
returnObj.onPop?.Invoke();
}
currentActivePoolables.Add(returnObj);
return returnObj;
}
// 생성한 풀러블 오브젝트가 없다면
else
{
// 프리팹 리스트에서 아이디로 프리팹을 찾아봄
Poolable prefab = poolableList.Find(p => p.id == id);
if (prefab == null)
{
Debug.LogError("ID에 해당하는 풀링된 오브젝트 없음");
return null;
}
// 프리팹을 찾았다면 생성
Poolable clone = Instantiate(prefab.gameObject, transform).GetComponent<Poolable>();
clone.gameObject.SetActive(false);
clone.isUsing = true;
if (clone.isAutoPooling)
clone.ResetAutoPoolingTimer();
clone.onPop?.Invoke();
currentActivePoolables.Add(clone);
return clone;
}
}
public void Push(Poolable poolObj)
{
if (!poolDictionary.ContainsKey(poolObj.id))
{
poolDictionary.Add(poolObj.id, new Stack<Poolable>());
poolableList.Add(poolObj);
}
poolObj.onPush?.Invoke();
poolObj.isUsing = false;
poolDictionary[poolObj.id].Push(poolObj);
poolObj.gameObject.SetActive(false);
if (currentActivePoolables.Contains(poolObj))
currentActivePoolables.Remove(poolObj);
}
public void PushAllActivePoolables()
{
if (currentActivePoolables.Count <= 0)
return;
List<Poolable> copyList = new List<Poolable>();
foreach (var p in currentActivePoolables)
copyList.Add(p);
foreach (var p in copyList)
Push(p);
currentActivePoolables.Clear();
}
}
Pooling할 객체에 부착하는 Poolable클래스
다음은 풀링 가능한 오브젝트에 컴포넌트로 부착하는
Poolable클래스이다
고유한 키값인 id변수를 가져야 한다.
isAutoPooling 변수를 true로 두면 자동으로 시간이되면 풀에 Push되게 구현하였다.
using System;
using UnityEngine;
using NaughtyAttributes;
public class Poolable : MonoBehaviour
{
public string id;
public int createCountInAwake = 0;
public bool isUsing = false;
public bool isAutoPooling;
[ShowIf("isAutoPooling")]
public float autoPoolingTime;
public Action onPop;
public Action onPush;
private float poolingTimer = 0f;
private void Update()
{
if(isAutoPooling && isUsing)
{
if(poolingTimer < autoPoolingTime)
poolingTimer += Time.deltaTime;
else
{
poolingTimer = 0f;
PoolManager.instance.Push(this);
}
}
}
public void ResetAutoPoolingTimer()
{
poolingTimer = 0f;
}
}
직접 작성한 예제:
https://github.com/qwes348/PoolManagerExample/tree/main
'Unity > C#' 카테고리의 다른 글
[Unity] Builder패턴으로 팝업 시스템 구현 (0) | 2023.02.16 |
---|---|
[Unity] GoogleSheetsToUnity에셋 활용 동적 스크립터블 오브젝트 생성기 제작 (0) | 2022.10.25 |
[Unity, C#] 박스 콜라이더안에 랜덤포인트 구하기 (0) | 2022.10.01 |
[C#] Enum에 Contains인지 확인하기, String을 Enum으로 변환하기 (0) | 2022.07.29 |
[Unity] Hitbox와 Hurtbox시스템 (0) | 2022.06.20 |