저번에 말했던 맞을 때 생길 이펙트를 넣을 예정인데.... 중요한 점이 있다. Effect 같은 자주 생성되고 자주 쓰이는 객체를 계속 Instantiate로 생성해주는 게 맞을까? 유니티에도 자세히 다루는 내용인데. 생성과 삭제는 큰 메모리를 소비한다는 내용이다. 이게 왜 그렇냐 하면 유니티는 Object들에 많은 Component들과 정보들이 있다 이 객체를 메모리에 할당하고 실제 비주얼로 만들어주는 작업은 굉장히 많은 메모리 낭비가 있다는 것을 알 수 있다. 삭제도 똑같은 이유이다.
따라서 생성을 달리 해줄 필요가 있는데 이때 등장하는 개념이 Pooling이다.
풀링이 뭐냐? 객체를 미리 만들어둬서 필요할 때 꺼내쓰고 삭제 대신 반납을 하는 알고리즘을 말한다. 이렇게 하면?? 굳이 생성, 삭제를 할 필요가 없다.
게임 만들면서 처음 나오는 최적화 부분인데. 메니저로 처리해줄 생각이여서 같이 Monosingleton도 설명하겠다.
Script : MonoSingleton
using UnityEngine;
public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance = null;
private static bool IsDestroyed = false;
public static T Instance
{
get
{
if (IsDestroyed == true)
{
_instance = null;
}
if (_instance == null)
{
_instance = FindObjectOfType(typeof(T)) as T;
if (_instance == null)
{
Debug.LogError(typeof(T).Name + " could not be found");
}
else
{
IsDestroyed = false;
}
}
return _instance;
}
}
private void OnDestroy()
{
IsDestroyed = true;
}
}
MonoSingleton은 Single패턴을 간단하게 만들기 위해서 만든 녀석이다. Singleton 패턴을 사용하고 싶다면 MonoSingleton을 상속받고 제네릭으로 타입을 정해주면 된다. 여튼 코드를 설명하겠다.
public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
제네릭 선언부분이다. where로 T를 MonoBehaviour타입만 T가 될 수 있게 해준다.
private static T _instance = null;
private static bool IsDestroyed = false;
T 타입 인스턴스와 현재 파괴되었는지 확인하는 불변수이다.
public static T Instance
{
get
{
if (IsDestroyed == true)
{
_instance = null;
}
if (_instance == null)
{
_instance = FindObjectOfType(typeof(T)) as T;
if (_instance == null)
{
Debug.LogError(typeof(T).Name + " could not be found");
}
else
{
IsDestroyed = false;
}
}
return _instance;
}
}
다른 스크립트에서 불러올때 사용할 프로퍼티다. 현재 파괴 되었다면 인스턴스가 없는 것이니 null을 반환해주고
그게 아니고 인스턴스가 null이라면 (인스턴스가 없음)
하이라키 창에서 T타입의 오브젝트를 찾아서(메니저는 한개니까)
인스턴스에 넣어준다. 그런데도 null이면 못 찾은 것이니 현재 T타입 스크립트를 가지고 있는 게임 오브젝트 없다고 Log를 출력해준다. 인스턴스가 null이 아니라면 IsDestoryed를 false로 해준다. (그리고 instance 반환)
Set은 없어도 됨 어차피 안바뀌기 때문임.
private void OnDestroy()
{
IsDestroyed = true;
}
인스턴스 파괴 될때 IsDestroyed를 True로 해준다.
이러면 MonoSingleton은 끝이다.
본격적으로 Pooling으로 넘어가겠다.
Pooling
풀링은 객체들을 Stack으로 저장해둔다. 왜냐하면 계속해서 객체를 빼내면서 부족하다면 또 생성해주고 다시 반환해주는 역할을 하기 때문에 FIFO인 Stack이 가장 적합하다고 판단하였다.
Script : PoolableMono
using UnityEngine;
namespace ObjectPooling
{
public abstract class PoolableMono : MonoBehaviour
{
public PoolingType type;
public abstract void ResetItem();
}
}
풀링을 하기 위해서 꼭 상속 받아야하는 클래스이다. 현재 item에 poolType과
namespace ObjectPooling
{
public enum PoolingType
{
SwordHitEffect,
}
}
Item을 꺼냈을 때 할 함수인 ResetItem을 만들어줘야 한다.
Script : Pool
using System.Collections.Generic;
using UnityEngine;
namespace ObjectPooling
{
public class Pool<T> where T : PoolableMono
{
private Stack<T> _pool = new Stack<T>();
private T _prefab;
private Transform _parent;
private PoolingType _type;
public Pool(T prefab, PoolingType type, Transform parent, int count)
{
_prefab = prefab;
_type = type;
_parent = parent;
for(int i = 0; i < count; i++)
{
T obj = GameObject.Instantiate(_prefab, _parent);
obj.type = _type;
obj.gameObject.name = _type.ToString();
obj.gameObject.SetActive(false);
_pool.Push(obj);
}
}
public T Pop()
{
T obj = null;
if(_pool.Count <= 0)
{
obj = GameObject.Instantiate(_prefab, _parent);
obj.type = _type;
obj.gameObject.name = _type.ToString();
}
else
{
obj = _pool.Pop();
obj.gameObject.SetActive(true);
}
return obj;
}
public void Push(T obj)
{
obj.gameObject.SetActive(false);
_pool.Push(obj);
}
}
}
마찬가지로 제네릭을 사용한다. 제네릭 부분은 넘어가겠다.
private Stack<T> _pool = new Stack<T>();
private T _prefab;
private Transform _parent;
private PoolingType _type;
변수 선언 부분 PoolItem들 담아주는 Stack과 T타입 변수, 부모 Transfrom, Poolingtype이다.
public Pool(T prefab, PoolingType type, Transform parent, int count)
{
_prefab = prefab;
_type = type;
_parent = parent;
for(int i = 0; i < count; i++)
{
T obj = GameObject.Instantiate(_prefab, _parent);
obj.type = _type;
obj.gameObject.name = _type.ToString();
obj.gameObject.SetActive(false);
_pool.Push(obj);
}
}
생성자이다. T 타입 인스턴스를 가지고 오고 type과 부모 그리고 생성할 갯수를 가지고 온다.
_prefab = prefab;
_type = type;
_parent = parent;
초기화 해주고....
for(int i = 0; i < count; i++)
{
T obj = GameObject.Instantiate(_prefab, _parent);
obj.type = _type;
obj.gameObject.name = _type.ToString();
obj.gameObject.SetActive(false);
_pool.Push(obj);
}
반복하며 생성해주고 이름 type으로 설정해주고 SetActive꺼주고(씬에서 당장 작동을 안해야하니) Stack에 넣어준다. 받아온 Int값만큼 반복해준다.
public T Pop()
{
T obj = null;
if(_pool.Count <= 0)
{
obj = GameObject.Instantiate(_prefab, _parent);
obj.type = _type;
obj.gameObject.name = _type.ToString();
}
else
{
obj = _pool.Pop();
obj.gameObject.SetActive(true);
}
return obj;
}
Pop은 꺼내오는 함수이다. 그러므로 T타입 인스턴스를 반환해준다 T타입변수 미리 선언해주고
스택이 비었다?? 그럼 새로 만들어준다. 아니라면 Stack에서 꺼내준다. 그리고 SetActive를 켜서 씬에서 작동하게 해주고 그 T타입 인스턴스를 반환해줌
public void Push(T obj)
{
obj.gameObject.SetActive(false);
_pool.Push(obj);
}
Push는 반환하는 함수이다. Stack에 넣어줘야하니 SetActive를 False로 해주고 스택에 넣어주면 끝이다.
Script : Pool
using UnityEngine;
namespace ObjectPooling
{
[CreateAssetMenu(menuName = "SO/Pool/PoolItem")]
public class PoolingItemSO : ScriptableObject
{
public string enumName;
public string poolingName;
public string description;
public int poolCount;
public PoolableMono prefab;
private void OnValidate()
{
if(prefab != null)
{
if(enumName != prefab.type.ToString())
{
prefab = null;
Debug.LogWarning("Type mismatch!");
}
}
}
}
}
ItemSO다 정보들을 SO로 저장할 생각이다. enumName은 파일 이름 poolingName은 pool의 이름, 그 다음 설명, 생성할 갯수, 생성할 Prefab를 변수로 들고 있다.
그리고 값이 바뀔 때 마다. Prefab이 null이 아닐때 enumName과 Prefab의 타입 이름이 다르면 prefab을 null로 하고 Log를 출력해준다. 나중에 자세히 다루고 넘어가겠다.
using ObjectPooling;
using System.Collections.Generic;
using UnityEngine;
public class PoolingTableSO : ScriptableObject
{
public List<PoolingItemSO> datas = new List<PoolingItemSO>();
}
Item들 담는 TableSO이다.
Script : PoolManager
using ObjectPooling;
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoSingleton<PoolManager>
{
private Dictionary<PoolingType, Pool<PoolableMono>> _pools
= new Dictionary<PoolingType, Pool<PoolableMono>>();
public PoolingTableSO listSO;
private void Awake()
{
foreach (PoolingItemSO item in listSO.datas)
{
CreatePool(item);
}
}
private void CreatePool(PoolingItemSO item)
{
var pool = new Pool<PoolableMono>(item.prefab, item.prefab.type, transform, item.poolCount);
_pools.Add(item.prefab.type, pool);
}
public PoolableMono Pop(PoolingType type)
{
if(_pools.ContainsKey(type) == false)
{
Debug.LogError($"Prefab does not exist on pool : {type.ToString()}");
return null;
}
PoolableMono item = _pools[type].Pop();
item.ResetItem();
return item;
}
public void Push(PoolableMono obj, bool resetParent = false)
{
if (resetParent)
obj.transform.SetParent( transform );
_pools[obj.type].Push(obj);
}
}
PoolManager는 Pool를 관리하고 다른 스크립트에서 Pop과 Push를 할 수 있도록 해주는 스크립트다. 싱글톤 패턴을 사용한다.
private Dictionary<PoolingType, Pool<PoolableMono>> _pools
= new Dictionary<PoolingType, Pool<PoolableMono>>();
public PoolingTableSO listSO;
변수 선언 부분이다. 딕셔너리로 Pool을 관리 할 것이다.
Table도 들고 온다.
private void Awake()
{
foreach (PoolingItemSO item in listSO.datas)
{
CreatePool(item);
}
}
private void CreatePool(PoolingItemSO item)
{
var pool = new Pool<PoolableMono>(item.prefab, item.prefab.type, transform, item.poolCount);
_pools.Add(item.prefab.type, pool);
}
Table에 담겨있는 Pool들을 Awake할때 만들어줌
CreatePool함수는 매개변수로 item들고 오고 풀 만들어주고 type과 함께 딕셔너리에 넣어준다.
public PoolableMono Pop(PoolingType type)
{
if(_pools.ContainsKey(type) == false)
{
Debug.LogError($"Prefab does not exist on pool : {type.ToString()}");
return null;
}
PoolableMono item = _pools[type].Pop();
item.ResetItem();
return item;
}
Pop은 반환으로 PoolableMono(PoolItem)을 반환해준다. type을 매개변수로 들고 와서 딕셔너리에서 꺼낼 수 있게 한다.
먼저 딕셔너리에 이 poolType이 존재하지 않는다면 Error띄워주고 null 반환해준다.
있다면 딕셔너리에 꺼낸 뒤에 Pop함수써서 변수에 넣어주고 ResetItem함수 실행시켜주고 반환해준다.
public void Push(PoolableMono obj, bool resetParent = false)
{
if (resetParent)
obj.transform.SetParent( transform );
_pools[obj.type].Push(obj);
}
Push는 반환해줄 item하고 item의 부모를 바꿀지 여부를 매개변수로 들고온다.
resetParent가 true라면 자기 자신으로 부모 바꿔주고
딕셔너리에 찾아서 push해준다.
사용하는 방법 다음 파트에서 다루도록 하겠다.
'Unity' 카테고리의 다른 글
[엔진 프로젝트]Enemy Walking Around with BT (0) | 2024.12.03 |
---|---|
[엔진 프로젝트]Enemy Walking Around with BT (0) | 2024.11.01 |
[Unity] (2024_09_30) 2D 플랫 포머 게임 개발일지 (0) | 2024.09.30 |
[Unity] (2024_09_25) 2D 플랫 포머 게임 개발일지 FSM Part 2 (0) | 2024.09.26 |
[Unity] (2024_09_25) 2D 플랫 포머 게임 개발일지 FSM Part 1 (3) | 2024.09.26 |