이 글은 유니티 튜토리얼을 번역한 글 입니다. 원문은 여기에서 확인하실 수 있습니다.

이 글은 총 5개의 챕터로 구성되며 아래와 같습니다.

  1. [번역] 에셋번들과 리소스에 대한 가이드
  2. [번역] 에셋과 오브젝트, 그리고 직렬화
  3. [번역] RESOURCES 폴더
  4. [번역] 에셋번들 기초
  5. [번역] 에셋번들 사용 패턴




이 글은 [번역] 에셋번들과 리소스에 대한 가이드 시리즈의 4번째 챕터입니다.

이 챕터에서는 에셋번들에 대해 이야기합니다. 여기에는 에셋번들의 기초 시스템과 에셋번들과 상호작용하는데에 사용되는 핵심 API를 포함하고 있습니다. 특히, 에셋번들 자신을 로드/언로드하는 것과 에셋번들에서 특정 에셋과 오브젝트를 로드/언로드하는 것에 대해서도 이야기 합니다.

에셋번들의 사용에 대한 다른 패턴과 모범 사례를 알고 싶다면, 이 시리즈의 다음 챕터를 보시기 바랍니다.

3.1. 개요(Overview)

에셋번들 시스템은 한 개 이상의 파일을 유니티가 색인할 수 있는 압축 포맷으로 저장하는 기능을 제공합니다. 이 시스템의 목적은 유니티의 직렬화 시스템과 호환되는 데이터 전송 방법을 제공하는 것 입니다. 에셋번들은 유니티에서 설치 후에 코드가 없는 컨텐츠를 전송하고 업데이트하는 주요 도구입니다. 이로 인해서 개발자들은 최초 설치 앱의 에셋 사이즈를 줄일 수 있고, 런타임 메모리 압축을 최소화할 수 있고, 최종 유저(end-user)의 디바이스에 최적화된 컨텐츠를 선택적으로 로드할 수 있습니다.

에셋번들이 동작하는 방식을 이해하는 것은 모바일 디바이스를 위한 유니티 프로젝트를 빌드하는데 필수적인 부분입니다.

3.2. 에셋번들 안에는 무엇이 있나?

에셋번들은 두 부분으로 구성되어 있습니다 : 헤더(header)와 데이터(data) 영역(segment)으로 말이죠.

헤더는 유니티에서 에셋번들이 빌드될 때 생성됩니다. 여기에는 에셋번들에 대한 정보가 포함되어 있는데, 여기에는 에셋번들의 식별자(identifier), 압축 여부, 매니페스트 (manifest)1 등이 있습니다.

매니페스트는 오브젝트의 이름을 키(key)로 하는 색인 테이블입니다. 각 항목(entry)는 바이트 인덱스를 제공하는데, 이는 에셋번들의 데이터 영역에서 특정 오브젝트를 어디에서 찾을 수 있는지를 가리키고 있습니다. 대부분의 플랫폼에서 이 색인 테이블은 STL std::multimap 으로 구현되어 있습니다. STL의 플랫폼마다 구현에 사용되는 알고리즘은 달라지지만, 대부분은 균형 탐색 트리(balanced search tree)의 변형입니다. 윈도우즈와 OSX에서 파생된 플랫폼들(iOS를 포함해서요)에서는 red-black 트리를 사용합니다. 그러므로, 매니페스트 파일을 생성하는데에 걸리는 시간은 에셋번들 안에 있는 에셋의 수의 증가율보다 더 높습니다.

데이터 영역에는 에셋번들에 있는 에셋을 직렬화해서 생기는 미가공 데이터(raw data)2를 포함하고 있습니다. 만약 데이터 영역을 압축한다면, 모든 에셋을 직렬화한 데이터인 축적된 직렬화 바이트 시퀀스(collective sequence of seralized bytes)에 LZMA 알고리즘이 적용되고, 바이트 배열 전체가 압축되게 됩니다.

