[Unity 강의] 뱀서라이크 강의 - PoolManager

업데이트:

카테고리:

태그: , ,




ObjectPool

✔ Instantiate는 자주 사용 시 성능 문제가 발생할 수 있다.
✔ 게임 오브젝트를 Destroy 하지 않고 비활성화하고 남겨둔다.
✔ 필요할 때 Instantiate를 하지 않고 숨겨둔 오브젝트를 활성화시켜서 보여준다.

ObjectPool<T>()

옛 정리글 ObjectPool()
유니티에서 제공하는 클래스 ObjectPool<T>()
Image

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));
    }
}





잡담, 일기?

리소스관리 오브젝트 풀링




📔

댓글남기기