Unity 에서의 오브젝트 생성과 파괴 함수


Instantiate - 오브젝트 생성은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화 하는 과정

Destroy - 오브젝트 파괴 이후에 발생하는 가비지 컬렉팅(Garbage Collecting)으로 인한 프레임 드랍이 발생

 

가비지 컬렉팅 - 더 이상 메모리에서 사용하지않을때 메모리에서 지우는 과정

 

 

인스턴스의 생성과 삭제는 매우 무거운 작업

-> 동적으로 생성된 인스턴스는 Heap 메모리 영역에 할당하고, 해당 메모리를 해제해주어야하는 작업이 필요하기때문

 

초기화 작업이 무거운 오브젝트를 Instantiate 하면 성능에 영향은 있겠지만, 일반적으로 성능에 영향을 미치는 것은 오브젝트를 파괴할 때 이다.

유니티에서는 사용하지 않는 메모리 해제를 위해 가비지 컬렉션(GC)을 사용한다.

Destroy() 를 반복적이고 빈번하게 호출하면 GC를 트리거 하게되고 GC가 사용중일 동안 게임 스레드가 멈추기때문에 

CPU가 느려지고 게임플레이에 영향을 미친다. 

  

 

 

문제점

1. 잦은 오브젝트의 생성과 삭제는 메모리 단편화 문제가 생김

메모리를 사용하고 해제해주게되면 하얀색 공간(인스턴스 크기)만큼 메모리 공간이 존재한다. 

 

이때 생성하려는 객체가 4MB 일때 하얀색 공간 (1MB + 2MB + 3MB) 총 6MB의 충분한 크기가 있는데도 불구하고 4MB 크기의 인스턴스를 할당할수없는 상태를 외부 단편화 라고한다.

 

 

2. 가비지 컬렉션이 발생하는 과정에서 클라이언트의 낮은 프레임 속도, 간헐적인 멈춤, 성능 저하 등이 일어날수있다.

 

가비지 컬렉터 개요 - Unity 매뉴얼

Unity에서는 가비지 컬렉터를 사용하여 애플리케이션과 Unity에서 더 이상 사용하지 않는 오브젝트로부터 메모리를 회수합니다. 스크립트가 관리되는 힙에 할당하려고 하지만 할당을 수용할 수

docs.unity3d.com

Heap 블록이 사용되고 있는지 확인하기 위하여 가비지 컬렉터는 모든 참조 변수(오브젝트)를 검색하고 애플리케이션에서 더 이상 참조가 없는 오브젝트를 삭제하도록 표시한다. 그런 다음 Unity는 레퍼런스가 없는 오브젝트를 삭제하여 메모리를 확보한다.

 

-> CPU가 이미 성능이 매우 중요한 시점에서 열심히 작업을 처리하고 있는경우, 가비지 컬렉션에의해서 발생한 약간의 오버헤드(overhead)는 프레임 속도(frame rate)와 성능을 떨어뜨릴 수 있습니다

 

힙 파편화는 2가지의 결과로 이어집니다.

첫째, 게임의 메모리 사용량이 게임에서 필요로하는 것보다 더 많아집니다.

둘째, 가비지 컬렉터가 더 자주 실행됩니다.

 

 

해결방법

게임 시작 전 로딩 할 때 미리 생성해 두었다가 필요할 때 사용한다.

오브젝트를 재활용 하기위한 디자인 패턴임

이 방식이 오브젝트 풀링 (Object Polling) 

 

사용한 오브젝트를 다시 반환함으로서 메모리 해제를 하지않아 가비지 컬렉팅 호출이 가능한 일어나지 않게하는 방식

 

 

 

1. 오브젝트를 담을 오브젝트 풀을 만든다.

 

 

2. 게임 로딩 때 오브젝트 풀 에다가 필요한 만큼 오브젝트를 Instantiate한다.

 

 

 

3. 게임 실행 중 오브젝트가 필요할 때 오브젝트 풀 에서 꺼내서 사용한다.

 

 

4. 오브젝트의 사용이 끝나면 오브젝트 풀에 다시 돌려준다.

 

 

5. 만약 오브젝트 풀에서 모든 오브젝트가 사용중이라면 새로운 오브젝트를 생성하여 꺼내준다.

 

이렇게 함으로서 게임 시작 전에 오브젝트 풀에 필요한 만큼만 오브젝트를 Instantiate 하여주고 Destroy 해줄필요없이 다시 돌려주어 시스템 자원을 적게 사용할 수 있다.

 

물론 소수의 오브젝트 Instantiate Destroy는 무시할 수 있겠지만, 배틀그라운드 게임을 예를들어보면 한 탄창당 30발 발사할때마다 Instantiate Destroy하는 것 보다 오브젝트 풀에서 꺼내오고 다시 돌려주는 방식이 더 효과적일 것이다.

만약 대용량 탄창을 흭득하여 40발이 되면 오브젝트 풀에 Instantiate를 10번 더 하여 저장하여 사용할수 있는것이고

