[Unity] 유니티 오브젝트풀 패턴 구현 2 ( ObjectPool Pattern )

TNT·2023년 11월 25일
0

유니티 디자인패턴

목록 보기
7/14
post-thumbnail

오브젝트풀 패턴 2번째이다
이번엔 여러 가지 오브젝트를 관리하는 코드를 보자 오브젝트 풀 패턴에서 잘 모르면
저번 게시글을 한번 보고 오자

이번에는 해외사이트 돌아다니다가 코드 가져오긴 했는데 몇 년 전에 본 거라서
출처는 찾을 수 없었다.

먼저 코드부터 보자 이 오브젝트 풀 패턴을 사용하려면 조건이 조금 있다.
잘 보면서 따라오면 사용 가능하다.

코드

ObjectPooler.cs

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

public class ObjectPooler : MonoBehaviour
{
    static ObjectPooler inst;
    void Awake() => inst = this;

    [Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;
    }

    [SerializeField] Pool[] pools;
    List<GameObject> spawnObjects;
    Dictionary<string, Queue<GameObject>> poolDictionary;

    public static GameObject SpawnFromPool(string tag, Vector3 position) =>
        inst._SpawnFromPool(tag, position, Quaternion.identity);

    public static GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation) =>
        inst._SpawnFromPool(tag, position, rotation);

    public static T SpawnFromPool<T>(string tag, Vector3 position) where T : Component
    {
        GameObject obj = inst._SpawnFromPool(tag, position, Quaternion.identity);
        if (obj.TryGetComponent(out T component))
            return component;
        else
        {
            obj.SetActive(false);
            throw new Exception($"Component not found");
        }
    }

    public static T SpawnFromPool<T>(string tag, Vector3 position, Quaternion rotation) where T : Component
    {
        GameObject obj = inst._SpawnFromPool(tag, position, rotation);
        if (obj.TryGetComponent(out T component))
            return component;
        else
        {
            obj.SetActive(false);
            throw new Exception($"Component not found");
        }
    }

    public static List<GameObject> GetAllPools(string tag)
    {
        if (!inst.poolDictionary.ContainsKey(tag))
            throw new Exception($"Pool with tag {tag} doesn't exist.");

        return inst.spawnObjects.FindAll(x => x.name == tag);
    }

    public static List<T> GetAllPools<T>(string tag) where T : Component
    {
        List<GameObject> objects = GetAllPools(tag);

        if (!objects[0].TryGetComponent(out T component))
            throw new Exception("Component not found");

        return objects.ConvertAll(x => x.GetComponent<T>());
    }

    public static void ReturnToPool(GameObject obj)
    {
        if (!inst.poolDictionary.ContainsKey(obj.name))
            throw new Exception($"Pool with tag {obj.name} doesn't exist.");

        inst.poolDictionary[obj.name].Enqueue(obj);
    }

    GameObject _SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
    {
        if (!poolDictionary.ContainsKey(tag))
            throw new Exception($"Pool with tag {tag} doesn't exist.");

        Queue<GameObject> poolQueue = poolDictionary[tag];
        if (poolQueue.Count <= 0)
        {
            Pool pool = Array.Find(pools, x => x.tag == tag);
            var obj = CreateNewObject(pool.tag, pool.prefab);
            ArrangePool(obj);
        }

        GameObject objectToSpawn = poolQueue.Dequeue();
        objectToSpawn.transform.position = position;
        objectToSpawn.transform.rotation = rotation;
        objectToSpawn.SetActive(true);

        return objectToSpawn;
    }

    void Start()
    {
        spawnObjects = new List<GameObject>();
        poolDictionary = new Dictionary<string, Queue<GameObject>>();

        // 미리 생성
        foreach (Pool pool in pools)
        {
            poolDictionary.Add(pool.tag, new Queue<GameObject>());
            for (int i = 0; i < pool.size; i++)
            {
                var obj = CreateNewObject(pool.tag, pool.prefab);
                ArrangePool(obj);
            }

            // OnDisable에 ReturnToPool 구현여부와 중복구현 검사
            if (poolDictionary[pool.tag].Count <= 0)
                Debug.LogError($"{pool.tag} Set ypur object code => ObjectPooler.ReturnToPool(gameObject);");
            else if (poolDictionary[pool.tag].Count != pool.size)
                Debug.LogError($"{pool.tag} Tag has the same name in ReturnToPool");
        }
    }

    GameObject CreateNewObject(string tag, GameObject prefab)
    {
        var obj = Instantiate(prefab, transform);
        obj.name = tag;
        obj.SetActive(false);
        return obj;
    }

    void ArrangePool(GameObject obj)
    {
        bool isFind = false;
        for (int i = 0; i < transform.childCount; i++)
        {
            if (i == transform.childCount - 1)
            {
                obj.transform.SetSiblingIndex(i);
                spawnObjects.Insert(i, obj);
                break;
            }
            else if (transform.GetChild(i).name == obj.name)
                isFind = true;
            else if (isFind)
            {
                obj.transform.SetSiblingIndex(i);
                spawnObjects.Insert(i, obj);
                break;
            }
        }
    }
}

