유니티 오브젝트 풀링(Unity object pooling)

2021. 6. 3. 15:26유니티(Unity)

오늘은 오브젝트 풀링을 쉬운 방법으로 만들어 보겠습니다.

 

총알같이 자주 생성되고 제거되는 오브젝트들은 생성하고 제거를 계속 반복하기에는 메모리에 부담이 됩니다. 렉이 잘 걸릴수 있다는 거죠. 이를 방지하기 위해서 오브젝트 풀링기법을씁니다. 계속 생성하고 제거하는거보단 맨처음 한번에

모두 생성해놓고, 쓸때만 활성화해서 쓰고 다쓰고나서 비활성화를 하면 하이어라키에는 남지만 생성하고 제거하는것 보다는 더 나은 방법이 됩니다. 두 방법의 차이는 쉽게 말하면 생성과 제거가 아니라 활성화와 비활성화입니다.

 

이 기능을 구현하기 위해서는 3가지 기능이 필요합니다. 오브젝트 풀러 생성, 오브젝트 활성화, 오브젝트 비활성화

다른분들은 이 기능을 다 한코드에 압축시켜서 간단하게 만들지만 저는 좀 더 쉬운방법으로 이해를 하기 위해 따로 만들겠습니다.

간단한 예제로 플레이어가 횡방향으로 이동하고 있을때 원을 생성하는 코드를 만들어보겠습니다.

C#코드 ObjectPooler, ObjectGenerator, ObjectDestroyer를 만들어 봅시다.

ObjectPooler의 역할은 오브젝트를 미리 생성 그리고 오브젝트가 필요할때 할당해주는 역할을 합니다.

using UnityEngine;

public class ObjectPooler : MonoBehaviour
{
    public GameObject pooledObject;
    public int numberOfObject;
    private List<GameObject> gameObjects;

    private void Start()
    {
        gameObjects = new List<GameObject>();
        for(int i =0; i < numberOfObject; i++)
        {
            GameObject gameObject = Instantiate(pooledObject);
            gameObject.SetActive(false);
            gameObjects.Add(gameObject);
        }
    }

    public GameObject GetPooledObject()
    {
        foreach (GameObject gameObject in gameObjects)
        {
            if (!gameObject.activeInHierarchy)
            {
                return gameObject;
            }
        }

        GameObject gameObj = Instantiate(pooledObject);
        gameObj.SetActive(false);
        gameObjects.Add(gameObj);
        return gameObj;
    }
}

한 단락씩 살펴보겠습니다.

public GameObject pooledObject;
public int numberOfObject;
private List<GameObject> gameObjects;

초기 변수와 오브젝트입니다. 각각 생성할 오브젝트, 미리 생성해둘 오브젝트의 수, 오브젝트를 저장해놓을 리스트

private void Start()
    {
        gameObjects = new List<GameObject>();
        for(int i =0; i < numberOfObject; i++)
        {
            GameObject gameObject = Instantiate(pooledObject);
            gameObject.SetActive(false);
            gameObjects.Add(gameObject);
        }
    }

Start부분에서는 리스트를 초기화해주고 오브젝트를 미리 만들어 놓습니다.

for문 안에서 생성할 오브젝트의 수만큼 반복해서 임시로 만들어 놓은 gameObject에 Instantiate를 이용해 오브젝트를 만들어 할당해 놓은뒤, 당장 사용하지 않기 때문에 SetActive로 disable 해놓고 gameObjects리스트에 추가를 해놓습니다

사용할 오브젝트들이 미리 생성되었고 사용될 준비가 되었습니다!

public GameObject GetPooledObject()
    {
        foreach (GameObject gameObject in gameObjects)
        {
            if (!gameObject.activeInHierarchy)
            {
                return gameObject;
            }
        }

        GameObject gameObj = Instantiate(pooledObject);
        gameObj.SetActive(false);
        gameObjects.Add(gameObj);
        return gameObj;
    }

GetPooledObject 메서드는 생성할 오브젝트 한개를 리턴합니다. 다음에 만들 ObjectGenerator에서 사용됩니다.

foreach문을 사용해서 위에서 만든 오브젝트 리스트를 하나씩 확인합니다. 만약 리스트안에 만들어진 오브젝트가 active상태라면 이미 사용되었다는 상태이고 그 반대이면 아직 사용되지 않았다는 뜻입니다.

if문으로 리스트안에 만약 사용되지 않은 오브젝트가 있다면 그 오브젝트를 리턴합니다.

만약 이미 만들어진 오브젝트들이 다 사용되고 있으면 어떻게 될까요?

리턴할 오브젝트가 없기 때문에 새로 오브젝트를 만들어서 사용해야합니다.

gameObj라는 임시 오브젝트에 아까 Start부분에서 했던것처럼 생성해서 할당해 리스트에 넣은뒤 그 오브젝트를 리턴합니다.

 

ObjectPooler까지 코드를 짜놓고 유니티 안에서 오브젝트를 만들어 봅시다.

생성될 원을 프리팹으로 3가지 종류정도 만들어 봅시다.