그 외에 먼지 이펙트같은 것에도 많이 사용된다고 한다.

 

 

베르님이 구현하신 오브젝트 풀에서 조금 바꿔서 사용하였다.

참조 블로그 - 베르의 프로그래밍 노트

 

[Unity3D] Programming - 오브젝트 풀링 기법 구현하기

Programming - 오브젝트 풀링 기법 작성 기준 버전 :: 2019.2 프로그래밍에서 오브젝트를 생성하거나 파괴하는 작업은 꽤나 무거운 작업으로 분류된다. 오브젝트 생성은 메모리를 새로 할당하고 리소

wergia.tistory.com

public class ObjectPool : MonoBehaviour
    {
        private static ObjectPool pInstance;

        public static ObjectPool Instance
        {
            get { return pInstance; }
        }

        // 얕은복사를 위한 변수
        private GameObject poolingObject;
        private Queue<GameObject> poolingObjectQueue;


        [SerializeField]
        private GameObject bangLogObject; // 뱅 로그
        Queue<GameObject> bangLogListQueue = new Queue<GameObject>();

        [SerializeField]
        private GameObject questLogObject; // 월드퀘스트 로그
        Queue<GameObject> questLogListQueue = new Queue<GameObject>();

        [SerializeField]
        private GameObject avoidLogObject; // 회피 로그
        Queue<GameObject> avoidLogListQueue = new Queue<GameObject>();

        [SerializeField]
        private GameObject bulletObject; // 총알 프리팹
        Queue<GameObject> bulletListQueue = new Queue<GameObject>();

        private void Awake()
        {
            pInstance = this;
            Initialize();
        }

        private void Initialize()
        {
            for (int i = 0; i < 7; i++)
            {
                bangLogListQueue.Enqueue(CreateNewObject(1));
            }
            for (int i= 0; i < 3; i++)
            {
                questLogListQueue.Enqueue(CreateNewObject(2));
            }
            for (int i = 0; i < 7; i++)
            {
                avoidLogListQueue.Enqueue(CreateNewObject(3));
            }
            for (int i = 0; i < 7; i++)
            {
                bulletListQueue.Enqueue(CreateNewObject(4));
            }
        }

        private GameObject CreateNewObject(int num)
        {
            if (num == 1)
                poolingObject = bangLogObject;
            else if (num == 2)
                poolingObject = questLogObject;
            else if (num == 3)
                poolingObject = avoidLogObject;
            else if (num == 4)
                poolingObject = bulletObject;

            GameObject newObj = Instantiate(poolingObject);
            newObj.SetActive(false);
            newObj.transform.SetParent(transform);


            if (poolingObject == bulletObject)
            {
                newObj.GetComponent<Bullet>().enabled = false;
            }


            return newObj;
        }

        public GameObject GetObject(int num)
        {
            if (num == 1)
                poolingObjectQueue = bangLogListQueue; // 아마 얕은복사될듯 (주소만)
            else if (num == 2)
                poolingObjectQueue = questLogListQueue;
            else if (num == 3)
                poolingObjectQueue = avoidLogListQueue;
            else if (num == 4)
                poolingObjectQueue = bulletListQueue;



            if (Instance.poolingObjectQueue.Count > 0)
            {
                // 오브젝트 풀에 담겨있는 오브젝트를 가져온다. 
                var obj = poolingObjectQueue.Dequeue();
                obj.transform.SetParent(null);
                obj.gameObject.SetActive(true);
                return obj;
            }

            else
            {
                var newObj = CreateNewObject(num);
                newObj.SetActive(true);
                newObj.transform.SetParent(null);
                return newObj;
            }
        }


        public void ReturnObject(GameObject obj, int num)
        {
            if (num == 1)
                poolingObjectQueue = bangLogListQueue;
            else if (num == 2)
                poolingObjectQueue = questLogListQueue;
            else if (num == 3)
                poolingObjectQueue = avoidLogListQueue;
            else if (num == 4)
                poolingObjectQueue = bulletListQueue;


            obj.SetActive(false);
            obj.transform.SetParent(Instance.transform);
            poolingObjectQueue.Enqueue(obj);
            // 사용한 오브젝트는 다시 오브젝트 풀에 돌려준다
        }
    }

오브젝트풀에서 여러가지 게임오브젝트를 사용할 것이기때문에 게임오브젝트마다 다른 리스트를 선언하여 num인자를 주어 Instantiate를 하였고 꺼내올때, 반환할때 각각 num인자에 따라 poolingObjectQueue의 주솟값이 바뀌어 해당하는 오브젝트를 담을수 있게끔 구현하였다.

 

'유니티' 카테고리의 다른 글

유니티 Input System  (0) 2025.02.18
Atan  (0) 2025.01.06
Coroutine  (1) 2022.04.07
Photon Animator View (Trigger)  (0) 2022.04.06
Character Controller / RigidBody / Transform  (0) 2022.03.29

+ Recent posts