Unity_Object Pooling(오브젝트 풀링)

HanJaeHoon·2024년 2월 7일

Object Pooling(오브젝트 풀링)이란?


Unity에서 오브젝트 풀링(Object Pooling)은 메모리 및 성능 향상을 위해 사용되는 기술입니다.

만약 우리가 여러 개의 오브젝트를 생성 (Instantiate)하고 삭제(Destroy)를 한다고 가정할 때, 이 때 쓰이는 생성과 삭제 함수는 많은 비용을 잡아 먹습니다. 생성(Instantiate)은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화 과정이 필요하고, Destroy는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있기 때문입니다. 이때 우리는 오브젝트 풀링(Object Pooling)을 이용하여 이러한 상황을 예방할 수 있습니다.

오브젝트 풀링의 개념은 간단합니다. 먼저 자주 사용할 여러개의 오브젝트들을 담아 둘 그릇 즉 오브젝트 풀을 구성하고, 자주 사용할(풀링할) 오브젝트들을 생성하고 풀 안에서 오브젝트들을 관리하게 만들어 줍니다. 그리고 오브젝트가 쓰일 일이 있을때는 이러한 오브젝트 풀안에서 꺼내서 쓰고 사용이 끝나면 오브젝트를 다시 풀에 돌려줍니다. 만약에 모든 오브젝트가 다 빠져나간 상태에서 오브젝트를 사용하려고 한다면 새로운 오브젝트를 생성해서 다시 꺼내줍니다. 이렇듯 오브젝트 풀링(Object Pooling)이란 게임에 필요한 오브젝트들을 미리 생성해서 필요할때마다 꺼내쓴다는 것으로 알고 계시면 됩니다. 이렇게 미리 생성하고 꺼내쓰면 오브젝트의 생성/파괴 횟수를 줄일 수 있게 됩니다.

ex) 총알, 먼지 이펙트

오브젝트 풀링 간단 구현


오브젝트 풀링의 개념을 제대로 이해하기 위해 간단한 예제로 오브젝트 풀링을 구현해 보았습니다. 저는 플레이어가 발생하는 총알을 관리하는 오브젝트 풀을 구현해 보았습니다.

1) 간단한 맵 구성

저는 기본적인 3D 오브젝트들을 배치하여 간단하게 맵을 구성하였습니다.

2) 플레이어 생성 및 컨트롤러 cs 생성

플레이어는 간단하게 cube로 제작하였고, 안에는 플레이어를 이동시킬 PlayerController.cs를 추가하여 줬습니다. 저는 Rigidbody를 이용하여 이동을 구현하였기에 플레이어 Inspector 창에서 Rigidbody를 추가하여 줍니다.

Hireachy창 우클릭 -> 3D Object -> Cube

Inspector 창 -> AddComponent 클릭 -> Rigidbody 선택

Project창 우클릭 -> Create -> C# script -> PlayerController.cs

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

public class PlayerController : MonoBehaviour
{
    // 이동 속도
    [SerializeField]
    private float moveSpeed = 5f;
    // 리지드바디 컴포넌트
    private Rigidbody rig;
    // 총알 매니저
    private BulletManager bulletManager;

    void Start()
    {
        rig = GetComponent<Rigidbody>();
        bulletManager = FindObjectOfType<BulletManager>();
    }

    //입력처리는 Update에서
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // 총알 생성 위치와 방향을 전달하여 총알 생성
            bulletManager.ActivateBullet(transform.position, transform.rotation);
        }
    }

    //물리처리는 FixedUpdate에서
    void FixedUpdate()
    {
        //x축은 좌우, z축은 앞뒤
        float _moveX = Input.GetAxisRaw("Horizontal");
        float _moveZ = Input.GetAxisRaw("Vertical");

        Vector3 moveHorizontal = transform.right * _moveX;
        Vector3 moveVertical = transform.forward * _moveZ;

        Vector3 velocity = (moveHorizontal + moveVertical).normalized * moveSpeed;

        rig.MovePosition(transform.position + velocity * Time.deltaTime);
    }
}

위의 코드는 Unity에서 플레이어를 제어하는 PlayerController 클래스입니다.

