애셋번들 기초 (AssetBundle Fundamentals) 2- 번역
애셋 번들 기초 (AssetBundle Fundamentals) 2
3.4 애셋번들 로딩하기 (Loading AssetBundles)
유니티 5에서는, 4가지의 서로 다른 API를 통해서 애셋번들을 로드할 수 있습니다. 이 4가지 API의 동작은 다음의 두 가지 기준에 따라서 달라집니다:
1. 애셋번들이 LZMA 방식으로 압축되었는지 또는 LZ4 방식으로 압축되었는지 또는 압축이 해제된 상태인지
2. 애셋번들이 로드되는 플랫폼
이 4가지의 API는 다음과 같습니다.
- AssetBundle.LoadFromMemoryAsync
- AssetBundle.LoadFromFile
- WWW.LoadFromCacheOrDownload
- UnityWebRequest’s DownloadHandlerAssetBundle (on Unity 5.3 or newer)
3.4.1 AssetBundle.LoadFromMemoryAsync
유니티는 이 API의 사용을 권장하지 않습니다.
유니티 5.3.3 업데이트: 이 API는 유니티 5.3.3에서 이름일 변경되었습니다.
유니티 5.3.2 (또는 이전버전)에서 이 API는 AssetBundle.CreateFromMemory로 알려져 있습니다. 하지만 그 기능은 변경되지 않았습니다.
AssetBundle.LoadFromMemoryAsync는 managed-code 바이트 배열(C#의 byte[])로부터 애셋번들을 로드합니다.
이 API를 이용하면 항상, 인접한 위치에 새로 할당된 메모리 블록으로 managed-code 바이트 배열을 복사합니다.
만약 애셋번들이 LZMA 방식으로 압축된 경우라면, 복사하는 과정에서 애셋번들의 압축을 해제하게됩니다. 비압축 애셋번들과 LZ4 방식으로 압축된 애셋번들은 그대로 복사됩니다.
이 API가 소비하는 최대 메모리 양은 애셋번들의 두배입니다:
하나는 API에 의해서 생성된 navtive 메모리에 있는 복사본이고, 다른 하나는 API에 전달되는, managed code 바이트 배열에 저장된 복사본입니다.
따라서, 이 API를 통해서 생성된 애셋번들로부터 로드된 애셋은 메모리에서 총 3번 복사됩니다:
managed-code 바이트 배열에서 한번, 애셋번들의 native-메모리 복사본에서 한번, 그리고 GPU 또는 애셋 자체의 시스템 메모리에서 세번째로 복사됩니다.
– 역자주: managed-code는 보통 C# 코드를 의미하고 native-memory는 c++에서 관리하는 메모리를 의미합니다 –
3.4.2 AssetBundle.LoadFromFile
유니티 5.3 업데이트: 이 API는 유니티 5.3에서 이름이 변경되었습니다.
유니티 5.2 (또는 이전버전)에서 이 API는 AssetBundle.CreateFromFile로 알려져 있습니다. 하지만 그 기능은 변경되지 않았습니다.
AssetBundle.LoadFromFile은 하드 디스크나 SD카드와 같은, 로컬 저장소에서 비압축(uncompressed) 에셋번들을 로딩하기 위한 의도로 설계된 고효율 API입니다.
비압축 방식 또는 LZ4로 압축된 애셋번들은 다음과 같이 동작합니다:
모바일 장치: API가 애셋번들의 헤더(header)만 로드하고 나머지는 디스크에 남겨둡니다. 애셋번들의 오브젝트(Object)는, 로딩 메소드(예: AssetBundle.Load)가 호출되거나 해당 오브젝트의 Instance ID의 참조가 해제되는 등, 요구가 있을때(on-demand)로드됩니다. 이 시나리오에서는 초과 메모리가 소모되지 않습니다.
유니티 에디터: 마치 디스크에서 바이트(byte)를 읽어냈거나 AssetBundle.LoadFromMemoryAsync가 사용된 것처럼, API가 전체 애셋번들을 메모리에 로드합니다. 유니티 에디터에서 프로젝트를 프로파일링을 하는 경우, 이 API를 사용하면 애셋번들 로딩중에 메모리 스파이크(메모리가 순간적으로 튀는 현상)가 나타날 수 있습니다. 이는 대부분의 경우에 기기의 성능에 영향을 주지않지만, 이러한 스파이크에 대한 개선 조치를 하기전에 장치에서 먼저 재-테스트 하는 것이 좋습니다.
노트: 안드로이드 장치에서 유니티 5.3 이하의 버전을 사용하는 경우, 이 API를 이용해서 Streaming Asset 경로에서 애셋번들을 로드하려고할 때 로드에 실패합니다. 이는 해당 경로의 콘텐츠가 압축된 .jar 파일안에 존재하기 때문입니다. 이 문제에 대한 자세한 내용은, 애셋번들 사용 패턴 장(Chapter)의 배포 – 프로젝트와 함께 제공하기 부분을 참고하시기 바랍니다. 이 문제는 유니티 5.4에서 해결되었습니다. 유니티 5.4 이상의 버전에서 제작된 게임은 이 API를 사용해서 Streaming Asset 경로에서 애셋번들을 로드할 수 있습니다.
노트: LZMA 방식으로 압축된 애셋번들의 경우, AssetBundle.LoadFromFile을 호출하면 항상 실패합니다.
3.4.3 WWW.LoadFromCacheOrDownload
WWW.LoadFromCacheOrDownload는 원격 서버 및 로컬 저장소에서 오브젝트를 로드할 때 유용한 API입니다.
파일은 file://URL을 통해서 로컬 저장소에서 로드할 수 있습니다. 애셋번들이 유니티 캐시(cache)에 존재하는 경우, 이 API는 AssetBundle.LoadFromFile과 동일하게 동작합니다.
애셋번들이 아직 캐시에 저장되지 않은 경우, WWW.LoadFromCacheOrDownload API는 원본에서 애셋번들을 읽어옵니다.
애셋번들이 압축되어있는 경우, 작업 쓰레드(worker Thread)를 이용해서 압축을 풀고 캐시에 저장합니다. 그렇지 않고 애셋번들이 압축되지 않은 경우, 작업 쓰레드를 통해서 직접 캐시에 저장합니다.
애셋번들이 캐시에 저장되면, WWW.LoadFromCacheOrDownload는 압축이 해제된 상태로 캐시에 저장된 애셋번들로부터 헤더 정보를 로드합니다.
이제 이 API는 AssetBundle.LoadFromFile을 이용해서 로드된 애셋번들과 동일하게 동작합니다.
노트: 압축이 해제되고 크기가 고정된 버퍼(fixed-size buffer)를 통해서 캐시에 기록되는 동안에, WWW 객체는 애셋번들의 바이트 전체를 native 메모리에 보관합니다. 이렇게 보관된 여분의 애셋번들 복사본은 WWW.bytes 속성이 지원될 수 있도록 유지됩니다.
WWW 객체에서 애셋번들의 바이트를 캐시에 저장하기 때문에 발생하는 메모리 오버헤드(memory overhead)로 인해서, WWW.LoadFromCacheOrDownload를 사용하는 모든 개발자는 애셋번들이 작게 유지하는 것을 권장합니다(최대 몇 메가 바이트 수준).
또한 모바일 장치와 같이, 메모리가 제한된 플랫폼을 사용하는 개발자의 경우, 메모리 스파이크를 피하기 위해서 애셋번들을 한번에 하나씩 다운로드하는 것이 좋습니다.
애셋번들의 크기 조절에 대한 자세한 내용은, 애셋번들 사용 패턴 장(Chapter)의 애셋 할당 전략 부분을 참고하시기 바랍니다.
노트: 이 API를 호출할 때마다 새로운 작업 쓰레드(worker thread)가 생성됩니다. 이 API를 여러번 호출할 때 과도한 수의 쓰레드가 생성되지 않도록 주의하시기 바랍니다.
5-10개의 애셋번들을 다운로드 해야하는 경우, 동시에 일부의 애셋번들만 다운로드 되도록 코드를 작성하는 것을 권장합니다.
3.4.4 AssetBundleDownloadHandler
유니티 5.3의 모바일 플랫폼에 도입된 UnityWebRequest API는 유니티의 WWW API보다 유연한 대안을 제공합니다.
UnityWebRequest를 통해서, 개발자는 유니티가 다운로드한 데이터를 처리하는 방법을 명확히 지정할 수 있으며, 불필요한 메모리 사용을 없앨 수 있습니다.
UnityWebRequest를 통해서 애셋번들을 다운로드하는 가장 간단한 방법은, UnityWebRequest.GetAssetBundle API를 사용하는 것입니다.
이 가이드의 목적에 따라서, 주목할 클래스는 DownloadHandlerAssetBundle 클래스입니다.
이 클래스를 사용하면, 다운로드 핸들러(handler)가 WWW.LoadFromCacheOrDownload와 비슷하게 동작합니다.
작업 쓰레드를 사용해서 다운로드한 데이터를 고정 크기 버퍼에 스트리밍한 다음, 이렇게 버퍼에 저장된 데이터를 임시 저장소 또는 애셋번들 캐시로 스풀링(spooling)합니다.
LZMA 방식으로 압축된 애셋번들은 다운로드 중에 압축이 해제되고 압축이 해제된 상태로 캐시에 저장됩니다.
이 모든 작업은 native code(c++ 코드)에서 발생하므로, managed 힙(c#에서 관리하는 힙 메모리)을 확장할 위험이 없습니다.
또한, 이 다운로드 핸들러는 다운로드한 모든 바이트의 native-code(c++ 코드) 사본을 보관하지 않기 때문에, 애셋번들의 다운로드로 인해서 발생하는 메모리 오버헤드가 줄어듭니다.
에셋번들의 다운로드가 완료되면, 다운로드된 애셋번들에서 AssetBundle.LoadFromFile이 호출된 것처럼, 다운로드 핸들러의 assetBundle 프로퍼티(property)를 통해서 다운로드된 애셋번들에 접근할 수 있습니다.
UnityWebRequest API는 WWW.LoadFromCacheOrDownload와 동일한 방식으로 캐싱을 지원합니다.
캐싱 정보가 UnityWebRequest 객체에 제공되면, 요청받은 애셋번들이 유니티 캐시에 이미 존재하는 경우, 해당 애셋번들은 바로 사용가능하며 UnityWebRequest API는 AssetBundle.LoadFromFile과 동일하게 동작합니다.
노트: WWW.LoadFromCacheOrDownload와 UnityWebRequest에서 유니티 애셋번들 캐시(Cache)를 서로 공유됩니다. 두 API중 하나를 통해서 다운로드된 애셋번들은 다른 API를 통해서도 사용가능합니다.
노트: WWW와는 달리, UnityWebRequest 시스템은, 개발자가 과도한 수의 애셋번들을 동시에 다운로드할 수 없도록 하기 위해서, 내부에 작업 쓰레드 풀(pool)을 가지고 있습니다. 작업 쓰레드 풀의 크기는 현재 조절이 불가능합니다.
3.4.5 권장사항 (Recommendations)
일반적으로, 사용이 가능한 경우, AssetBundle.LoadFromFile을 사용하는 것이 좋습니다. 이 API는 속도, 디스크 사용, 런타임 메모리 사용 측면에서 가장 효율적입니다.
애셋번들을 다운로드 하거나 패치(patch) 해야하는 프로젝트의 경우, 유니티 5.3 이상을 사용하는 프로젝트는 UnityWebRequest를 사용하고, 유니티 5.2 이전 버전을 사용하는 프로젝트는 WWW.LoadFromCacheOrDownload를 사용하는 것을 권장합니다. 다음 장의 배포 섹션(section)에서 자세하게 설명한대로, 프로젝트의 설치 프로그램에 포함되는 번들(Bundle)에 애셋번들 캐시(AssetBundle Cache)를 준비할 수 있습니다.
WWW.LoadFromCacheOrDownload를 사용할 때, 메모리 사용량 급등으로 인한 프로그램의 종료를 막기위해서, 프로젝트의 애셋번들이 프로젝트에서 사용할 수 있는 최대 메모리 예산의 2-3% 보다 작게 유지하는 것을 권장합니다.
대부분의 프로젝트에서 애셋번들의 파일 크기가 5MB를 넘지 않도록 하고, 동시에 1-2개 이상의 애셋번들을 다운로드 하지 않도록 하는 것이 좋습니다.
WWW.LoadFromCacheOrDownload 또는 UnityWebRequest를 사용할 때, 애셋번들을 로드한 후에 다운로더 코드(다운로드를 수행하는 코드)가 적절하게 Dispose 메소드를 호출했는지 확인합니다.
다른 방법으로, C#의 using 구문은 WWW 또는 UnityWebRequest 객체를 안전하게 해제하는 가장 편리한 방법입니다.
프로젝트에 참여한 개발자가 많고 고유한 캐싱 또는 다운로드를 요구하는 프로젝트의 경우, 사용자 지정(Custom) 다운로더가 필요할 수도 있습니다.
사용자 지정 다운로더를 작성하는 것은 간단하지 않은 엔지니어링 작업이며, AssetBundle.LoadFromFile과 호환되어야 합니다.
이에 대한 자세한 내용은 다음 장의 배포 섹션을 참고하시기 바랍니다.
내용 끝까지 읽어주셔서 감사합니다.
배너 클릭은 저에게 많은 힘이 됩니다.
감사합니다 🙂
감사합니다