Unity 최종프로젝트 TIL

Unity 최종프로젝트 TIL 4일차 25/04/09 [오브젝트풀 구조 변경]

kk2247401 2025. 4. 9. 23:33

오늘 구현한 기능

다양한 오브젝트풀을 쉽게 추가,관리 할수있는 구조의 오브젝트풀 작성

 

목표

다양한 오브젝트풀을 각각 오브젝트풀cs를 만들어 생성할 필요없이 추가

오브젝트풀로 생성되는 다양한 프리펩들의 초기화Init 메서드가 다양한 매개변수를 가질수있고 맞춰서 사용가능

 

 

완성된 구조 사용방법 (오브젝트풀 구조)

Pool구조로 관리할 프리펩 생성

Pool로 관리할 Prefab에 붙여야할 스크립트 코드

Pool로 관리할 프리펩에 BasePoolable을 상속받은 스크립트를 생성, 붙임

BasePoolable에서 abstarct 된 Init() 을 구현

Init을 해당 프리펩에 맞게 오버로드  (예시에선 : Init(float height, int damage) )

 

 

새로운 풀 추가

PoolManager를 통해 새 오브젝트풀 추가,관리

 

PoolManager 오브젝트와 스크립트를 생성하여 전체 오브젝트풀들을 추가, 관리

 

Inspector창에서 간단하게 새로운 오브젝트풀 추가가능

 -> Pool Configs 를 추가 -> Pool로 관리할 프리펩을 할당 -> 프리펩이 생성될 부모Pool오브젝트 할당 (PoolManager 자식 오브젝트로 생성하여 할당) -> 초기생성할 개수 입력

 

사용방법

Explosion프리펩 생성코드
Pool로 관리되는 Explosion(폭발효과)과 해당 프리펩을 생성하는 코드

기본적으로 PoolManager는 싱글톤, 전역 접근가능

PoolManager.Instance.Get<가져올프리펩에 붙은 스크립트 클래스>().Init(가져올 프리펩에 맞는 초기화 매개변수들);

이 방법으로 생성

 

 

 

구현방법 ( 오브젝트풀 매니저, 오브젝트풀, 프리펩 구조)

핵심 원리

제네릭 T (=BasePoolable을 상속받은 클래스) 를 이용하여 ObjectPool을 제네릭클래스로 구현

같은 ObjectPool클래스를 다양한 클래스의 오브젝트풀로 사용가능

 

PoolManager 풀 매니저 코드 (싱글톤, 전역접근가능)

코드 텍스트

더보기

[Serializable]
public class PoolConfig     //새로 풀을 추가할때 필요한 정보
{
    public BasePoolable prefab;     //오브젝트풀 구조로 관리할 프리펩
    public Transform parent;        //해당 오브젝트를 생성할 부모
    public int initialSize = 10;    //초기 생성해둘 프리펩 수
}
public class PoolManager : Singleton
{
    [SerializeField] private List poolConfigs;      //풀을 추가할때 쓰는 리스트

    private Dictionary<type, objectpool> poolDict = new();    //리스트의 풀을 저장하는 딕셔너리</type, objectpool


    protected override void Awake()
    {
        base.Awake();

        for(int i = 0; i < poolConfigs.Count; i++)      //관리할 오브젝트풀을 생성하고 프리펩의 '클래스를 키값으로 사용하여 불러올수있도록' 리스트의 값들을 딕셔너리에 저장
        {
            ObjectPool pool = new ObjectPool(poolConfigs[i].prefab, poolConfigs[i].initialSize, poolConfigs[i].parent);
            poolDict.Add(poolConfigs[i].prefab.GetType(), pool);
        }
    }
    public T Get() where T : BasePoolable            //제네릭T 에 받은 생성할 프리펩의 클래스를 키값으로 딕셔너리에서 오브젝트풀을 불러와 Get을 호출하여 입력된 T값에 맞는 프리펩클래스를 반환
    {
        var type = typeof(T);       //입력받은 제네릭T 값을 키값으로 정의

        if (poolDict.TryGetValue(type, out var pool))   //키값으로 딕셔너리의 오브젝트풀을 호출
            return (T)pool.Get();                       //호출한 오브젝트풀에서 오브젝트를 생성, 제네릭T로 형변환하여 호출 

        Debug.LogWarning($"{type}에 대한 풀을 찾을 수 없습니다.");
        return null;
    }
}

   return (T)pool.Get();     //호출한 오브젝트풀에서 오브젝트를 생성, 제네릭T로 형변환하여 호출 