새로 ObjectPooler.cs를 만들어 준다.

여기서 스폰하는 것
public static GameObject SpawnFromPool(string tag, Vector3 position)
public static GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
으로 쓰고 리턴하는 것
public static void ReturnToPool(GameObject obj)
으로 사용한다.

이러고 여기에 사용될 bullet 2가지를 만들어 준다.
Bullet.cs Bullet2.cs 2가지를 만든다.

Bullet.cs

using UnityEngine;

public class Bullet : MonoBehaviour
{
    private Vector3 direction;
    public void Shoot(Vector3 direction)
    {
        this.direction = direction;
        Invoke("DestroyBullet", 5f);
    }

    public void DestroyBullet()
    {
        gameObject.SetActive(false);
    }

    private void OnDisable()
    {
        ObjectPooler.ReturnToPool(gameObject);
    }
    void Update()
    {
        transform.Translate(direction);
    }
}

Bullet2.cs

using UnityEngine;

public class Bullet2 : MonoBehaviour
{
    private Vector3 direction;
    public void Shoot(Vector3 direction)
    {
        this.direction = direction;
        Invoke("DestroyBullet", 5f);
    }

    public void DestroyBullet()
    {
        gameObject.SetActive(false);
    }

    private void OnDisable()
    {
        ObjectPooler.ReturnToPool(gameObject);
    }
    void Update()
    {
        transform.Translate(direction);
    }
}

여기서 보면 오브젝트 풀에 리턴할 때
오브젝트를 SetActive()으로 끄고
리턴하는 함수는 OnDisable() 안에 넣어둬야 한다.

그러면 이제 소환할 플레이어를 만들어보자.

TestPlayer.cs

using UnityEngine;

public class TestPlayer : MonoBehaviour
{
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            var bullet = ObjectPooler.SpawnFromPool("bullet", Vector3.zero); 
            var direction = new Vector3(transform.position.x, transform.position.y, transform.position.z) - transform.forward;
            bullet.transform.position = direction.normalized;
            bullet.GetComponent<Bullet>().Shoot(direction.normalized);
        }
        if (Input.GetMouseButtonDown(1))
        {
            var bullet = ObjectPooler.SpawnFromPool("bullet2", Vector3.zero);
            var direction = new Vector3(transform.position.x, transform.position.y, transform.position.z) - transform.forward;
            bullet.transform.position = direction.normalized;
            bullet.GetComponent<Bullet2>().Shoot(direction.normalized);
        }
    }

}

SpawnFromPool()으로 가져온다.
스폰할 때 이제 여러 오브젝트를 들고 오기 때문에 bullet과 bullet2를 소환해 보자.

코드 작성 후에 유니티로 돌아가자.
먼저 코드 2개를 넣은 bullet 프리펩을 2개를 만들자.


bullet2 도 같이 세팅해 주자.

유니티에 오브젝트 하나 만들고 거기에 세팅하자.
거기엔 ObjectPooler 넣고 Pools 안에 사용할 프리펩 넣자
Tag는 코드에서 불러올 이름이고 Size는 만들어질 개수이다.

그 후 오브젝트 하나 만들고 TestPlayer 넣고 실행해서 우클릭 좌클릭 하면서 확인해 보자.

여러 프리펩을 오브젝트 풀로 관리하는 모습니다.

profile
개발

0개의 댓글