Deff_Dev

[Unity/C#] 오브젝트 풀 구현 본문

Unity(유니티)/유니티 공부

[Unity/C#] 오브젝트 풀 구현

Deff_a 2024. 5. 19. 23:29

 

게임을 만들다보면 프리팹으로 저장된 오브젝트를 생성, 삭제해야할 경우가 생긴다.

 

예를 들어, 총알을 발사하는 경우를 생각해보자.

이때, 총알 프리팹을 저장한 뒤, Instantiate를 이용하여 총알을 생성하고, 적이 총알에 맞았거나 총알이 카메라를 벗어났을 때 Destroy를 이용하여 발사된 총알을 삭제하게 된다.

 

이때, Destroy 함수를 이용하여 오브젝트를 삭제한다면, 해당 오브젝트의 메모리가 깔끔하게 전부 삭제될까 ?

 

정답은 '아니다' 이다. (깔끔하게 메모리가 삭제된다면 복잡하게 오브젝트 풀을 쓸 이유가 없다.)

 

물론 몇개만 Destroy한다고 게임에 큰 지장을 주진 않을 것이다.

 

하지만 프로젝트의 볼륨이 커져서, 오브젝트(Bullet, Enemy)의 생성/삭제의 빈도 수가 늘어난다면 게임 성능에 큰 영향을 미칠 수 있다.

 

그렇기 때문에 오브젝트를 비활성화시켜 삭제된 것 처럼 보이게 하고, 비활성화된 오브젝트를 재사용하는 오브젝트 풀링을 사용한다.


오브젝트 풀링은 구현 방법이 여러가지가 존재한다. 

  1. 비활성화된 오브젝트가 없을 때마다 새로운 오브젝트를 생성하는 방법
  2. 미리 일정 갯수의 오브젝트들을 생성한 뒤, 비활성화 해놓고 재사용하는 방법 

PoolObject를 저장하는 방식도 여러가지가 존재한다. (리스트, 큐, 딕셔너리 등등)

 

이처럼 오브젝트 풀링을 구현하는 방법에는 정해진 틀이 없으며, 구현하는 사람마다 각기 다른 방법을 사용할 수 있다.

 

이 글에서는 2번 방법과 딕셔너리를 이용하여 오브젝트 풀링을 구현하는 방법에 대해 설명하겠다.


구현

using System.Collections.Generic;
using UnityEngine;

public class PoolManager : MonoBehaviour
{
    [System.Serializable]
    public class Pool
    {
        public string tag; // key 값
        public GameObject prefab; // 실제 생성될 오브젝트
        public int size; // 한번에 몇개를 생성할 것인지
        public Transform parentTransform; // 부모 오브젝트
    }

    [Header("# Pool Info")] 
    [SerializeField] private List<Pool> pools;
    private Dictionary<string, List<PoolObject>> poolDictionary;
    
    
    private void Awake()
    {
        // 딕셔너리 초기화
        poolDictionary = new Dictionary<string, List<PoolObject>>();
        
        // pools에 있는 모든 오브젝트를 탐색하고 정해놓은 size만큼 프리팹을 미리 만들어 놓음
        foreach (Pool pool in pools)
        {
            List<PoolObject> list = new List<PoolObject>();
            poolDictionary.Add(pool.tag,list);
            AddPoolObject(pool.tag);
        }
    }

 
    private void AddPoolObject(string tag) // 프리팹 생성
    {
        Pool pool = pools.Find(obj => tag == obj.tag);
        
        // pool.size만큼 프리팹을 생성 -> 비활성화 -> 리스트에 넣어줌
        for (int i = 0; i < pool.size; i++)
        {
            PoolObject poolObj = Instantiate(pool.prefab, pool.parentTransform).GetComponent<PoolObject>();
            poolObj.gameObject.SetActive(false);
            poolDictionary[tag].Add(poolObj);
        }
    }

    // 이미 생성된 오브젝트 풀에서 프리팹을 가져옴
    public PoolObject SpawnFromPool(string tag)
    {
        if (!poolDictionary.ContainsKey(tag))
        {
            return null;
        }

        PoolObject poolObject = null;
        
        for (int i = 0; i < poolDictionary[tag].Count; i++)
        {
            if (!poolDictionary[tag][i].gameObject.activeSelf) // 비활성화된 오브젝트를 찾았을 때
            {
                poolObject = poolDictionary[tag][i];
                break;
            }

            if (i == poolDictionary[tag].Count - 1) // 비활성화가 된 오브젝트가 없다면
            {
                AddPoolObject(tag);
                poolObject = poolDictionary[tag][i + 1];
            }
        }
        
        poolObject.gameObject.SetActive(true); // 활성화
        
        return poolObject;
    }
}

여기서 ObjectPool 클래스는 오브젝트 풀링하는 오브젝트들을 하나의 딕셔너리로 관리하기 위해서 만든 부모 클래스로 이 부분을 각자 원하는 Class, GameObject로 바꿔서 작성하면 된다.

 

위와 같이 코드를 작성한 뒤 오브젝트에 붙이고 SpawnFromPool을 Tag에 맞게 호출한다면 오브젝트 풀링이 완성된다.