개념

Object Pooling이란 최적화를 하는 방법 중 하나이다. 주로 생성(Instantiate)과 소멸(Destroy)을 반복하는 오브젝트에 적용한다. 이는 힙 영역에 빈번한 메모리의 할당과 해제가 일어나는 것을 막아줘서, Garbage Collector의 부담을 줄인다.

 

적용 방식

Pool Manager에 모든 Instantiate를 위임한다. (Spawner와 같은 개별 객체가 Instantiate를 하기 위해선 public static PoolManager instance;처럼 선언했다는 가정 하에, PoolManager.instance.MyInstantiate(~~~)처럼, 반드시 PoolManager를 거쳐가야 한다)

 

MyInstantiate()는:

1. 만일 화면 내에 Instantiate를 하고 싶은 객체가 SetActive(false) 상태로 존재한다면? 그걸 SetActive(true)한다. 

2. 만일 화면 내에 Instantiate를 하고 싶은 객체가 없다면? Instantiate()한다. 

 

여기에서 핵심은,

원래는 Destroy(gameObject) 후 Instantiate를 함으로써 Instantiate와 Destroy를 반복하며 객체를 사용했다면,

Object Pooling에서는, 원래는 Destroy()를 할 부분에 SetActive(false)를 넣어서 오브젝트를 재사용한다는 것이다. 

 

Pool Manager

using UnityEngine;
using System.Collections.Generic;

public class PoolManager : MonoBehaviour
{
    public static PoolManager instance;

    private List<GameObject>[] pools; // 한 칸에 리스트 한 개를 담는 2차원 배열
    [SerializeField] private GameObject[] all_prefabs;

    private void Awake()
    {
        instance = this;

        pools = new List<GameObject>[all_prefabs.Length];
        for (int i = 0; i < pools.Length; i++) // prefab의 종류만큼 list를 만든다.
        {
            pools[i] = new List<GameObject>();
        }
    }

    // about "index"
    // Bird(0), Net(1)...
    public GameObject Get(int index)
    {
        GameObject select = null;

        for (int i = 0; i < pools[index].Count; i++)
        {
            if (pools[index][i].activeSelf == false)
            {
                select = pools[index][i];
                select.SetActive(true);
                break;
            }
        }

        if (select == null)
        {
            select = Instantiate(all_prefabs[index], transform);
            pools[index].Add(select);
        }

        return select;
    }

    public GameObject Get(int index, Vector2 position) // overloading, 특정 위치에 스폰해야 하는 경우
    {
        GameObject select = null;

        foreach (GameObject item in pools[index])
        {
            if (item.activeSelf == false)
            {
                select = item;
                select.transform.position = position;
                select.SetActive(true);
                break;
            }
        }

        if (select == null)
        {
            select = Instantiate(all_prefabs[index]);
            select.transform.position = position;
            pools[index].Add(select);
        }

        return select;
    }
}

 

public static PoolManager instance;는 다른 코드에서도 PoolManager를 자유롭게 참조하기 위함이다. 예를 들어 Spawner 코드에서...

 

Spawner

using UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour
{
    private int level;

    private void Awake()
    {
        level = 1;
    }

    private void Start()
    {
        StartCoroutine(SpawnEnemy());
    }

    IEnumerator SpawnEnemy()
    {
        while (true)
        {
            PoolManager.instance.Get(Random.Range(0, level));
            yield return new WaitForSeconds(1f);
        }
    }
}

 

이런 식으로 쉽게 사용할 수 있다. 여기에선 Get이 MyInstantiate와 같은 역할을 하고 있다. (참고로 0은 Bird 객체를 나타낸다) 그럼 Object Pooling을 적용한 객체는 어떤 식으로 구성되어 있는지 보자.

 

Bird - Enemy 클래스 상속

using UnityEngine;

public class Bird : Enemy
{
    private Rigidbody2D rb;
    private float speed;
    private int lane;

    private void Awake()
    {
        // Enemy class
        max_health = 5;

        // my class
        rb = GetComponent<Rigidbody2D>();
        speed = -5f;
        // lane (매번 OnEnable마다 랜덤으로 할당)
    }

    protected override void Start()
    {
        base.Start();
        lane = Random.Range(-1, 2); // -1 ~ 1
        transform.position = new Vector2(10, lane * 3.5f);
        rb.linearVelocity = new Vector2(speed, 0);
    }

    private void OnEnable()
    {
        Start(); // 이해하기 쉽도록 이렇게 짬. 익숙해지면 Start를 지울 것 같다!
    }

    private void Update()
    {
        if (transform.position.x < -15f)
        {
            gameObject.SetActive(false);
        }
    }
}

 

이 코드를 보면 화면 밖으로 날아갔을 시 SetActive(false)를 하고, SetActive(true)가 된 순간을 OnEnable()을 통해 포착하여 초기화를 해주고 있는 것을 볼 수 있다. (참고로 Enemy class를 상속하고 있다)

 

Enemy (참고용)

using UnityEngine;

public class Enemy : MonoBehaviour
{
    protected float health;
    protected float max_health;
    [SerializeField] private GameObject[] effect_particle;

    protected virtual void Start()
    {
        InitStat();
    }

    private void InitStat()
    {
        health = max_health;
    }

    public void TakeDamage(float damage)
    {
        if (health - damage > 0)
        {
            GenerateEffect("Hit");
            health -= damage;
        }
        else
        {
            Die();
        }
    }

    private void Die()
    {
        GenerateEffect("Die");
        gameObject.SetActive(false);
    }

    private void GenerateEffect(string effect_type)
    {
        if (effect_type == "Hit")
        {
            for (int i = 0; i < 10; i++) { GameObject temp = Instantiate(effect_particle[0], transform.position, transform.rotation); }
        }
        else if (effect_type == "Die")
        {
            for (int i = 0; i < 20; i++) { GameObject temp = Instantiate(effect_particle[1], transform.position, transform.rotation); }
        }
    }
}

+ Recent posts