moveSpeed 변수: 플레이어의 이동 속도를 나타내는 변수입니다. Inspector 창에서 조절할 수 있도록 SerializeField로 선언되어 있습니다.

rig 변수: 플레이어의 Rigidbody 컴포넌트를 저장하는 변수입니다. Rigidbody를 사용하여 플레이어의 이동을 제어합니다.

bulletManager 변수: BulletManager 클래스의 인스턴스를 저장하는 변수입니다. 총알 생성 및 관리를 위해 BulletManager 클래스를 사용합니다.

Start() 메서드: 게임 오브젝트가 활성화될 때 한 번 호출되는 Unity 라이프사이클 함수입니다. 여기서는 Rigidbody 컴포넌트와 BulletManager 인스턴스를 가져옵니다.

Update() 메서드: 매 프레임마다 호출되는 Unity 라이프사이클 함수입니다. 여기서는 마우스 왼쪽 버튼이 눌리면 총알을 생성하기 위해 BulletManager의 ActivateBullet 메서드를 호출합니다.

FixedUpdate() 메서드: 고정된 주기로 호출되는 Unity 라이프사이클 함수입니다. 여기서는 플레이어의 이동을 제어합니다. 입력에 따라 플레이어의 이동 방향을 계산하고, 이동 속도와 함께 Rigidbody의 MovePosition을 사용하여 플레이어를 이동시킵니다.

코드는 플레이어의 이동과 총알 생성을 담당하며, 물리적인 처리는 FixedUpdate에서 수행됩니다. 이렇게 함으로써 프레임당 호출되는 Update와는 별도로 고정된 주기로 호출되는 FixedUpdate를 사용하여 더 부드러운 이동을 구현할 수 있습니다.

3) 총알 프리팹 준비

먼저 플레이어가 발사하는 총알의 프리팹을 준비하여 줍니다.

Hireachy창 우클릭 -> 3D Object -> Sphere


이 안에는 따로 Bullet.cs 스크립트 파일이 포함이 되어있습니다.

Project창 우클릭 -> Create -> C# script -> Bullet.cs

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

public class Bullet : MonoBehaviour
{
    [SerializeField]
    private float speed = 8f;

    void Update()
    {
        // 총알을 일정 속도로 이동
        transform.Translate(Vector3.forward * speed * Time.deltaTime);
    }

    // 일정 시간 후에 자동으로 비활성화 되도록 하는 코루틴
    IEnumerator Start()
    {
        yield return new WaitForSeconds(2f);
        gameObject.SetActive(false);
    }

    // 활성화되면 호출되는 함수
    void OnEnable()
    {
        StartCoroutine(DisableAfterDelay());
    }

    // 일정 시간이 지난 후 비활성화하는 코루틴
    IEnumerator DisableAfterDelay()
    {
        yield return new WaitForSeconds(2f);
        gameObject.SetActive(false);
    }
}

위의 코드는 Unity에서 총알을 나타내는 Bullet 클래스입니다. 이 클래스는 총알이 발사되면 일정한 속도로 이동하다가 일정 시간이 지난 후에 자동으로 비활성화되도록 구현되어 있습니다.

speed 변수: 총알의 이동 속도를 나타내는 변수입니다. Inspector 창에서 조절할 수 있도록 SerializeField로 선언되어 있습니다.

Update() 메서드: 매 프레임마다 호출되는 Unity 라이프사이클 함수입니다. 총알을 일정한 속도로 이동시킵니다.

Start() 코루틴: 총알이 활성화된 후에 호출되는 함수입니다. WaitForSeconds를 사용하여 일정 시간(여기서는 2초)이 지난 후에 gameObject를 비활성화합니다. 이것은 총알이 특정 시간이 지난 후에 자동으로 사라지도록 합니다.

OnEnable() 메서드: 총알이 활성화될 때 호출되는 Unity 이벤트 함수입니다. 활성화된 후에 DisableAfterDelay 코루틴을 시작하여 일정 시간이 지난 후에 자동으로 총알을 비활성화합니다.

DisableAfterDelay() 코루틴: 일정 시간이 지난 후에 총알을 비활성화하는 코루틴입니다. WaitForSeconds를 사용하여 일정 시간(여기서는 2초)이 지난 후에 gameObject를 비활성화합니다.

