유니티2D 입문 정리 10 - 오브젝트 풀 구현

woollim·2024년 11월 9일
0

0. 핵심 내용

○ 오브젝트 풀링

  • 게임 개발에 널리 사용되는 테크닉으로, 게임의 성능을 개선하기 위해 사용됩니다.
  • 오브젝트 풀링은 객체를 미리 생성해 두고 필요할 때 가져다 사용하고, 사용이 끝나면 다시 풀에 반납하는 방식을 말합니다.
  • 오브젝트 풀링은 생성(Instantiate)과 소멸(Destroy)이라는 비용이 큰 작업을 최소화함으로써 성능을 향상시키는 데 중요한 역할을 합니다.
  • 이는 특히 빈번하게 생성하고 파괴되는 객체(예: 총알, 입자 등)에 대해 중요하며, 이런 객체들을 풀에 저장해 놓고 재사용함으로써 메모리 할당과 가비지 컬렉션에 따른 성능 저하를 방지할 수 있습니다.
  • 오브젝트 풀링은 적절히 사용하면 큰 성능 개선을 가져올 수 있지만, 불필요한 메모리 사용을 증가시킬 수 있으므로 사용 시에는 신중해야 합니다. 오브젝트 풀의 크기를 적절히 조절하는 것이 중요합니다.


1. 오브젝트 풀 만들기

○ ObjectPool 만들기

using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : MonoBehaviour
{
    // 오브젝트 풀 데이터를 정의할 데이터 모음 정의
    [System.Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;
    }

    public List<Pool> Pools;
    public Dictionary<string, Queue<GameObject>> PoolDictionary;

    private void Awake()
    {
        // 인스펙터창의 Pools를 바탕으로 오브젝트풀을 만들 것. 
        // 오브젝트풀은 오브젝트마다 따로이며, pool개수를 넘어가면 강제로 끄고 새로운 오브젝트에게 할당.
        PoolDictionary = new Dictionary<string, Queue<GameObject>>();
        foreach (var pool in Pools)
        {
            // 큐는 FIFO(First-in First-out) 구조로서, 줄을 서는 것처럼 가장 오래 줄 선(enqueue) 객체가 가장 먼저 빠져 나올(dequeue) 수 있는 구조
            Queue<GameObject> objectPool = new Queue<GameObject>();
            for (int i = 0; i < pool.size; i++)
            {
                // Awake하는 순간 오브젝트풀에 들어갈 Instantitate 일어나기 때문에 터무니없는 사이즈 조심
                GameObject obj = Instantiate(pool.prefab);
                obj.SetActive(false);
                // 줄의 가장 마지막에 세움.
                objectPool.Enqueue(obj);
            }
            // 접근이 편한 Dictionary에 등록
            PoolDictionary.Add(pool.tag, objectPool);
        }
    }

    public GameObject SpawnFromPool(string tag)
    {
        // 애초에 Pool이 존재하지 않는 경우
        if (!PoolDictionary.ContainsKey(tag))
            return null;

        // 제일 오래된 객체를 재활용
        GameObject obj = PoolDictionary[tag].Dequeue();
        PoolDictionary[tag].Enqueue(obj);
				obj.SetActive(true);
        return obj;
    }
}

○ 오브젝트 풀 적용하기

  • 임시로 Character에 ObjectPool 컴포넌트 추가.
    (현재 상황에서는 플레이어가 오브젝트 풀 가지고 있어도 괜찮으나, 적이 등장하면서 대응할 수 없는 구조임. 적 구현하면서 수정 예정)
  • 다음과 같이 Object Pool 수정

    🚧 Prefab의 [⊙]를 눌러도 Arrow 뜨지 않습니다.
    프리팹은 씬에 직접 배치되어 있는 것이 아니기 때문입니다. 드래그앤드랍해주세요.

○ TopDownShooting 수정

using UnityEngine;

public class TopDownShooting : MonoBehaviour
{
    private TopDownController contoller;

    // TODO 나중에 GameManager를 만들어서 옮길 예정 (모든 오브젝트는 오브젝트풀을 쓸 권리가 있으니까)
    private ObjectPool objectPool;
    private Vector2 aimDirection = Vector2.right;

    [SerializeField] private Transform projectileSpawnPosition;

    public GameObject testPrefab;

    private void Awake()
    {
        contoller = GetComponent<TopDownController>();
        objectPool = GetComponent<ObjectPool>();
    }

    void Start()
    {
        contoller.OnAttackEvent += OnShoot;
        // OnLookEvent에 이제 두개가 등록되는 것(하나는 지난 시간에 등록했었죠? TopDownAimRotation.OnAim(Vec2)
        // 한 개의 델리게이트에 여러 개의 함수가 등록되어있는 것을 multicast delegate라고 함.
        // Action이나 Func도 델리게이트의 일종인 것 기억하시죠..?
        contoller.OnLookEvent += OnAim;
    }

    private void OnAim(Vector2 newAimDirection)
    {
        aimDirection = newAimDirection;
    }

    private void OnShoot(AttackSO attackSO)
    {
        RangedAttackSO RangedAttackSO = attackSO as RangedAttackSO;
        float projectilesAngleSpace = RangedAttackSO.multipleProjectilesAngel;
        int numberOfProjectilesPerShot = RangedAttackSO.numberofProjectilesPerShot;

        float minAngle = -(numberOfProjectilesPerShot / 2f) * projectilesAngleSpace + 0.5f * RangedAttackSO.multipleProjectilesAngel;


        for (int i = 0; i < numberOfProjectilesPerShot; i++)
        {
            float angle = minAngle + projectilesAngleSpace * i;
            float randomSpread = Random.Range(-RangedAttackSO.spread, RangedAttackSO.spread);
            angle += randomSpread;
            CreateProjectile(RangedAttackSO, angle);
        }
    }

    private void CreateProjectile(RangedAttackSO RangedAttackSO, float angle)
    {
        // 오브젝트 풀을 활용한 생성으로 변경
        GameObject obj = objectPool.SpawnFromPool(RangedAttackSO.bulletNameTag);
        
        // 발사체 기본 세팅
        obj.transform.position = projectileSpawnPosition.position;
        ProjectileController attackController = obj.GetComponent<ProjectileController>();
        attackController.InitializeAttack(RotateVector2(aimDirection, angle), RangedAttackSO);
    }

    private static Vector2 RotateVector2(Vector2 v, float degree)
    {
        return Quaternion.Euler(0, 0, degree) * v;
    }
}

0개의 댓글