[Unity 강의] 뱀서라이크 강의 - PoolManager
카테고리: Class VamSurLike
태그: C#, Unity, VamSurLike
ObjectPool
✔ Instantiate는 자주 사용 시 성능 문제가 발생할 수 있다.
✔ 게임 오브젝트를 Destroy 하지 않고 비활성화하고 남겨둔다.
✔ 필요할 때 Instantiate를 하지 않고 숨겨둔 오브젝트를 활성화시켜서 보여준다.
ObjectPool<T>
()
옛 정리글 ObjectPool()
유니티에서 제공하는 클래스 ObjectPool<T>()
ObjectPool()
// 유니티 제공 클래스 기본
public ObjectPool(
Func<T> createFunc, // 객체를 새로 생성하는 함수
Action<T> actionOnGet, // 풀에서 가져올 때 실행되는 함수
Action<T> actionOnRelease, // 풀에 반환할 때 실행되는 함수
Action<T> actionOnDestroy, // 객체가 완전히 삭제될 때 실행되는 함수
bool collectionCheck = true, // 중복 반환 검사
int defaultCapacity = 10, //초기용량
int maxSize = 10000 //최대 용량
)
----------------------
// 프로젝트에서 코드
public Pool(GameObject prefab)
{
_prefab = prefab;
_pool = new ObjectPool<GameObject>(OnCreate, OnGet, OnRelease, OnDestroy);
}
GameObject OnCreate()
{
GameObject go = GameObject.Instantiate(_prefab);
go.transform.parent = Root;
go.name = _prefab.name;
return go;
}
void OnGet(GameObject go)
{
go.SetActive(true);
}
void OnRelease(GameObject go)
{
go.SetActive(false);
}
void OnDestroy(GameObject go)
{
GameObject.Destroy(go);
}
PoolManager
Pool- 저수지 Manager - Pool 생성, Pool 관리
Pool 생성, 관리, 오브젝트 POP() - go.SetActive(true) , PUSH() - go.SetActive(false);
PoolManager
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
class Pool
{
GameObject _prefab;
IObjectPool<GameObject> _pool;
Transform _root; // 풀링되는 오브젝트들을 담는 오브젝트
Transform Root
{
get
{
if (_root == null)
{
GameObject go = new GameObject() { name = $"{_prefab.name}Root" };
_root = go.transform;
}
return _root;
}
}
public Pool(GameObject prefab)
{
_prefab = prefab;
_pool = new ObjectPool<GameObject>(OnCreate, OnGet, OnRelease, OnDestroy);
}
public void Push(GameObject go)
{
_pool.Release(go);
}
public GameObject Pop()
{
return _pool.Get();
}
#region Funcs
GameObject OnCreate()
{
GameObject go = GameObject.Instantiate(_prefab);
go.transform.parent = Root;
go.name = _prefab.name;
return go;
}
void OnGet(GameObject go)
{
go.SetActive(true);
}
void OnRelease(GameObject go)
{
go.SetActive(false);
}
void OnDestroy(GameObject go)
{
GameObject.Destroy(go);
}
#endregion
}
public class PoolManager
{
Dictionary<string, Pool> _pools = new Dictionary<string, Pool>();
public GameObject Pop(GameObject prefab)
{
if (_pools.ContainsKey(prefab.name)== false)
CreatePool(prefab);
return _pools[prefab.name].Pop();
}
public bool Push(GameObject go)
{
if (_pools.ContainsKey(go.name) == false)
return false;
_pools[go.name].Push(go);
return true;
}
void CreatePool(GameObject prefab)
{
Pool pool = new Pool(prefab);
_pools.Add(prefab.name, pool);
}
}
SpawningPool
Object 생성 주기, 최대치 등 설정, 코루틴으로 생성
SpawningPool
using System.Collections;
using UnityEngine;
public class SpawningPool : MonoBehaviour
{
float _spawnInterval = 2.0f;
int _maxMonsterCount = 100;
Coroutine _coUpdateSpawningPool;
void Start()
{
_coUpdateSpawningPool = StartCoroutine(CoUpdateSpawningPool());
}
IEnumerator CoUpdateSpawningPool()
{
while (true)
{
TrySpawn();
yield return new WaitForSeconds(_spawnInterval);
}
}
private void TrySpawn()
{
int monsterCount = Managers.Object.Monster.Count;
if (monsterCount > _maxMonsterCount)
return;
MonsterController mc = Managers.Object.Spawn<MonsterController>(Random.Range(0, 2));
mc.transform.position = new Vector2(Random.Range(-5, 5), Random.Range(-5, 5));
}
}
ObjectPooling 몬스터 생성 순서
1. GameScene에 SpawningPool 컴포넌트 추가;
2. SpawningPool Start()에서 코루틴(x초마다 TrySpawn) 실행
3. TrySpawn => Managers.Object.Spawn
4. ObjectManager.Spawn => Managers.Resource.Instantiate(name, pooling : true);
모든 객체 생성은 Resource로 이루어지지만 Pooling true에 따라 ObjectPooling인지 Instantiate를 할 건지 나누어진다.
5. pooling : true로 Managers.Resource.Instantiate
6. Managers.Pool.Pop(prefab);
7. return _pools[prefab.name]
.Pop()
8. return _pool.Get(); - Get => 객체 풀에서 사용 가능한 GameObject를 하나 꺼내서 반환, OnGet 실행
✔ 객체가 pool에 없으면 CreatePool로 새로 풀을 생성하고
✔ 객체가 pool에 있다면 비활성화된 오브젝트를 활성화시켜 재사용
실행순서
//1. GameScene SpawningPool 컴포넌트
public class GameScene : MonoBehaviour
{
SpawningPool _spawningPool;
void StartLoaded()
{
_spawningPool = gameObject.AddComponent<SpawningPool>();
}
}
//2. SpawningPool TrySpawn => Managers.Object.Spawn
public class SpawningPool : MonoBehaviour
{
private void TrySpawn()
{
MonsterController mc = Managers.Object.Spawn<MonsterController>(Random.Range(0, 2));
}
}
//3. ObjectManager Spawn
public class ObjectManager
{
public T Spawn<T>(int templateID =0) where T : BaseController
{
System.Type type = typeof(T);
else if(type == typeof(MonsterController))
{
string name = (templateID == 0 ? PrefabsName.Goblin : PrefabsName.Snake);
GameObject go = Managers.Resource.Instantiate(name, pooling : true);
MonsterController mc = go.GetOrAddComponent<MonsterController>();
Monster.Add(mc);
return mc as T;
}
}
}
//4. ResourceManager Instantiate
public class ResourceManager
{
public GameObject Instantiate(string key, Transform parent = null, bool pooling = false)
{
GameObject prefab = Load<GameObject>($"{key}");
//Pooling
if (pooling==true)
return Managers.Pool.Pop(prefab);
}
}
//5. PoolManager CreatePool
public class PoolManager
{
Dictionary<string, Pool> _pools = new Dictionary<string, Pool>();
public GameObject Pop(GameObject prefab)
{
if (_pools.ContainsKey(prefab.name)== false)
CreatePool(prefab);
return _pools[prefab.name].Pop();
}
void CreatePool(GameObject prefab)
{
Pool pool = new Pool(prefab);
_pools.Add(prefab.name, pool);
}
}
//6. Pool.POP
class Pool
{
public GameObject Pop()
{
return _pool.Get();
}
void OnGet(GameObject go)
{
go.SetActive(true);
}
}
ObjectPooling 몬스터 삭제 순서
1. ✔ 1. 몬스터 사망 OnDead() => Managers.Object.Despawn(this);
2. ObjectManager에서 디스폰 => Managers.Resource.Destroy(obj.gameObject);
3. ResourceManager.Destroy pool에 있으면 push
4. PoolManager Push => Pool Push
5. Pool Push - OnRelease => go.SetActive(false); 비활성화
✔ 객체를 삭제하지 않고 SetActive(false)로 비활성화
실행순서
//1. MonsterController OnDead() => Managers.Object.Despawn(this);
public class MonsterController : CreatureController
{
protected override void OnDead()
{
Managers.Object.Despawn(this);
}
}
//2. ObjectManager Despawn => Managers.Resource.Destroy(obj.gameObject);
public class ObjectManager
{
public void Despawn<T>(T obj) where T : BaseController
{
System.Type type = typeof(T);
else if (type == typeof(MonsterController))
{
Monster.Remove(obj as MonsterController);
Managers.Resource.Destroy(obj.gameObject);
}
}
}
//3 ResourceManager Destroy
public class ResourceManager
{
public void Destroy(GameObject go)
{
if (go == null)
return;
if (Managers.Pool.Push(go))
return;
Object.Destroy(go);
}
}
//4. PoolManager Push
public class PoolManager
{
public bool Push(GameObject go)
{
if (_pools.ContainsKey(go.name) == false)
return false;
_pools[go.name].Push(go);
return true;
}
}
//5. Pool Push - OnRelease
class Pool
{
public void Push(GameObject go)
{
_pool.Release(go);
}
void OnRelease(GameObject go)
{
go.SetActive(false);
}
}
이것저것 메모
_pool.Release(), _pool.get(go);
✔ _pool.Release(go); => go가 풀에 들어가고 비활성화됨.
✔ _pool.get(go); => 풀에 사용 가능한 GameObject를 꺼냄.
오브젝트 풀 주의점
✔ 오브젝트가 삭제되지 않고 SetActive(false)가 되는 것에서 오는 문제점이 있을 수 있다.
✔ 예로 OncollisionEnter에서 충돌 물제(target)을 체크하는 if(target==null )부분
✔ if(target==null) target이 destroy 되면 Null 이 나온다.
✔ if(target==null) target이 SetActive(false) 되면 null일까??
✔ 타이밍이 안 좋으면 에러가 날 수 있다.
✔ 예로 if(target==isActiveAndEnabled==false) 코드를 넣을 수 있다.
isActiveAndEnabled
public class MonsterController : CreatureController
{
private void OnCollisionEnter2D(Collision2D collision)
{
PlayerController target = collision.gameObject.GetComponent<PlayerController>();
if (target == null)
return;
if(target==isActiveAndEnabled==false)
return;
if (_coDotDamage != null) // 기존에 뭔가 있는걸 대비
StopCoroutine(_coDotDamage);
_coDotDamage = StartCoroutine(CoStartDotDamage(target));
}
}
잡담, 일기?
리소스관리 오브젝트 풀링
댓글남기기