[Unity] 유니티 공식 지원 오브젝트 풀링
개발단에 가입하여 베르의 게임 개발 유튜브를 후원해주세요!
안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다!
이번에는 유니티에서 공식 지원하는 오브젝트 풀링을 알아봅시다.
[패키지 다운로드]
https://github.com/WERGIA/WerGameDevChan/blob/main/Unity/unity-object-pool.unitypackage
사용 엔진 버전 : 2021.3
타임라인
0:00 인트로
0:10 오브젝트 생성과 파괴 방식의 문제
2:26 오브젝트 풀링의 개념
3:02 유니티 오브젝트 풀링
6:14 아웃트로
스크립트
인트로
안녕하세요. 여러분들과 함께 게임 개발을 공부하는 베르입니다.
이번에는 유니티 2021부터 정식 기능으로 포함된 오브젝트 풀링에 대해서 알아보도록 하겠습니다.
오브젝트 생성과 파괴 방식의 문제
오브젝트 풀링이 무엇인지는 이전에 만든 오브젝트 풀링 영상에서 이야기했었지만 그래도 한 번 더 간단하게 설명해보도록 하겠습니다.
먼저 오브젝트 풀링을 사용하지 않고 오브젝트를 생성하거나 파괴하면 두 가지 문제가 발생할 수 있습니다.
첫 번째 문제는 가비지 컬렉팅으로 인한 프레임 드랍입니다.
프로그래밍에서 오브젝트를 생성하거나 파괴하는 작업은 꽤나 무거운 작업으로 분류됩니다.
오브젝트 생성은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화 과정이 필요하고, 오브젝트 파괴는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있습니다.
가비지 컬렉팅이란 유니티 스크립팅의 기반이 되는 C# 프로그래밍에서 제공되는 기능입니다.
우리가 Destroy 함수로 게임 오브젝트를 파괴한다고 선언하면 곧바로 메모리에서 사라지는 것이 아닙니다.
그 게임 오브젝트는 게임에서 보이지 않게 되지만 가비지 콜렉터라고 부르는 것이 그것을 수거해서 파괴하기 전까지 메모리에 남아있습니다.
그래서 오브젝트를 파괴한다고 선언하면 일정 사이클이 지난 이후에 가비지 컬렉터가 메모리를 뒤져서 파괴 선언된 오브젝트를 수거해서 파괴합니다.
이렇게 파괴 선언된 오브젝트가 적으면 수거해서 파괴하는 시간이 짧지만, 많은 양의 오브젝트가 쌓여있다면 당연히 수거하고 파괴하는 데 더 많은 시간이 걸리게 됩니다.
거기에 컴퓨터는 이렇게 파괴한 오브젝트를 수거해서 정리하는 작업만 진행하고 있는게 아닙니다.
게임이 동작하는 로직도 처리하고 있고 게임 화면을 보여주기 위해서 렌더링 작업까지 진행하고 있습니다.
그렇기 때문에 파괴된 오브젝트가 많다면 많은 양의 메모리를 정리하기 위해서 게임의 프레임이 일시적으로 떨어지는 문제가 발생합니다.
두 번째 문제는 메모리 파편화입니다.
프로그램에서는 처음부터 정해진 메모리의 양도 있지만, 동작하는 도중에 필요한 양에 따라 새로 할당받는 경우도 있습니다.
그런데 이 예시 이미지처럼 64바이트의 A, B, C, D를 할당받은 상태에서 A와 C를 할당 해제하면 3번과 같이 빈 공간이 생겨납니다.
이 때 128바이트의 오브젝트를 생성하려고 하면 A와 C 자리에는 공간이 부족에서 D의 뒤에 있는 공간에 할당해야 합니다.
이 할당 이 후에 128바이트 짜리 오브젝트를 하나 더 생성하려고 하면, 메모리 빈 공간만 따졌을 때는 128바이트의 공간이 존재하지만, 연결된 크기가 128바이트인 공간이 없어서 오브젝트 생성에 실패합니다.
한마디로 메모리는 남아 있는데 메모리가 부족한 역설적인 상황이 발생하는 것입니다.
이런 문제들을 해결하기 위해서 고안된 개념이 바로 오브젝트 풀링입니다.
오브젝트 풀링의 개념
오브젝트 풀링의 개념은 생각보다 간단합니다.
먼저 오브젝트를 담아둘 오브젝트 풀을 만들고 여기에 빌려줄 오브젝트를 생성해서 담아둔 뒤 외부에서 이 오브젝트가 필요하면 오브젝트 풀에서 빌려가는 갑니다.
그리고 빌려온 오브젝트를 모두 사용하고 나면 다시 오브젝트 풀에 돌려주는 겁니다.
그런데 만약 외부에서 오브젝트 풀 안에 있는 모든 오브젝트를 다 빌려가서 더 빌려줄 수 없는 상황이 되면 그때서야 새로운 오브젝트를 생성해서 꺼내줍니다.
그리고 이렇게 새로 만든 오브젝트 역시 사용이 끝나서 돌려 받으면 파괴하지 않고 오브젝트 풀이 보관하고 있으면 됩니다.
이게 바로 오브젝트 풀링의 개념입니다.
유니티 오브젝트 풀링
유니티 오브젝트 풀링을 사용하는 방법을 알아보기 전에 영상 하단의 링크에서 자료를 다운로드 받아서 프로젝트에 임포트합니다.
패키지를 임포트한 다음에 Pooling Test Scene을 열고 플레이시켜보면 클릭하는 동안에 많은 수의 총알 오브젝트가 생성되는 모습을 볼 수 있습니다.
총알을 발사하는 역할의 Shooter 클래스를 보면 지금은 오브젝트 풀을 사용하지 않고 총알을 생성하고 있습니다.
이제 이 총알 생성과 파괴 부분을 유니티 오브젝트 풀로 대체해보겠습니다.
유니티 오브젝트 풀을 사용하기 위해서는 UnityEngine.Pool 네임스페이스가 필요합니다.
그리고 오브젝트 풀을 적용하기 위해서는 풀링의 대상이 될 오브젝트의 클래스와 풀링된 오브젝트를 관리할 클래스, 두 군데에 작업을 해줘야합니다.
먼저 풀링의 대상이 될 Bullet 클래스로 이동합니다.
그리고 클래스의 상단에 UnityEngine.Pool 네임스페이스를 선언해줍니다.
IObjectPool<Bullet> 타입으로 이 총알 오브젝트를 관리 중인 풀을 캐싱할 _ManagedPool 멤버 변수를 선언합니다.
그리고 SetManagedPool 함수를 만들고 매개변수로 받은 풀을 _ManagedPool에 저장하도록 만들어줍니다.
그 다음에는 DestroyBullet 함수를 만들고 호출되면 총알 오브젝트를 풀에 반환하도록 코드를 작성합니다.
그리고 Shoot 함수에서 총알을 5초 후에 파괴하는 부분을 5초 후에 DestroyBullet 함수를 호출하도록 수정합니다.
그 다음에는 Shooter 클래스로 이동해서 상단에 UnityEngine.Pool 네임스페이스를 선언해줍니다.
그리고 멤버 변수로 총알 오브젝트들을 관리할 IObjectPool<Bullet> 타입의 _Pool 변수를 선언해줍니다.
그리고 Awake 함수를 만들어서 _Pool 멤버 변수에 Object Pool을 생성해서 넣어줍니다.
여기서 생성자의 매개 변수에 넣어줄 것이 좀 많습니다.
먼저 총알 오브젝트를 생성할 때 호출될 함수인 CreateBullet 함수를 만들고 가지고 있는 프리팹으로 총알 오브젝트를 생성한 다음 생성된 bullet 오브젝트에 자신이 등록되어야할 풀을 알려주고 반환합니다.
그리고 풀에서 오브젝트를 빌려올 때 사용될 OnGetBullet 함수와 오브젝트를 풀에 돌려줄 때 사용될 OnReleaseBullet 함수를 선언하고 코드를 작성합니다.
마지막으로 풀에서 오브젝트를 파괴할 때 사용될 OnDestroyBullet 함수 역시 작성해줍니다.
그리고 이렇게 만든 함수들을 풀의 생성자 매개변수에 넣어주고, 생성가능한 오브젝트 최대 갯수 역시 넣어줍니다.
오브젝트 풀과 관련된 코드를 모두 작성한 다음에는 Update 함수로 가서 프리팹을 인스턴스화해서 총알 오브젝트를 생성하는 부분의 코드를 오브젝트 풀을 사용하도록 코드를 수정합니다.
코드를 모두 작성한 다음에는 저장하고 에디터로 돌아갑니다.
그리고 게임을 플레이해서 테스트해보면 처음에는 총알 오브젝트가 없지만 총알을 발사할 때 생겨납니다.
그리고 총알이 사라질 때가 되면 풀에 설정해놓은 최대 갯수만 남아서 비활성화되고 나머지는 완전히 파괴되는 모습을 볼 수 있습니다.
다시 한 번 총알을 발사해보면 비활성화된 총알들이 재활용되고 모자란 총알들만 생성되는 모습을 볼 수 있습니다.
아웃트로
이번 영상에서는 2021 버전부터 정식 지원되는 유니티 오브젝트 풀링에 대해서 알아보았습니다.
이 강좌는 시청자 여러분들의 시청과 후원으로 제작되었습니다.
이상 베르의 게임 개발 유튜브였습니다. 감사합니다.
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
[투네이션]
[Patreon]
[디스코드 채널]
[참고자료]
https://luv-n-interest.tistory.com/1368
https://docs.unity3d.com/ScriptReference/Pool.ObjectPool_1.html
https://workinprogress.kr/wiki/programming/do-not-feed-the-gc/