해당 코드를 통해 Init을 각각의 클래스에서 오버로드 한 Init을 사용가능

 

 

ObjectPool 오브젝트풀 코드 (제네릭 클래스) 중요

코드텍스트

더보기

public class ObjectPool<T> where T : BasePoolable
{
    private Queue<T> pool = new();
    private T _prefab;
    private Transform _parent;

    //생성자, 초기화 (프리펩,초기생성수,부모)
    public ObjectPool(T prefab, int initialSize, Transform parents) 
    {
        _prefab = prefab;
        _parent = parents;

        for(int i = 0; i < initialSize; i++)
        {
            CreatNew();
        }
    }

    T CreatNew()        //부족할경우 추가생성
    {
        T obj = Object.Instantiate(_prefab, _parent);
        obj.SetPool(this as ObjectPool<BasePoolable>);
        obj.gameObject.SetActive(false);
        pool.Enqueue(obj);
        return obj;
    }

    public T Get()      //프리펩을 (생성)활성화, 해당 프리펩의 스크립트를 제네릭T를 이용해 반환
    {
        if(pool.Count == 0)
        {
            CreatNew();
        }

        T obj = pool.Dequeue();
        obj.gameObject.SetActive(true);
        return obj;
    }
    public void ReturnToPool(T obj)
    {
        pool.Enqueue(obj);
    }
}

제네릭 T를 이용하여 이 클래스 하나로 다양한 클래스의 프리펩을 오브젝트풀로 관리,저장 가능

 

 

BasePoolable 풀로관리할 클래스가 상속받는 베이스

코드텍스트

더보기

public abstract class BasePoolable : MonoBehaviour
{
    protected ObjectPool<BasePoolable> _pool;

    //풀 설정
    public virtual void SetPool(ObjectPool<BasePoolable> pool)
    {
        _pool = pool;
    }

    //반드시 오버로딩하여 사용
    public abstract void Init();

    public virtual void ReturnToPool()
    {
        gameObject.SetActive(false);
        _pool.ReturnToPool(this);
    }
}

해당 클래스를 상속받은 오브젝트 클래스 예시 Explosion(폭발이펙트 프리펩)

코드 텍스트

더보기

public class Explosion : BasePoolable
{
    [SerializeField] List<Sprite> _sprites;
    [SerializeField] SpriteRenderer _spriteRenderer;
    [SerializeField] Collider2D _collider;
    List<Player> hitPlayers = new List<Player>();
    int _damage;

    private void OnEnable()
    {
        //폭발 사운드 삽입
    }

    public override void Init()
    {
        //호출용 실제 초기화는 오버로드한 init에서 실행
    }

    public void Init(Vector3 position,int damage, float size = 1, int spriteNum = 0)
    {
        transform.position = position;
        _damage = damage;
        transform.localScale = Vector3.one * size;
        _spriteRenderer.sprite = _sprites[spriteNum];
        
    }
    public void StartExplosion()
    {
        Collider2D[] hitplayers = Physics2D.OverlapCircleAll(transform.position, 0.5f, LayerMask.NameToLayer("Player"));

        for(int i = 0; i < hitplayers.Length; i++)
        {
            if(hitplayers[i].TryGetComponent<Player>(out Player player))
            {
                //데미지주기 코드 삽입필요
                hitPlayers.Add(player);
            }
        }
        _collider.enabled = true;
    }
    private void OnTriggerStay2D(Collider2D collision)
    {
        if(collision.TryGetComponent<Player>(out Player player) && !hitPlayers.Contains(player))
        {
            //데미지주기 코드 삽입필요
            hitPlayers.Add(player);
        } 
    }
    public void StopDamage()
    {
        _collider.enabled = false;
    }

    
}

 

Init을 각각의 필요성에 맞게 오버로드하여 매개변수를 다르게 받음

 

 

생성된 Explosion 사용예시

PoolManager.Instance.Get<Explosion>().Init(targetPosition + (Vector3.down * 1f), damage, explosionSize);

 

간단하게

PoolManager.Instance.Get<생성할 프리펩 클래스>( ).Init(클래스에 맞게 오버로드된 초기화 매개변수);

방법으로 전역적으로 접근,생성가능

 

 

결론

인스펙터 창에서 간단히 새로운 오브젝트풀을 추가가능

만들어진 오브젝트풀의 오브젝트를 간단한 코드로 전역적으로 접근,생성 가능