[25.04.01] TIL(오브젝트 풀링)

설민우·2025년 4월 1일

내일배움캠프 - Unity

목록 보기
12/85

< 오브젝트 풀링과 프로젝트 >

오늘은 기존의 CS 공부와 프로그래머스 문제 풀이에서 벗어나, 전일 언급했던 오브젝트 풀링에 대해서 공부하고, 이를 직접 프로젝트에서 적용해 보았습니다.

( 1. 오브젝트 풀링이란 )

오브젝트 풀링은 게임이나 앱에서 성능을 최적화 시키기 위해 자주 사용되는 디자인 패턴입니다. 이는, 자주 생성되고 파괴되는 오브젝트를 미리 생성하여 관리하고, 사용이 끝났으면 Destory 하는 것이 아닌 Pool에 반환합니다. 또한 다시 필요할때도 인스턴스로 생성하는것이 아닌 Pool에서 가져오는 형태로 구성됩니다.

( 2. 오브젝트 풀링의 장점 )

    1. 메모리 할당 및 해제의 비용 및 절감 : 객체를 매번 생성하고 삭제하는 것은 GC에 의해서 성능 저하를 유발 할 수 있습니다. 오브젝트 풀링은 이를 해겷하는데 도움을 줍니다.
    1. 빠른 객체 관리 : 자주 사용되는 객체를 바로 풀에서 꺼내고 반환하기 때문에 프레임 상승에 도움을 줍니다.
    1. 객체 관리의 용이성 : 객체를 Pool 이라는 한 지점에서 관리하기 때문에 객체 관리가 쉬워집니다.

( 3. 유니티에서의 오브젝트 풀링 )

유니티에서는 이러한 오브젝트 풀링을 적, 탄환, 아이템, 이벤트 등과 같이 자주 생성되고 삭제되는 객체들에게 사용합니다.

( 4. 구현 )

먼저 PoolManager를 작성해 Pool 클래스를 만들고, 이를 생성하고, 오브젝트를 추가하고 가져오고 돌려놓는 기능을 작성합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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(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
    //풀링 오브젝트들의 부모
    Transform _root;
    Dictionary<string, Pool> _pool = new Dictionary<string, Pool>();
    public void Init()
    {
        if(_root == null)
        {
            _root = new GameObject { name = "@Pool_Root" }.transform;
            Object.DontDestroyOnLoad(_root);
        }
    }

    public void CreatePool(GameObject original, int count = 5)
    {
        if (_pool.ContainsKey(original.name))
        {
            AddToPool(original.name, count);
            return;
        }

        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();
    }
    public void AddToPool(string name, int count)
    {
        if (!_pool.ContainsKey(name))
        {
            Debug.LogWarning($"풀링 오브젝트 {name}이(가) 존재하지 않습니다.");
            return;
        }

        Pool pool = _pool[name];
        for (int i = 0; i < count; i++)
        {
            pool.Push(pool.Pop(null)); // 새로운 오브젝트를 생성 후 다시 풀에 넣음
        }
    }
}

이렇게 딕셔너리 형태로 구성된 pool 에는 PoolAble 스크립트를 포함한 객체가 등록되게 되고, 객체는 각각의 Root_OriginalName 밑으로 복속됩니다.

구체적인 외부에서의 사용 흐름은 아래와 같습니다.

    1. Pool 생성 : 오브젝트와 그 갯수를 지정하여 먼저 Pool을 생성합니다.
    1. Pop : Pop을 통해서 해당 오브젝트가 필요할때 받아옵니다
    1. Push : 해당 오브젝트가 더 이상 필요없을때 Destroy대신 사용합니다.
    1. AddToPool : 특정 오브젝트의 갯수가 더 필요할때 풀에 추가 등록합니다.
    1. Clear : 해당 Pool이 더 이상 필요 없을때 파기합니다.

( 5. 적용 )

적용된 영상은 유튜브 링크로 등록 해두었습니다.

링크텍스트

profile
클라이언트 개발자를 지망하고 있습니다.

0개의 댓글