4) ObjectPooling_Manager 스크립트 작성

Project창 우클릭 -> Create -> C# script -> ObjectPooling_Manager.cs

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

public class BulletManager : MonoBehaviour
{
    // 싱글톤을 할당할 전역변수
    public static BulletManager Instance;

    // 총알 오브젝트 풀
    public List<GameObject> bulletPool = new List<GameObject>();
    public int bulletPoolSize = 10; // 풀의 크기
    public GameObject bulletPrefab;
    public Transform bulletParent; // 생성된 총알들을 정리할 부모 Transform

    void Awake()
    {
        // 싱글톤 할당
        Instance = this;

        // 오브젝트 풀 초기화
        InitializeBulletPool();
    }

    // 오브젝트 풀 초기화 함수
    void InitializeBulletPool()
    {
        for (int i = 0; i < bulletPoolSize; i++)
        {
            GameObject bullet = Instantiate(bulletPrefab, Vector3.zero, Quaternion.identity, bulletParent);
            bullet.SetActive(false); // 비활성화 상태로 생성
            bulletPool.Add(bullet);
        }
    }

    // 총알을 활성화하는 함수
    public void ActivateBullet(Vector3 position, Quaternion rotation)
    {
        // 비활성화된 총알을 찾아서 활성화
        for (int i = 0; i < bulletPool.Count; i++)
        {
            if (!bulletPool[i].activeInHierarchy)
            {
                bulletPool[i].transform.position = position;
                bulletPool[i].transform.rotation = rotation;
                bulletPool[i].SetActive(true);
                return;
            }
        }
    }

    // 총알을 비활성화하는 함수
    public void DeactivateBullet(GameObject bullet)
    {
        bullet.SetActive(false);
    }
}

위의 코드는 Unity에서 총알을 관리하기 위한 BulletManager 클래스입니다. 이 클래스는 오브젝트 풀링을 사용하여 총알을 생성하고 재사용합니다.

Instance 변수: 이 변수는 BulletManager 클래스의 싱글톤 인스턴스를 나타냅니다. 다른 스크립트에서 이 인스턴스에 쉽게 접근할 수 있도록 합니다.

bulletPool 변수: 총알을 담을 리스트입니다. 이 리스트는 총알을 재사용하기 위한 오브젝트 풀을 나타냅니다.

bulletPoolSize 변수: 총알 풀의 크기를 나타내는 변수입니다. 초기에 생성될 총알의 개수를 결정합니다.

bulletPrefab 변수: 총알의 프리팹을 나타내는 변수입니다. 이 프리팹을 사용하여 총알을 생성합니다.

bulletParent 변수: 생성된 총알들을 정리할 부모 Transform을 나타냅니다. 생성된 총알들은 이 부모 아래에 정리됩니다.

Awake() 메서드: 클래스가 초기화될 때 호출되는 메서드입니다. 여기서는 싱글톤 인스턴스를 설정하고 오브젝트 풀을 초기화합니다.

InitializeBulletPool() 메서드: 총알 풀을 초기화하는 메서드입니다. 지정된 크기만큼 총알을 생성하고 비활성화 상태로 리스트에 추가합니다.

ActivateBullet() 메서드: 비활성화된 총알을 활성화하여 재사용합니다. 위치와 회전값을 받아와 해당 위치에서 총알을 발사합니다.

DeactivateBullet() 메서드: 총알을 비활성화하여 풀에 반환합니다. 총알이 더 이상 필요하지 않을 때 호출됩니다.

코드를 작성하신 뒤 Player에게 부착하여 줍니다. 이때 Bullet Prefab 안에는 우리가 미리 만들어준 Bullet 프리팹을 넣어줍니다.

실행 화면

실행을 해보면 미리 10개의 오브젝트가 미리 생성되어 있으며 왼쪽 클릭을 할때마다 오브젝트를 꺼내서 쓸 수 있는 모습을 확인하실 수 있습니다. 간단하게 제작하고 아직 실력이 많이 부족해서 어색한 부분이 많지만 오브젝트 풀링이란게 어떤 개념인지만 알고 가시면 좋을 것 같습니다.

profile
게임 개발자

0개의 댓글