유니티 5.3 이전에는, 에셋번들 안에 오브젝트가 개별적으로 압축될 수 없었습니다. 그 결과, 유니티 버전 5.3 이전에서 압축된 에셋번들에 있는 한 개 이상의 오브젝트를 읽으려고 하면, 유니티는 그 에셋번들 전체의 압축을 풀어야만 했습니다. 일반적으로, 추후에 같은 에셋번들에 대한 로딩 요청에 대한 로딩 성능을 개선하기 위해서, 유니티는 압축 해제된 에셋번들에 대한 복사본을 캐쉬해 둡니다.

유니티 5.3에서 LZ4 압축 옵션이 추가되었습니다. LZ4 압축 옵션으로 빌드된 에셋번들은 에셋번들 내에 개별적인 오브젝트들을 압축할 것이고, 유니티는 압축된 에셋번들을 디스크에 저장하게 됩니다. 이는 유니티가 에셋번들 전체에 대한 압축을 풀 필요없이 개별 오브젝트를 압축 해제할 수 있도록 해줍니다.

3.3. 에셋 번들 관리자(The AssetBundle Manager)

유니티는 Bitbucket에 AssetBundle Manager에 대한 참조 구현을 개발했고 유지하고 있습니다. 이 관리자는 이번 챕터에서 자세히 다루는 많은 컨셉과 API를 채용하고 있으며, 에셋번들을 리소스 관리(resource-management) 작업 흐름(workflow)에 합쳐야 하는 어떠한 프로젝트에서든지 유용=할 수 있는 시작 지점을 제공하고 있습니다.

주목할만한 기능에는 "simulation mode"가 있다. 유니티 에디터가 활성되 되었을 때, 이 모드는 에셋번들에 태그된 에셋에 대한 요청을 프로젝트의 /Assets/ 폴더에 있는 원래의 에셋 위치로 투명하게(transparently, 자동으로) 방향을 틀어줍니다(redirect). 이는 개발자들이 에셋번들을 재 빌드할 필요없게 만들어 줍니다.

AssetBundle Manager는 오픈 소스이고 여기에서 볼 수 있습니다.

3.4. 에셋번들의 로드

유니티 5에서, 에셋번들은 4개의 API를 이용해서 로드될 수 있습니다. 이 4개의 API의 동작은 2개의 영역으로 구분지어 다르게 볼 수 있습니다.

  1. 에셋번들이 LZMA로 압축되었는지, LZ4로 압축되었는지, 아니면 압축되지 않았는지
  2. 에셋번들이 로드될 플랫폼


4개의 API는 다음과 같습니다.



3.4.1. AssetBundle.LoadFromMemoryAsync

유니티의 추천에서는 이 API를 사용하지 말라고 하고 있습니다.

유니티 5.3.3 업데이트 : 이 API는 유니티 5.3.3에서 이름이 변경되었습니다. 유니티 5.3.2와 그 이전에는 AssetBundle.CreateFromMemory 였고, 기능의 변화는 없습니다.

