오브젝트 풀링: Pool Manager

개발조하·2024년 1월 2일
0

Unity

목록 보기
21/30
post-thumbnail

1. 오브젝트 풀링이란?

보통 유니티에서 프리팹을 불러오거나 삭제시킬 때는 Instantiate(), Destroy()메서드를 사용한다. 간단한 게임의 경우에는 상관없지만 만약 생성하고 삭제시키는 오브젝트가 많이 사용되는 규모의 프로젝트라면 메모리 사용량이 늘어나 성능 저하의 원인이 될 수 있다. (ex. FPS게임의 탄환 등)
이를 해결하기 위해 생긴 개념이 오브젝트 풀링아다.

"게임이 실행되기 전에 정해진 양의 게임 오브젝트를 생성하고 필요한 게임 오브젝트를 단순히 활성화/비활성화하여 효과적으로 게임 오브젝트를 재활용하고 절대 파괴하지 않는 방식으로 작동한다"

2. 오브젝트 풀링 실습

2.1 스크립트 생성

2.1.1 PoolManager.cs 생성

2.1.2 Poolable.cs 생성

풀링을 할 개체인지 아닌지를 구별해줄 컴포넌트용 스크립트 생성
-> Poolable.cs을 부착하고 있는 오브젝트는 풀링 대상이라는 의미!

2.2 ResourceManager.cs 수정

+3가지 추가 사항

2.2.1 Original 프리팹을 이미 들고 있다면 바로 사용

  • PoolManager.cs
public class PoolManager 
{
    #region Pool
    class Pool
    {
        public GameObject Original { get; private set; }
        public Transform Root { get; set; }

        Stack<Poolable> _poolStack = new Stack<Poolable>();

        public void Init(GameObject original, int count = 5)
        {
            Original = original;
            Root = new GameObject().transform;
            Root.name = $"{original.name}_Root";

            for(int i = 0; i < count; i++)
            {
                Push(Create());
            }
        }

        Poolable Create()
        {
            GameObject go = Object.Instantiate<GameObject>(Original);
            go.name = Original.name;    
            return go.GetOrAddComponent<Poolable>();
        }

        public void Push(Poolable poolable)
        {
            if (poolable == null)
                return;

            poolable.transform.parent = Root;
            poolable.gameObject.SetActive(false); //비활성화
            poolable.IsUsing = false;

            _poolStack.Push(poolable);
        }

        public Poolable Pop(Transform parent)
        {
            Poolable poolable;
            if (_poolStack.Count > 0)
                poolable = _poolStack.Pop();
            else //대기상태가 하나도 없을 경우
                poolable = Create();

            poolable.gameObject.SetActive(true);

            //DontDestroyOnLoad 해제 용도
            if (parent == null)
                poolable.transform.parent = Managers.Scene.CurrentScene.transform;

            poolable.transform.parent = parent;
            poolable.IsUsing = true;

            return poolable;
        }
    }
    #endregion

    Dictionary<string, Pool> _pool = new Dictionary<string, Pool>(); 
    Transform _root;

    public void Init() 
    {
        if (_root == null)
        {
            _root = new GameObject { name = "@Pool_Root" }.transform; //풀링 대기실
            Object.DontDestroyOnLoad(_root);
        }
    }
    private void CreatePool(GameObject original, int count = 5)
    {
        Pool pool = new Pool();
        pool.Init(original, count);
        pool.Root.parent = _root;

        _pool.Add(original.name, pool);
    }

    public void Push(Poolable poolable) //사용 후 반환
    {
        string name = poolable.gameObject.name;
        if(_pool.ContainsKey(name) == false)
        {
            GameObject.Destroy(poolable.gameObject);
            return;
        }
        _pool[name].Push(poolable);
    }

    public Poolable Pop(GameObject original, Transform parent = null) //풀링된 오브젝트를 바로 사용
    {
        if(_pool.ContainsKey(original.name) == false)
        {
            CreatePool(original);
        }
        return _pool[original.name].Pop(parent);
    }


    public GameObject GetOriginal(string name)
    {
        if (_pool.ContainsKey(name) == false)
            return null;

        return _pool[name].Original;
    }

    public void Clear()
    {
        foreach(Transform child in _root)
        {
            GameObject.Destroy(child.gameObject);
        }

        _pool.Clear();
    }
}
  • ResourceManager.cs

2.2.2 풀링 대상일 경우_ 생성 방법

  • ResourceManager.cs
    public GameObject Instantiate(string path, Transform parent = null)
    {
        GameObject original = Load<GameObject>($"Prefabs/{path}");
        if(original == null)
        {
            Debug.Log($"Failed to load prefab : {path}");
            return null;
        }

        //탐색_풀링 대상 오브젝트일 경우
        if (original.GetComponent<Poolable>() != null)
            return Managers.Pool.Pop(original, parent).gameObject;

        //풀링 대상이 아닐경우_카메라, UI 등등
        GameObject go = Object.Instantiate(original, parent);
        go.name = original.name; 
        return go;
    }

2.2.3 풀링 대상일 경우_ 삭제 방법

    public void Destroy(GameObject go)
    {
        if(go == null)
           return;
        
        //풀링 대상이라면 PoolManager에 위탁
        Poolable poolable = go.GetComponent<Poolable>();
        if(poolable != null)
        {
            Managers.Pool.Push(poolable);
            return;
        }

        //풀링 대상이 아닐경우
        Object.Destroy(go);
    }

2.3 Managers.cs에 풀링 Init(), Clear() 코드 추가

ㄴ풀링의 경우 특수하기 때문에 최대한 마지막에 Clear()해주는 것이 좋다.

3. 실행

💡 풀링할 대상인 UnityChan에 Poolable.cs 컴포넌트를 추가해준다.

  • LoginScene.cs에 유니티짱 2개 생성

  • 유니티짱 생성 후 바로 해제

📄참고자료
[인프런] c#과 유니티로 만드는 MMORPG 게임 개발 시리즈_3. 유니티 엔진
Unity Learn_Object Pooling

profile
Unity 개발자 취준생의 개발로그, Slow and steady wins the race !

0개의 댓글