유니티에 대해 공부를 하다보면 에셋 번들에 대한 내용을 드문드문 접할 수 있다.
나 또한 에셋 번들에 관한 얘기를 지나가면서 몇 번 듣기는 했는데... 에셋 번들이 뭔지도 모르고 있었다.
지금으로부터 약 1달 전, 메모리 관리를 하며 에셋의 복제를 확인했고, 그 원인이 에셋 번들에 있음을 인지한 이후로 많은 공부를 하였다.
지금은 기존의 BuildPipeline을 Scriptable Build Pipeline으로 업그레이드를 했고, Shader Stripping에 문제가 있어서 해당 부분에 대한 원인을 찾아보고 있는 중이다.
아무튼, 이제는 에셋 번들에 대해 조금 알게된 것 같아 에셋 번들이란 무엇인지를 정리해보고자 한다.
모바일 게임을 설치하고 실행하면 이런 화면을 보게 될 것이다.
여기서 다운로드 받는 것이 에셋 번들이다.
원스토어에서 블루 아카이브의 용량은 143MB이지만, 실제 핸드폰에서 차지하는 게임 용량은 7.57GB로 굉장한 괴리감이 있다.
스토어에서 받은 용량을 제외한 나머지 용량은 전부 에셋 번들을 다운로드 받은 것이라고 보면 된다.
그러면, 에셋 번들을 사용하는 이유는 무엇일까?
첫 번째로는 스토어에서 용량을 작게 유지하기 위해서이다.
구글 플레이 스토어의 경우, 앱의 크기를 150MB로 제한을 해놨기 때문에 울며 겨자먹기로 이 용량보다 작게 유지해야 한다.
앱스토어 버전은 용량 제한이 500MB라 그런지 464.4MB이고, 전체적으로 다른 앱들도 용량이 크다. (스토어를 통해 받으면 돈이 안 나가니까 비용 절감에 도움이 된다)
그리고 앱의 용량이 작을수록 다운로드 수가 늘어난다고 한다.
아마 나같아도 요렇게 되어있으면 별로 안 받고 싶어질 것 같다.
가끔 위 사진처럼 플레이 스토어에도 150MB를 넘는 경우가 있는데, PAD(Play Asset Delivery)라는 기능을 이용한 것이다. 앱은 따로 받고, 에셋을 따로 올려서 앱을 다운로드 받을 때 최대 1GB까지 같이 받게 만들 수 있다. 후술하겠지만 여기에 올릴 에셋은 LZ4로 압축하는 것이 좋다.
에셋 번들을 사용하는 두 번째 이유는, 게임 내 리소스들의 유동성을 확보할 수 있다는 점이다.
앱을 빌드하면 스토어에 바로 올릴 수 있는 것이 아니다. 스토어에 앱을 올릴 때는 스토어의 검수 과정을 거치는데, 거절을 당할 수도 있고, 거절당한 이유를 알 수 없는 경우도 있어서 예상한 것보다 시간이 많이 걸릴 수 있다. 매 번 유저에게 스토어에서 앱을 다운로드 받으라고 하는 것도 바람직하지 못하다. 에셋 번들을 사용하지 않는 경우 게임 내 이미지 1개를 바꾸는 데에도 이런 작업이 필요하다는 점에서 굉장히 비효율적이라고 할 수 있겠다. 데이터가 잘못 들어가서 버그가 났는데, 서버를 내리고, 앱을 다시 빌드해서 검수를 통과하여 며칠이 지난 뒤에 서버가 열린다? 생각만 해도 끔찍한 일이다.
반대로 에셋 번들을 사용한다면, 앱을 빌드하고 심사할 필요 없이 에셋 번들만 따로 빌드해서 업로드를 하고, 유저가 접속할 때 변경사항을 체크하여 바뀐 부분만 다운로드를 받으면 바로 게임에 적용할 수가 있다. 종종 게임에 접속할 때 점검도 없었는데 갑자기 작은 용량의 데이터를 받는 경우가 있을텐데, 그게 바로 이런 경우라고 할 수 있겠다. 이 경우, 버그가 터져도 에셋 번들을 빌드할 몇 시간만 서버를 내렸다가 다시 오픈하면 문제를 해결할 수 있다.
버그 뿐만 아니라 게임 내에서 테이블을 통해 데이터를 읽어오고 적용하도록 만들면, 신규 아이템이나 캐릭터 등을 추가할 때도 단순히 데이터 패치를 통해서 적용할 수 있다.
위 두 가지 이유로 인해서 모바일 게임을 만든다면 에셋 번들을 쓰는 것은 선택이 아니라 필수라고 할 수 있을 것이다.
하지만 에셋 번들의 단점이 있는데, 에셋 번들을 사용하는 시스템을 구축하는 것이 굉장히 복잡하다는 것이다.
에셋 번들을 사용하려면 위와 같은 시스템을 구축해야 한다.
먼저 에셋 번들의 빌드는 BuildPipeline이나 Scriptable Pipeline의 ContentPipeline 등을 사용하여 에셋 번들을 빌드할 수도 있고, 유니티에서 많은 부분을 개선하여 낸 Addressable을 사용할 수도 있다.
*BuildPipeline이나 ContentPipeline을 사용하면 굉장히 번거로운 처리들을 직접 해줘야 하기 때문에 신규로 에셋 번들을 사용할 준비를 하고 있다면 이런 부분들의 일부를 알아서 처리해주는 Addressable을 사용하는 것을 권장한다.
그 다음은 에셋 번들을 다운로드 받을 수 있도록 업로드를 해야한다. 보통은 aws를 사용할텐데, 1GB에 대략 30원 정도의 비용이 나간다. 용량이 작다면 위에 언급한 PAD나 ODR(On Demand Resources)을 활용할 수도 있겠다.
그 다음은 유저가 모바일 기기에서 에셋 번들을 다운로드하는 부분을 구축해야 한다. 에셋 번들마다 버전을 관리하고, Hash를 이용해서 변경된 에셋 번들만을 받을 수 있도록 해야한다. 유니티로 한다면 UnityWebRequest, DownloadHandlerAssetBundle을 사용하게 될 것이다.
DownloadHandler 생성 - Unity 매뉴얼 (unity3d.com)
DownloadHandler 생성 - Unity 매뉴얼
다음과 같은 몇 가지 DownloadHandler 타입이 있습니다.
docs.unity3d.com
마지막으로는 에셋 번들을 로드하는 과정을 거쳐야 한다. 에셋 번들의 Dependencies를 읽어와서 에셋을 로드하기 전에 필요한 다른 에셋들을 먼저 로드해야 리소스가 깨지지않고 정상적으로 보이게 된다. 이런 코드를 직접 만든다면 순환 참조를 하지 않도록 막아주는 것이 필요하다.
정말 마지막으로는 로드한 에셋을 관리하는 것인데, 더 이상 사용하지 않는 에셋들을 해제해준다거나 하는 부분이다. 다만 에셋의 복제나 리소스가 깨진다든지 등의 문제가 생길 가능성이 많다. Addressable은 사용하지 않는 에셋 번들을 자동으로 메모리에서 해제해준다고 하니 신경을 적게 써도 된다. Dependencies로 알아서 체크해준다는 것 같고. 이외에도 위에 언급한 내용 중 일부분을 알아서 처리해주기도 하니 새로 구축한다면 반드시! 꼭! Addressable을 사용하도록 하자!
에셋 번들을 빌드할 때는 압축을 하게 되는데 3가지 방식이 있다.
1. 압축을 아예 하지 않는 방법
압축을 하지 않아서 빌드가 빠르겠지만, 용량이 굉장히 커지기 때문에 권장하지 않는 방식이다.
Bug - Addressables - Extremely slow load time - Unity Forum
It seems that LZ4 makes that first load faster than Uncompressed
추가로, 위 글에서 Uncompressed보다 LZ4가 첫 로딩 속도가 빠른 것 같다는 내용이 있는데, Unity에서 저장하는 방식을 LZ4로 해놔서 아마 실행할 때 Uncompressed를 LZ4로 하여 디스크에 저장하는 과정이 있는 것이 아닐까 싶다. 결국 Uncompressed는 꼭 필요한 것이 아니라면 권장하는 방식이 아니라고 생각된다.
2. LZMA
용량을 가장 작게 줄일 수 있는 압축 방식으로, zip이나 7z 등도 해당 방식을 사용한다. 단, 압축률이 높은 만큼 데이터를 불러오는 속도가 느리다. 설상가상으로 에셋 중 1개만 필요하더라도 에셋 번들 전체를 압축 해제해야 하기 때문에, 이 방식으로는 사용하기 어렵다. 하지만, 유니티에서 앱을 실행할 때, 혹은 다운로드를 받는 도중에 바로 다음에 쓸 LZ4로 Recompress를 해서 디스크에 저장하기 때문에 이런 걱정은 할 필요가 없다. 단, 주의해야할 점은, aws 등을 사용해서 다운로드를 받게 한다면 다운로드와 Recompress가 동시에 일어나기 때문에 유저가 다운로드를 받으면서 자연스럽게 재압축을 할 수 있지만, PAD 등의 기능을 사용하면 LZMA를 받아서 앱을 처음 실행할 때 통째로 LZ4로 Recompress를 하기 때문에 앱을 처음에 실행할 때 굉장히 오래 걸리게 된다. 떄문에 이런 기능을 사용할 것이라면 LZ4로 압축하는 것이 좋다. 단, 후술하겠지만 BuildPipeline을 사용해서 LZ4와 LZMA로 따로 빌드를 한다면 Dependencies를 제대로 찾을 수 없기 때문에 에셋 중복이 일어날 수 있다는 점을 주의해야 한다.
3. LZ4
LZ4는 위에서 말한 것 처럼 압축률은 LZMA보다 낮지만, 데이터를 로딩하는 속도가 빠르고 청크 단위로 로딩이 가능하기 때문에, 필요한 에셋만 압축을 해제하여 사용할 수 있다. LZMA로 압축해도 유니티가 LZ4로 재압축을 해주기 때문에 대부분의 경우 LZMA로 압축하는 것이 유저의 다운로드 용량을 줄여주면서 비용도 절감할 수 있는 방법이다.
참고로, 파일에 따라 다르겠지만, 일반적으로 LZ4대신 LZMA를 사용하면 약 30%정도 용량을 더 줄일 수 있다는 것 같다. (기억에 의존한 내용)
에셋 번들 압축 - Unity 매뉴얼 (unity3d.com)
에셋 번들 압축 - Unity 매뉴얼
기본적으로 Unity는 LZMA 압축을 사용하여 에셋 번들을 생성하고, LZ4 압축을 사용하여 캐싱합니다. 이 섹션에서는 이 두 가지 압축 포맷에 대해 설명합니다.
docs.unity3d.com
자세한 내용은 위 링크를 참조하시라.
또, 에셋 번들을 사용하면 에셋이 중복되는 문제가 발생할 수 있다.
이는 메모리 프로파일러를 통해서 확인할 수 있다.
글이 길어져서 에셋의 중복에 대한 이야기는 끊고 다음 글에서 마저 다루도록 하겠다.
'개발 > 공부' 카테고리의 다른 글
메모리 관리에 대한 탐구 (8) - AssetBundle, Scriptable Build Pipeline, Addressables (0) | 2023.08.15 |
---|---|
메모리 관리에 대한 탐구 (7) - 에셋의 중복과 Scriptable Build Pipeline (0) | 2023.08.12 |
[유니티] Vector3의 Normalize 방법 별 성능 테스트와 오버헤드 (0) | 2023.08.05 |
[유니티] for과 foreach 성능 비교 (0) | 2023.07.24 |
[유니티] 성능 테스트 환경 구축 / i++과 ++i의 속도 차이 (0) | 2023.07.22 |