[Unity/C#] 오브젝트 풀링(Object Pooling)에 대해서

신지한·2024년 6월 10일
1

면접준비

목록 보기
8/9
post-thumbnail

📢 공부내용에 앞서서

본 게시글은 면접준비 및 자기계발을 목적으로 작성된 게시글입니다
공부한 내용을 토대로 남들에게 설명할 수 있도록 이해하는 과정에 작성한 게시글이니 참고바랍니다

📖 오브젝트 풀링(Object Pooling)


📌 오브젝트 풀링에 대해선 위 영상을 참고하여 게시글을 작성 및 실습 하였습니다


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

  • Object를 담아둘 Object Pool을 만들고 빌려줄 오브젝트를 생성 후 오브젝트 풀이 그 오브젝트를 관리하도록 함
  • 외부에서 해당 오브젝트가 필요하면 오브젝트 풀에서 빌려주고 다 쓰게되면 다시 돌려줌
  • 해당 오브젝트를 더 빌려줄 수 없는 상황이 되면 그때 새로운 오브젝트를 생성해서 빌려줌
  • 디바이스의 메모리와 CPU의 성능이 낮았던 예전에 주로 사용됐지만, 현재에 와서도 게임의 최적화를 위해서 자주 사용됨

오브젝트가 필요할 때마다 생성하거나 다 쓰면 파괴하지않고 재활용 함으로써 오브젝트의 생성 및 파괴횟수를 줄이게 됨

📌 오브젝트 풀링을 사용하는 이유

  • 오브젝트의 생성과 파괴는 꽤나 무거운 작업임
    - 오브젝트의 생성: 메모리를 새로 할당하고 리소스를 로드하는 초기화 과정
    • 오브젝트의 파괴: 파괴 이후 발생하는 가비지 컬렉팅으로 인한 프레임 드랍 발생

📌 가비지 컬렉팅(Garbage Collecting)이란?

  • Object를 Destroy할 때 메모리에서 곧바로 사라지는것이 아니라, 게임에서 보이지 않게 되지만 Garbage Collector가 그것을 수거해서 파괴하기 전엔 메모리에 남아있게 됨
  • Object를 Destroy하면 일정 사이클이 지난 후에 Garbage Collector가 메모리를 뒤져서 이것을 수거
  • 파괴된 객체가 많으면 그 순간 모두 정리하려 하기 때문에 게임의 프레임이 일시적으로 올라감
간략하게 설명, 이후 Garbage Collecting에 대한 자세한 게시글 작성 예정

💻 오브젝트 풀링 실습

실습에 사용된 오브젝트는 Shooter, Bullet(Prefab), ObjectPool이 있다

Shooter : Bullet을 발사하는 객체, 화면 클릭시 해당 방향으로 Bullet을 발사
Bullet : 화면을 클릭시 해당 방향으로 날아가는 객체, Prefab으로 생성
ObectPool : Object들을 관리하는 Object Pool

Object Pooling 기법을 사용하기에 앞서 해당 방법을 사용하지 않았을 시에 발생하는 문제점을 파악했다
오브젝트 풀링으로 Bullet을 생성하는것이 아닌
클릭시 Instantiate로 Bullet을 생성하고 일정시간 뒤에 Destroy를 통해 오브젝트를 파괴하였다

위 이미지는 가비지 컬렉터의 프레임이 튀는 이미지이다

해당 방법으로 Bullet을 생성했을 시에 프로파일러를 확인해본 결과
Bullet을 생성과 파괴에 CPU가 일정 시간 할당되고 일정 주기로 Garbage Collector의 프레임이 스파이크 튀는것을 확인할 수 있었다

따라서 해당 문제를 해결하기 위해 Object Pooling 기법을 적용해보았다

📌 Object Pooling 실습 코드

//Shooter 클래스
public class Shooter : MonoBehaviour
{
    [SerializeField] private GameObject bulletPrefab;

    private Camera mainCam;

    private void Start()
    {
        mainCam = Camera.main;
    }
	
    //화면 클릭시 해당 방향으로 Bullet 생성
    private void Update()
    {
        if(Input.GetMouseButton(0))
        {
            Ray ray = mainCam.ScreenPointToRay(Input.mousePosition);
            
            var direction = new Vector3(ray.origin.x, ray.origin.y, 0) - transform.position;
            var bullet = ObjectPool.GetObject();
            bullet.transform.position = transform.position + direction.normalized;
            bullet.Shoot(direction.normalized);
        }
    }
}

//Bullet 클래스
public class Bullet : MonoBehaviour
{
    private Vector2 direction;
	
    //Shoot Bullet
    public void Shoot(Vector2 dir)
    {
        direction = dir;
        Invoke("DestroyBullet", 5f);
    }

    private void DestroyBullet()
    {
        ObjectPool.ReturnObject(this);
    }

    private void Update()
    {
        transform.Translate(direction);
    }
}

//ObjectPool 클래스
public class ObjectPool : MonoBehaviour
{
    public static ObjectPool Instance;

    [SerializeField]
    private GameObject poolingObjectPrefab;
	
    //Pool에 있는 객체들을 Queue로 관리
    private Queue<Bullet> poolingObjectQueue = new Queue<Bullet>();

    private void Awake()
    {
        Instance = this;

        Initialize(10); //초기 객체의 수를 10으로 설정
    }
	
    //새로운 객체를 생성
    private Bullet CreateNewObject()
    {
        var newObj = Instantiate(poolingObjectPrefab, transform).GetComponent<Bullet>();
        newObj.gameObject.SetActive(false);
        return newObj;
    }

    private void Initialize(int count)
    {
        for(int i=0;i<count;i++)
        {
            poolingObjectQueue.Enqueue(CreateNewObject());
        }
    }

    public static Bullet GetObject()
    {
        if(Instance.poolingObjectQueue.Count > 0) //Pool에 객체가 있으면 Pool에 있는 객체 재사용
        {
            var obj = Instance.poolingObjectQueue.Dequeue();
            obj.transform.SetParent(null);
            obj.gameObject.SetActive(true);
            return obj;
        }
        else //Pool에 객체가 없으면 새로운 객체 생성
        {
            var newObj = Instance.CreateNewObject();
            newObj.transform.SetParent(null);
            newObj.gameObject.SetActive(true);
            return newObj;
        }
    }

	//객체 사용 후 Pool에 반납
    public static void ReturnObject(Bullet bullet)
    {
        bullet.gameObject.SetActive(false);
        bullet.transform.SetParent(Instance.transform);
        Instance.poolingObjectQueue.Enqueue(bullet);
    }
}

📌 Object Pooling 실습 결과

화면 클릭시 오브젝트가 생성될 때 발사할때마다 새로 생성되는것이 아닌
Object Pool에 있는 객체를 활성화하며 우선적으로 사용한 후에 수가 부족할 시 새롭게 생성되는 결과를 확인

  • 프로파일러를 통해 Instantiate와 Destroy에서 CPU가 할당되는 시간이 없어졌음을 확인
  • Garbage Collector의 프레임이 스파이크 튀는 현상도 없어졌음을 확인
profile
게임 개발자

0개의 댓글