그리고 각 원에 적용할 풀러 3개, 그리고 ObjectGenerator를 담을 빈오브젝트를 사진처럼 만들어 놓읍시다.

CirclePooler1~3에 아까 만든 코드를 넣고 원 프리팹과 만들 개수를 정해놓읍시다.

그리고 메인 카메라 자식으로 오브젝트를 생성하고 제거할 기준점을 사진처럼 2개 만들어 놓아 적당한 거리에 배치해 둡시다. 이렇게 배치를 하면 카메라가 플레이어를 따라가면서 기준점또한 자동으로 따라가기 때문에 플레이어가 이동하면서 오브젝트가 생성되고 제거됩니다.

 

인제 ObjectGenerator를 만들어봅시다.

using UnityEngine;

public class ObjectGenerator : MonoBehaviour
{
    public Transform Right;

    public float maxGap;
    public float minGap;
    public ObjectPooler[] circlePoolers;


	private void Update()
    {
        if (transform.position.x < Right.position.x)
        {

            int random = Random.Range(0, circlePoolers.Length);
            float height = Random.Range(-1, 1);
            float gap = Random.Range(minGap, maxGap);
            transform.position = new Vector3(transform.position.x + gap, transform.position.y + height, transform.position.z);
            GameObject circle = circlePoolers[random].GetPooledObject();
            circle.transform.position = transform.position;
            circle.SetActive(true);
            transform.position = new Vector3(transform.position.x + gap, transform.position.y - height, transform.position.z);

        }
    }
}
    public Transform Right;

    public float maxGap;
    public float minGap;
    public ObjectPooler[] circlePoolers;

초기 변수, 오브젝트 설정입니다.

각각 오브젝트를 생성할 우측 기준점, 오브젝트 사이의 거리 최대,최소, 오브젝트풀러를 담을 배열

private void Update()
    {
        if (transform.position.x < Right.position.x)
        {

            int random = Random.Range(0, circlePoolers.Length);
            float height = Random.Range(-1, 1);
            float gap = Random.Range(minGap, maxGap);
            transform.position = new Vector3(transform.position.x + gap, transform.position.y + height, transform.position.z);
            GameObject circle = circlePoolers[random].GetPooledObject();
            circle.transform.position = transform.position;
            circle.SetActive(true);
            transform.position = new Vector3(transform.position.x + gap, transform.position.y - height, transform.position.z);

        }
    }

드디어 오브젝트를 생성하는 부분입니다!

만약에 ObjectGenerator의 x좌표가 우측 기준점보다 왼쪽에 있으면 오브젝트를 생성해야 우리가 원하는대로 오브젝트가 알맞게 생성됩니다. if문을 사용해 구현합시다.

Random.range를 사용해 어떤 원을 생성할지 랜덤하게 지정한다은 random변수에 할당합시다.

그리고 원이 생성될 위치를 조금씩 바꾸기 위해 height와 gap을 정해줍시다.

우리는 오브젝트를 ObjectGenerator의 위치에 생성할것이기 때문에 transform.position을 이용해 위치를 이동해줍시다.

아까 만들어둔 ObjectPooler에서 GetPooledObject메서드를 호출해 임시로 만든 circle에 할당해줍시다.

그러면 사용하지 않은 오브젝트중 하나가 리턴되어 circle에 할당됩니다.

circle의 위치를 ObjectGenerator의 위치로 이동한다음 SetActive를 하면 원이 성공적으로 생성됩니다!

코드를 CircleGenerator에 넣고 CircleGenerator를 플레이어 우측 적당한곳에 둡시다.

게임을 실행하면 성공적으로 원들이 생성되며 하이어라키를 보면 계속 원들이 생성되는 것을 볼수 있습니다.

인제 ObjectDestroyer를 만들어 원들을 비활성화해 원들을 재활용해봅시다.

using UnityEngine;

public class ObjectDestoryer : MonoBehaviour
{
    private GameObject point;

    private void Start()
    {
        point = GameObject.Find("LeftPoint");
    }

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

코드가 짧으니 한번에 설명하겠습니다.

point에 오브젝트를 비활성화할 기준인 좌측기준점을 할당하고 find를 사용해 자동으로 오브젝트를 지정해줍시다.

만약에 이 오브젝트가 기준점보다 좌측에있다면 제거되어야 하기 떄문에 if문을 통해 구현한다음 이 오브젝트를 SetActive로 비활성화해줍시다.

 

완성된 코드를 원 프리팹들에 넣어줍시다. 게임을 다시 실행하고 하이어라키를 보면 생성된 원들이 비활성화되고 활성화되는게 보입니다.

만들어진 오브젝트 생성개수를 제한하거나 순서를 정한다면 더 적은 수의 오브젝트로 게임을 만들어 더 최적화 할수도 있습니다. 그 부분을 구현하는것은 여러분의 몫으로 남겨두겠습니다!

 

긴글 읽어주셔서 감사합니다! 궁금한점이 있으면 댓글에 남겨주세요