AssetBundle.LoadFromMemoryAsync 는 관리되는(managed) 코드 바이트 배열(C#의 byte[]) 에서 에셋번들을 로드합니다. 이는 항상 관리된 코드 바이트 배열로부터 소스 데이터를 복사하는데, 이 때 네이티브 메모리의 연속된 블락에 새롭게 할당을 하게 됩니다. 만약 에셋번들이 LZMA 압축이 되었다면, 복사하는 중에 에셋번들의 압축을 해제하게 됩니다. 압축되지 않았거나 LZ4로 압축이 되었다면 에셋번들은 그대로 복사되게 됩니다.

이 API를 사용할 때에 소비되는 메모리의 최대량은 최소한 에셋번들의 크기의 2배가 됩니다 : 하나는 API에 의해 생성되는 네이티브 메모리이고, 다른 하나는 API에 전달되는 관리되는 바이트 배열입니다. 따라서 이 API를 통해서 에셋번들에서 로드된 에셋들은 메모리에 3번 복제되게 됩니다 : 한번은 관리된 코드 바이트 배열, 한번은 에셋번들의 네이티브 메모리 복사본, 또 한번은 에셋 자체를 로드하기 위한 GPU나 시스템 메모리를 의미합니다.

3.4.2. AsssetBundle.LoadFromFile

유니티 5.3 업데이트 : 이 API는 유니티 5.3에서 이름이 변경되었습니다. 유니티 5.2와 그 이전에는 AssetBundle.CreateFromFile 이었고, 기능의 변화는 없습니다.

AssetBundle.LoadFromFile은 하드 디스크나 SD카드같은 로컬 저장소에서 압축되지 않은 에셋번들을 로드하는 목적의 고효율 API입니다. 만약 에셋번들이 압축되지 않았거나 LZ4 압축일 경우 이 API는 다음과 같이 동작하게 됩니다 :

모바일 디바이스 : 이 API는 에셋번들의 헤더 부분만 로드하고 나머지 데이터 부분은 디스크에 남겨둡니다. 에셋번들의 오브젝트는 로딩 메서드(AssetBundle.Load 같은)가 호출되거나 Instance ID가 역참조될 때 로드됩니다. 이 시나리오에서는 초과 메모리 사용은 없습니다.

유니티 에디터 : 이 API는 에셋번들 전체를 메모리에 로드합니다. 마치 바이트를 디스크에서 읽었고, AssetBundle.LoadFromMemoryAsync 를 사용한 것처럼 말이죠. 만약 유니티 에디터에서 해당 프로젝트를 프로파일링 해본다면, 이 API로 인해 에셋번들을 로딩하는 동안 메모리 사용량이 확 증가하는 현상을 보실 수 있습니다. 이는 디바이스 상에서 성능에 영향을 주지 않아야 하며 보강 행동(remedial action)을 취하기 전에 디바이스 상에서 다시 테스트가 진행되어야 합니다.

메모 : 유니티 5.3 이하이고 안드로이드 기기에서는 Streaming Assets 경로로부터 에셋번들을 로드하려고하면 이 API는 실패하게 됩니다. 왜냐하면 해당 경로의 컨텐츠는 압축된 .jar 파일 내에 존재하기 때문이죠. 더 자세한 내용이 궁금하다면, 에셋번들 사용 패턴 챕터배포- 프로젝트와 같이 전달 섹션을 읽어보세요. 이 이슈는 유니티 5.4에서 해결됐습니다. 유니티 5.4 이상으로 빌드된 게임은 Streaming Assets에서 에셋번들을 로드할 때 이 API를 사용할 수 있습니다.

메모 : AssetBundle.LoadFromFile 은 LZMA 압축된 에셋번들을 로드할 때 항상 실패합니다.

3.4.3. WWW.LoadFromCacheOrDownload

WWW.LoadFromCacheOrDownload 는 원격 서버나 로컬 저장 공간에서 오브젝트를 로드하기에 유용한 API입니다. 로컬 저장 공간에 있는 파일은 file:// URL을 이용해서 로드할 수 있습니다. 만약 어떤 에셋번들이 유니티 캐쉬에 있다면, 이 API는 AssetBundle.LoadFromFile 똑같이 동작하게 됩니다.

만약 에셋번들이 캐쉬되지 않았다면, WWW.LoadFromCacheOrDownload 는 에셋번들을 특정 소스에서 읽게 됩니다. 만약 에셋번들이 압축되어 있다면, 작업 쓰레드(worker thread)를 이용해서 압축 해제되고 캐쉬에 쓰여지게 됩니다. 압축되어 있지 않다면, 작업 쓰레드를 이용해서 바로 캐쉬에 쓰여지게 됩니다.

일단 에셋번들이 캐쉬되고 나면, WWW.LoadFromCacheOrDownload는 캐쉬에서 헤더 정보를 로드하고 에셋번들의 압축을 해제합니다. 그 후에는 AssetBundle.LoadFromFile을 이용해 에셋번들을 로드한 것과 똑같이 API가 동작하게 됩니다.

메모 : 데이터가 압축 해제되고 고정 크기 버퍼(fixed-size buffer)를 이용해 캐쉬되는 동안, WWW 오브젝트는 해당 에셋번들의 바이트에 대한 전체 복사본을 네이티브 메모리에 유지합니다. 이러한 에셋번들의 추가 복사는 WWW.bytes 속성을 지원하기 위해 유지되는 것 입니다.

WWW 오브젝트에서 에셋번들의 바이트를 캐슁하는 것에 대한 메모리 오버헤드 때문에, WWW.LoadFromCacheOrDownload를 사용하는 모든 개발자들은 에셋번들의 크기를 작게 만들도록 추천합니다 - 최대 몇 메가 바이트 이내로 말이죠. 모바일 기기처럼 제한된 메모리를 가진 플랫폼에서 동작시켜야하는 개발자들에게는, 메모리가 갑자기 증가하는 것을 피하기 위해 한 번에 단 하나의 에셋번들만 다운로드하도록 추천합니다. 에셋번들의 크기에 대해서 더 알고 싶다면 에셋번들 사용 패턴 챕터의 에셋 할당 전략 섹션을 참고해 주세요.

메모 : 이 API를 호출할 때마다 새로운 작업 쓰레드가 생성되게 됩니다. 이 API를 여러번 호출할 때 지나친 수의 쓰레드가 생성되지 않게 조심하십시오. 만약 5~10개의 에셋번들이 다운로드되어야 한다면, 동시에는 아주 적은 수의 에셋번들만 다운로드하도록 코드를 작성할 것을 추천드립니다.

3.4.4. AssetBundleDownloadHandler

유니티 5.3의 모바일 플랫폼에서 도입된 UnityWebRequest API는 유니티의 WWW API 보다 더 유연한 대안을 제공합니다. UnityWebRequest는 개발자들이 어떻게 유니티가 다운로드한 데이터를 다룰 지 정확히 설정할 수 있도록 해주고, 불필요한 메모리 사용을 없앨 수 있도록 해줍니다. UnityWebRequest를 통해 에셋번들을 다운로드할 수 있는 가장 간단한 방법은 UnityWebRequest.GetAssetBundle API를 이용하는 것 입니다.

이 가이드의 목적에 따라, 관심있는 부분은 DownloadHandlerAssetBundle 입니다. 이를 사용하면 WWW.LoadFromCacheOrDownload와 비슷하게 동작하게 됩니다. 작업 쓰레드를 이용해서 다운로드된 데이터를 고정 크기 버퍼에 스트리밍하고, 이렇게 버퍼에 담긴 데이터를 임시 저장 공간이나 에셋번들 캐쉬로 가져옵니다. 임시 저장 공간에 넣을 지, 에셋번들 캐쉬에 넣을 지는 Download Handler를 어떻게 설정했냐에 따라 결정됩니다. LZMA로 압축된 에셋번들은 다운로드 하는 중에 압축이 해제되고, 압축이 해제된 상태로 캐쉬되게 됩니다.

이런 모든 동작은 네이티브 코드 상에서 발생하게 되는데, 관리 힙을 늘리는 위험을 줄이기 위함입니다. 게다가 이 Download Handler는 모든 다운로드된 바이트에 대한 네이티브 코드 복사본을 유지하지 않고, 에셋번들을 다운로드할 때 발생하는 메모리 오버헤드를 줄입니다.

다운로드가 완료되었을 때,마치 다운로드된 에셋번들에 AssetBundle.LoadFromFile이 불린 것처럼, Download Handler의 assetBundle 속성은 다운로드된 에셋번들에 대한 접근을 제공합니다.

UnityWebRequest API는 WWW.LoadFromCacheOrDownload에 동일한 방법으로 캐슁을 지원합니다. 캐슁 정보가 Download Handler에 제공되고 요청한 에셋번들이 이미 유니티의 캐쉬에 존재한다면, 해당 에셋번들은 즉시 사용 가능한 상태가 되고 이 API는 AssetBundle.LoadFromFile과 동일하게 동작하게 됩니다.

메모 : 유니티의 에셋번들 캐쉬는 WWW.LoadFromCacheOrDownloadUnityWebRequest에서 공유됩니다. 둘 중 하나의 API로 다운로드된 에셋번들은 다른 하나의 API를 이용해 이용할 수 있게 됩니다.

메모 : WWW와는 다르게, UnityWebRequest 시스템은 작업 쓰레드에 대한 내부 풀(pool)과 내부 작업(job) 시스템이 있고, 이로 인해 개발자들은 초과 수량의 동시 다운로드를 할 수 없게 됩니다. 쓰레드 풀의 크기는 지금은 설정 불가능합니다.

3.4.5. 추천(Recommendations)

일반적으로, AssetBundle.LoadFromFile 이 가능하면 쓰여야 합니다. 이 API가 속도, 디스크 사용, 런타임 메모리 사용량에서 가장 효율적입니다.

에셋번들을 다운로드하거나 패치해야하는 프로젝트에서는, 유니티 5.3 이상에서는 UnityWebRequest를 사용할 것을 강력히 추천하고, 유니티 5.2 이하에서는 WWW.LoadFromCacheOrDownload를 사용할 것을 강력히 추천합니다. 다음 챕터에 나오는 배포 섹션에서 다루겠지만, 에셋번들 캐쉬를 프로젝트의 인스톨러에 있는 번들보다 높은 우선 순위를 가지게 하는 것이 가능합니다.

WWW.LoadFromCacheOrDownload를 사용할 때, 메모리 사용량의 갑작스러운 폭발로 인한 애플리케이션의 강제 종료를 예방하기 위해서, 프로젝트의 최대 메모리 예산이 2~3%보다 작게 에셋번들을 유지하는 것을 강력히 추천합니다. 대부분의 프로젝트에서, 에셋번들은 파일 크기로는 5MB를 넘어가지 않고, 동시에 1~2개의 에셋번들만 다운로드되어야 합니다.

WWW.LoadFromCacheOrDownloadUnityWebRequest를 사용할 때에, 에셋번들을 로드하고 나서는 다운로드 코드에서 적절하게 Dispose를 불러줘야 합니다. 대안으로는 C#의 using 문을 이용하는 것이 WWWUnityWebRequest를 안전하게 처리할 수 있는 가장 편리한 방법입니다.

실제 엔지니어링 팀에서 특별한 캐싱이나 다운로드가 필요한 프로젝트를 할 경우, 커스텀3 다운로드가 필요할 수도 있습니다. 커스텀 다운로더를 작성하는 것은 그리 쉬운 일이 아니고, 어떠한 커스텀 다운로더이던지 간에 AssetBundle.LoadFromFile과의 호환성이 지켜져야 합니다. 다음 챕터의 배포 섹션에서 더 자세한 내용을 다루겠습니다.

3.5. 에셋번들에서 에셋을 로드하기

에셋번들에서 UnityEngine.Object를 로드할 때에 AssetBundle 오브젝트에 붙어있는 3개의 API를 사용하면 됩니다 : LoadAsset, LoadAllAssets, LoadAssetWithSubAssets. 이 API들은 모두 비동기 방식에 대한 변형을 가지고 있고, 이들은 -Async라는 접미사가 붙습니다 : LoadAssetAsync, LoadAllAssetsAsync, LoadAssetWithSubAssetsAsync.

동기 API들은 비동기 API보다 최소한 1프레임은 더 빠릅니다. 이는 특히 유니티 5.1이하에서 맞습니다. 유니티 5.2 이전에 모든 비동기 API는 매 프레임마다 최대 하나의 오브젝트를 로드했습니다. 이는 LoadAllAssetsAsync와 LoadAssetsWithSubAssetAsync가 각각의 연관된 동기 API보다 심각하게 느릴 수 있음을 의미합니다. 유니티 5.2에 와서 이러한 동작이 개선되었는데요. 비동기 로드에서 매 프레임에 여러 개의 오브젝트를 로드하게 되는데, 최대량은 시간 조각(time-slice) 한계에 따라 결정됩니다. 이렇게 동작하는 기술적인 이유와 시간 조각(time-slicing)에 대한 더 자세한 내용을 알고 싶다면 3.5.1. 저 수준의 로딩 세부 사항 섹션을 읽어보세요.

LoadAllAssets 는 여러개의 독립된 UnityEngine.Object를 로드하는데에 사용됩니다. 이는 에셋번들의 대부분의 오브젝트가 로드되어야 하는 경우에만 사용하는 것이 좋습니다. 다른 2개의 API와 비교해서 LoadAllAssets는 LoadAssets를 여러번 호출하는 것보다 약간 더 빠릅니다. 따라서, 로드해야할 에셋의 수가 많고, 한 번에 로드되어야하는 수가 에셋번들 전체 컨텐츠의 2/3 이하인 경우에는, LoadAllAssets를 이용해서 그 에셋번들을 여러 개의 작은 번들로 나누는 것을 고려해보십시오.

LoadAssetWithSubAssets는 여러 개의 자식 오브젝트를 포함하는 복합 에셋을 로딩하는데에 사용되어야 하고, 이러한 에셋의 예로는 애니메이션을 여러 개 포함하고 있는 FBX 모델이라던지, 여러 개의 스프라이트를 포함하는 스프라이트 아틀라스같은 것이 있을 수 있습니다. 로드해야 할 오브젝트들이 모두 같은 에셋에 있는데, 다른 관계없는 오브젝트들이 많은 그러한 에셋번들에 저장되어 있는 경우에 이 API를 사용하십시오.

다른 경우에는 LoadAsset이나 LoadAssetAsync를 사용하십시오.

3.5.1. 저 수준의 로딩 세부 사항

UnityEngine.Object의 로딩은 메인 쓰레드 밖에서 일어납니다 : 저장 공간에서 오브젝트의 데이터를 읽는 것은 작업 쓰레드에서 일어납니다. 유니티의 쓰레드에 민감한 부분(스크립팅, 그래픽스)을 건드리지 않는 모든 것들은 작업 쓰레드에서 이루어 집니다. 예를 들어, VBO가 메쉬에서 생성되거나, 텍스쳐가 압축 해제되는 등이 있습니다.

유니티 5.3 이전에서는, 오브젝트 로딩은 순차적으로 이루어졌고, 오브젝트 로딩의 어떤 부분은 오직 메인 쓰레드에서만 이루어졌습니다. 이는 "통합(Integration)"이라고 부릅니다. 작업 쓰레드가 오브젝트의 데이터 로딩을 끝낸 후에, 새롭게 로드된 오브젝트를 메인 쓰레드로 통합시키기 위해 멈추는데, 메인 쓰레드의 통합작업이 끝나기 전까지 작업 쓰레드의 멈춤이 유지됩니다.

유니티 5.3 버전부터는, 오브젝트 로딩이 병렬적으로 진행되도록 변경되었습니다. 여러 개의 오브젝트들이 작업 쓰레드에서 역직렬화되고, 처리되어 통합되어 집니다. 오브젝트의 로딩이 끝나게 되면, Awake 콜백이 호출되고, 해당 오브젝트는 다음 프레임에 사용 가능하게 됩니다.

동기적인 AssetBundle.Load 메서드는 오브젝트의 로딩이 끝나기 전까지 메인 쓰레드를 정지시킵니다. 5.3 이전의 버전에서는 비동기적인 AssetBundle.LoadAsync 메서드는 메인 쓰레드에서 오브젝트를 통합하기 전까지는 메인 쓰레드를 정지시키지 않습니다. 물론 시간을 쪼개서 오브젝트를 로딩하기 때문에, 오브젝트 통합 과정에서 한 프레임에 일정 시간 이상의 시간을 잡아먹지는 않습니다. 한 프레임에 잡을 수 있는 시간은 Application.backgroundLoadingPriority 속성을 설정함으로써 변경할 수 있습니다.

  • ThreadPriority.High : 프레임 당 최대 50 ms
  • ThreadPriority.Normal : 프레임 당 최대 10 ms
  • ThreadPriority.BelowNormal : 프레임 당 최대 4 ms
  • ThreadPriority.Low : 프레임 당 최대 2 ms

유니티 5.1 이하에서, 비동기 API는 한 프레임에 하나의 오브젝트만 통합시킵니다. 이러한 동작은 버그로 인식되어 유니티 5.2에 수정되었습니다. 유니티 5.2부터는 오브젝트 로딩에 대한 프레임 시간 한계에 도달하기 전까지의 오브젝트들을 한꺼번에 로드합니다. AssetBundle.LoadAsync는 동기적 API보다 항상 더 오래 걸리는데, 이는 LoadAsync 호출을 하고나서부터 그 오브젝트가 쓸 수 있는 상태가 되기 전까지 최소 1프레임의 딜레이가 존재하기 때문입니다.

실제 오브젝트와 에셋을 가지고 테스트를 진행해보면 조금 다른 점을 볼 수 있습니다. 5.2 이전에는 저 사양 기기에서 꽤나 큰 텍스쳐를 로딩하는데에 동기적 메서드가 7 ms, 비동기적 메서드가 70 ms 걸렸습니다. 5.2 이후에는 그 차이가 거의 0에 가까워 졌습니다.

3.5.2. 에셋 번들 의존 관계

유니티 5의 에셋번들 시스템에서, 에셋번들 간의 의존성은 런타임 환경에 따라 자동으로 2개의 다른 API에 의해 추적되어 집니다. 유니티 에디터에서는, 에셋번들의 의존성은 AssetDatabase API를 통해서 확인할 수 있습니다. 에셋번들의 할당과 의존성은 AssetImporter API를 통해서 접근하고 변경될 수 있습니다. 런타임에는, 에셋번들 빌드 시에 만들어진 ScriptableObject 기반의 AssetBundleManifest API를 통해서 의존성 정보를 읽을 수 있습니다.

한 개 이상의 부모 에셋번들의 UnityEngine.Object가 한 개 이상의 다른 에셋번들의 UnityEngine.Object를 참조하고 있는 경우에 에셋번들이 다른 에셋번들에 "의존하고 있다"라고 말합니다. 오브젝트 내부 참조에 대한 더 세부적인 내용을 알고 있다면, 에셋과 오브젝트, 그리고 직렬화 글의 오브젝트 내부의 참조들 섹션을 참고해주세요.

저 글의 직렬화와 인스턴스 섹션에서 말하고 있듯이, 에셋번들은 에셋번들에 포함되어 있는 각 오브젝트의 File GUI와 Local ID에 의한 각 소스 데이터를 위한 소스 역할을 합니다.

오브젝트는 Instance ID가 처음으로 역참조되었을 때 로드되고, 에셋번들이 로드될 때 오브젝트에는 유효한 Instance ID가 할당되기 때문에, 에셋번들이 로드되는 순서는 중요하지 않습니다. 대신에, 오브젝트를 로드하기 전에 여기에 참조를 가지고 있는 모든 에셋번들을 로드하는 것이 중요합니다. 유니티는 부모 에셋번들이 로드될 때 자동으로 자식 에셋번들을 로드하지 않습니다.

예:

매터리얼 A가 텍스쳐 B를 참조한다고 가정해 보겠습니다. 매터리얼 A는 AssetBundle 1에 들어가 있고, 텍스쳐 B는 AssetBundle 2에 들어가 있습니다.

Asset bundle refering example


이러한 경우에 AssetBundle 2는 AssetBundle 1의 매터리얼 A가 로딩되기 이전에 로드되어야만 합니다.

이는 AssetBundle 1이 로드되기 전에 AssetBundle 2가 먼저 로드되어야만 함을 의미하지는 않습니다. 그리고 AssetBundle 2에서 Texture B가 명시적으로 먼저 로드되어야 함을 의미하지도 않습니다. AssetBundle 1의 매터리얼 A가 로드되기 전에 AssetBundle 2가 로드되어야 함을 의미합니다.

유니티는 AssetBundle 1이 로드될 때 자동으로 AssetBundle 2를 로드하지 않습니다. 이는 스크립트 상에서 수동으로 진행해줘야 합니다. AssetBundle 1과 2를 로드하는데에 사용되는 에셋번들 API는 상관없습니다. WWW.LoadFromCacheOrDownload를 통해서 로드되는 에셋번들은 AssetBundle.LoadFromFile이나 AssetBundle.LoadFromMemoryAsync를 통해서 로드된 에셋번들과 얼마든지 같이 사용될 수 있습니다.

3.5.3. AssetBundle manifests

BuildPipeline.BuildAssetBundles API를 통해서 에셋번들 빌드 파이프라인을 수행할 때, 유니티는 각 에셋번들의 의존성 정보에 포함된 오브젝트를 직렬화합니다. 이 데이터는 독립된 에셋번들에 저장되고, 여기에는 AssetBundleManifest 타입의 오브젝트가 하나 포함되어 집니다.

이 에셋은 빌드되는 에셋번들의 부모 디렉토리와 같은 이름의 에셋번들에 저장됩니다. 만약 프로젝트에서 (projectroot)/build/Client 폴더로 에셋번들을 빌드하게 되면, manifest를 포함하고 있는 에셋번들은 (projectroot)/build/Client/Client.manifest 에 저장되게 됩니다.

manifest를 포함하는 에셋번들 또한 다른 에셋번들처럼 로드될 수 있고, 캐쉬될 수 있고, 언로드될 수 있습니다.

AssetBundleManifest 오브젝트는 GetAllAssetBundles API를 제공하는데, 이 API를 통해 이 manifest에 동시에 빌드되는 모든 에셋번들을 리스트를 가져올 수 있습니다. 또한 특정 에셋번들의 참조를 가져올 수 있는 두 개의 메서드를 제공합니다.

AssetBundleManifest.GetAllDependencies는 에셋번들의 모든 참조를 반환하는데, 여기에는 에셋번들의 자식 뿐만 아니라, 그 하위 자식들까지도 모두 포함합니다.

AssetBundleManifest.GetDirectDependencies는 에셋번들의 자식까지만 반환합니다.

이 2개의 API는 모두 문자열 배열을 할당함을 알아 두십시오. 이 API는 너무 자주 쓰지 마시고, 애플리케이션의 생명주기에 있어서 성능에 민감한 부분에서는 사용하지 마십시오.

3.5.4. 추천

유저가 애플리케이션의 성능에 치명적인 부분에 들어서기 전에 가능한 필요한 오브젝트를 많이 로드하는 것이 좋습니다. 이런 데이터에는 메인 게임 레벨이라든지 월드 같은 것이 있습니다. 이는 특히 모바일 플랫폼에서 치명적인데, 로컬 저장 공간에 접근하는 것이 느리고, 플레이 시에 오브젝트를 메모리에 로드/언로드하는 것이 가비지 콜렉터가 동작하게 끔 만들 수 있습니다.

애플리케이션이 진행 중에 오브젝트를 로드/언로드 해야 하는 프로젝트의 경우에는, 에셋번들의 오브젝트를 언로드 하는 것에 대한 정보를 얻기 위해, 에셋번들 사용 패턴 글의 로드된 에셋의 관리 섹션을 보십시오.


  1. manifest는 일반적으로 소프트웨어에서 많이 사용되는 용어이므로 굳이 번역하지 않겠습니다. 단어 그대로 번역을 하자면 “적하 목록” 즉, 포함된 컨텐츠에 대한 리스트 정도로 생각하면 될 것 같습니다.

  2. raw data를 평소에 말할 때에는 그냥 “로우 데이터"라고 말하는데, 아무래도 번역을 하다보니 미가공 데이터라고 번역해도 괜찮을 것 같습니다.

  3. custom을 맞춤이라 해석할 수도 있지만, 보통 그냥 커스텀이라고 많이 읽고 쓰이기 때문에 따로 맞춤이라 번역하지 않겠습니다.

+ Recent posts