AssetBundle의 기본 요소     [원본 링크]


확인 완료한 버전 2017.3 - 난이도: 고급


이 문서는 유니티 AssetBundles 및 Resources 가이드의 4번째 챕터 입니다.


이 챕터에서는 AssetBundle에 대해 설명합니다. AssetBundle이 구축되는 기본 시스템과 AssetBundle과 상호작용하는 핵심 API를 소개합니다. 특히 AssetBundle의 로드와 언로드 그리고 AssetBundle에서 Asset과 Object를 로드, 언로드에 대해 설명합니다.


AssetBundle의 사용에 대한 패턴과 좋은 이용 예제는 다음 챕터에서 이야기 합니다.




3.1 개요

AssetBundle 시스템은 유니티가 인덱싱 하고 직렬화 할 수 있는 보관 형식으로 하나 이상의 파일을 보관하는 방법을 제공합니다. AssetBundle은 유니티 설치 후 비 코드 콘텐츠의 배포, 업데이트를 위한 기본 도구 입니다. 이를 총해 개발자는 더 작은 앱 패키지를 제출하고 런타임 메모리 부담을 최소화 하며 촤종 사용자의 장치에 최적화 된 콘텐츠를 선택적으로 로드 할 수 있습니다.


AssetBundle의 동작 방식을 이해하는 것은 모바일 장치를 대상으로 한 유니티 프로젝트의 성동적인 빌드에 필수입니다. AssetBundle 콘텐츠에 대한 전체 설명을 보려면 AssetBundle 문서를 보세요.




3.2 AssetBundle 레이아웃

요약하면 AssetBundle은 헤더와 데이터 세그먼트의 두 부분으로 구성됩니다.

헤더는 식별자, 압축 유형 및 매니페스트와 같은 AssetBundle의 정보가 들어있습니다. 매니페스트는 Object 이름으로 키가 지정된 조회 테이블 입니다. 각 엔트리는 AssetBundle의 데이터 세그먼트 내에서 주어진 Object가 어디서 찾을 수 있는지 나타내는 바이트 인덱스를 제공합니다. 대부분의 플랫폼에서, 이 조회 테이블은 균형 탐색 트리로 구현됩니다. 특히 윈도우나 OSX 파생 플랫폼(iOS 포함)에서는 red-black 트리를 사용합니다. 따라서 매니페스트를 구성하는데 걸리는 시간은 AssetBundle에 포함된 Asset의 수가 증가에 따라 선형적으로 증가합니다.


데이터 세그먼트는 AssetBundle내부의 Asset을 직렬화 하여 생성 된 원시 데이터가 포함되어 있습니다. 만약 LZMA가 압축 스키마로 지정되어 있으면 직렬화 된 Asset들의 전체 바이트 배열은 압축됩니다. LZ4가 대신 지정 된다면, 각 Asset들을 위한 바이트는 개별적으로 압축됩니다. 만약 압축을 사용하지 않으면, 데이터 세그먼트는 원시 바이트 스트림으로 유지됩니다.


유니티 5.3 이전에는 Object를 AssetBundle 내에 개별적으로 압축 할 수 없었습니다. 결과적으로 유니티 5.3 이전에는 하나 이상의 Object를 AssetBundle에서 불러오면 전체 AssetBundle을 압축 해제해야 했습니다. 일반적으로 유니티는 동일한 AssetBundle에서 후속 로드 요청에 대한 로딩 성능을 향상시키기 위해 압축 해제 된 AssetBundle의 복사본을 캐싱합니다.




3.3 AssetBundle 로딩

AssetBundle은 fivedistinct API를 통해 로드 됩니다. 이 다섯가지 API는 2가지 기준에 따라 다릅니다.

1. AssetBundle이 LZMA 또는 LZ4 아니면 비압축 상태인지 여부

2. AssetBundle이 로드되는 플랫폼에 따라서


API는 다음과 같습니다.

● AssetBundle.LoadFromMemory (Async optional)

● AssetBundle.LoadFromFile (Async optional)

● AssetBundle.LoadFromStream (Async optional)

● UnityWebRequest's DownloadHandlerAssetBundle

● WWW.LoadFromCacheOrDownload (on Unity 5.6 or older)


이 API들로 AssetBundle에 참조하는 방법은 자유롭게 혼합 사용이 가능합니다. 즉, UnityWebRequest를 통해 로드 된 AssetBundle은 AssetBundle.LoadFromFile 이나 AssetBundle.LoadFromMemory 를 통해 로드 된 것과 호환됩니다.



3.3.1 AssetBundle.LoadFromMemory(Async)

유니티는 이 API를 사용하지 않기를 권장합니다.

AssetBundle.LoadFromMemoryAsync 는 관리 코드 바이트 배열(C#의 byte[]) 에서 AssetBundle을 로드합니다. 이는 언제나 소스 데이터를 관리 코드 바이트 배열에서 새로 할당된 인접 기본 메모리 블록으로 복사합니다. AssetBundle이 LZMA 압축이라면 복사할 때 압축을 해제합니다. 비압축 또는 LZ4 압축 AssetBundle이라면 그대로 복사됩니다.


이 API가 소비하는 메모리의 최대량은 AssetBundle 크기의 2배가 됩니다: 하나는 API의해 생성된 기본 메모리에 있는 것 이며, 다른 하나는 관리 바이트 배열에서 API로 전달 한 것 입니다. 이 API를 통해 만들어진 AssetBundle에서 로드 한 Asset은 메모리에서 3번 중복 될 수 있습니다: 하나는 관리 코드 바이트 배열, 다른 하나는 기본 메모리의 AssetBundle, 마지막으로 GPU나 시스템 메모리에 있는 Asset 그 자체 입니다.


유니티 5.3.3 이전에는 이 API를 AssetBundle.CreateFromMemory 라고 불렀습니다. 기능상 차이점은 없습니다.



3.3.2 AssetBundle.LoadFromFile(Async)

AssetBundle.LoadFromFile 은 비압축 또는 LZ4 압축 AssetBundle을 하드디스크나 SD 카드와 같은 로컬 저장소에서 로딩하는데 효율적인 API 입니다.


데스크톱 독립형, 콘솔, 모바일 플랫폼에서 API는 AssetBundle의 헤더만 로드하고 나머지 데이터는 디스크에 남겨둡니다. AssetBundle의 Object는 로딩 메소드(AssetBundle.Load)의 호출이 있거나 인스턴스 ID가 참조 될 경우 로드 됩니다. 이 경우 초과 메모리 소비는 없습니다. 유니티 에디터에서, API는 디스크에서 데이터를 읽어내고 AssetBundle.LoadFromMemoryAsync가 사용 된 것 처럼 모든 AssetBundle을 메모리에 로드합니다. 이 API는 유니티 에디터에서 프로젝트를 프로파일 중일 때 메모리 스파이크가 나타난것으로 보여질 수 있습니다. 이는 기기에서의 성능에는 영향을 미치지 않을 수 있으며 추가 조치 전에 기기에서 스파이크가 일어나는지 테스트를 해 보아야 합니다.


참고: 유니티 5.3 이하의 안드로이드 디바이스 빌드에서, 이 API는 스트리밍 Asset 경로에서 AssetBundle을 로드하는것을 실패합니다. 이 문제는 유니티 5.4에서 수정되었으며 자세한 내용은 유니티 사용 패턴 챕터의 프로젝트와 함께 배포 부분을 참고하세요.



3.3.3 AssetBundleDownloadHandler

UnityWebRequest API는 개발자들이 유니티가 다운로드된 데이터를 처리하는 방법을 명확하게 지정하고 개발자가 불필요한 메모리 사용을 제거할 수 있게 합니다. UnityWebRequest를 사용하여 AssetBundle을 다운로드하는 가장 간단한 방법은 UnityWebRequest.GetAssetBundle의 호출 입니다.


이 가이드의 목적에 따라서, 관심 가질 클래스는 DownloadHandlerAssetBundle 입니다. 워커 스레드를 사용하여 다운로드 된 데이터를 고정 크기 버퍼로 스트리밍하고 버퍼된 데이터를 다운로드 핸들러의 설정에 따라 AssetBundle 캐시나 임시 저장소로 스풀링 합니다. 모든 작업은 네이티브 코드에서 이루어지며, 이는 힙 영역의 증가 부담이 없습니다. 추가로 이 다운로드 핸들러는 모든 다운로드 된 바이트에 대한 네이티브 코드를 유지하지 않기 때문에 AssetBundle 다운로드로 일어나는 메모리 오버해드를 줄입니다.


LZMA 압축 AssetBundle은 다운로드 중에 압축 해제되며 LZ4 압축을 사용하서 캐시됩니다.  이 동작은 Caching.CompressionEnabled 설정을 통해 변경할 수 있습니다.


다운로드가 완료되면 다운로드 핸들러의 AssetBundle 속성은 마치 AssetBundle.LoadFromFile이 호출 된 것 처럼 다운로드 된 AssetBundle에 대한 접근을 제공합니다.


만약 캐싱된 정보가 UnityWebRequest object에 제공되고 유니티의 캐시에 요청 된 AssetBundle이 이미 존재한다면 AssetBundle은 즉시 사용이 가능해지며 이 API는 AssetBundle.LoadFromFile과 동일하게 작동합니다.


유니티 5.6 이전에는 UnityWebRequest 시스템은 고정 워커 스레드 풀을 사용하고 내부 작업 시스템을 사용했습니다. 이는 동시에 UnityWebRequest 시스템의 과도한 동시 다운로드를 방지하기 위해서입니다. 스레드 풀의 크기는 설정이 불가능했습니다. 유니티 5.6에서 더 많은 최신 하드웨어를 수용하고 HTTP 응답 코드와 헤더에 빠른 접근을 허용하기 위해서 위의 방지는 제거되었습니다.



3.3.4 WWW.LoadFromCacheOrDownload

* 참고: 유니티 2017.1 부터 WWW.LoadFromCacheOrDownload 는 단순히 UnityWebRequest를 랩 합니다. 따라서 유니티 2017.1 이상을 사용하는 개발자는 UnityWebRequest로 마이그레이션 해야합니다.

WWW.LoadFromCacheOrDownload 는 앞으로의 릴리즈에서 더이상 사용되지 않습니다.


이하 정보는 유니티 5.6 이하 버전에 해당됩니다.


WWW.LoadFromCacheOrDownload 는 원격 서버와 로컬 저장소에서 Object를 로딩하는 API 입니다. 로컬 저장소의 파일은 file:// URL로 로드 가능합니다. 이미 AssetBundle이 유니티 캐시에 있으면 API는 AssetBundle.LoadFromFile과 같이 동작합니다.


만약 AssetBundle이 아직 캐시되지 않았다면 WWW.LoadFromCacheOrDownload 는 소스에서 AssetBundle을 읽어옵니다. 만약 AssetBundle이 압축된 경우, 워커 스레드를 사용하여 압축을 해제한 뒤 캐시에 올립니다. 압축되어 있지 않다면 워커 스레드를 통해 바로 캐시에 올라갑니다. AssetBundle이 캐시 되면 WWW.LoadFromCacheOrDownload는 압축 해제되어 캐시된 AssetBundle에서 해더 정보를 불러옵니다. 이후 API는 AssetBundle이 로드 된 AssetBundle.LoadFromFile과 동일하게 작동합니다. 이 캐시는 WWW.LoadFromCacheOrDownload와 UnityWebRequest가 공유합니다. 한 API에서 로드 한 AssetBundle은 다른 API를 통해서도 사용 가능합니다.


데이터가 압축 해제되고 고정 크기 버퍼를 통해 캐시로 올라갈 때, WWW Object는 AssetBundle의 전체 바이트 복사본을 네이티브 메모리에 유지합니다. 이 여분의 AssetBundle 복사본은 WWW.bytes 속성을 지원하도록 유지됩니다.


WWW Object에 있는 AssetBundle 바이트 캐시가 일으키는 메모리에 오버해드 때문에 AssetBundle은 최대 몇 메가바이트의 작은 크기로 남아있습니다. AssetBundle 크기 조절에 대한 더 자세한 내용은 AssetBundle 사용 패턴 챕터의 Asset 할당 전략 부분을 참고하세요.


UnityWebRequest와 달리, 이 API는 호출 할 때 마다 새로운 워커 스레드가 생성됩니다. 따라서, 모바일 장치와 같이 제한된 메모리를 가진 플랫폼에서는 메모리 스파이크를 방지하기 위해 이 API를 사용한다면 한번에 하나의 AssetBundle을 다운로드 해야합니다. 이 API를 여러번 호출하여 과도한 수의 스레드를 생성하는것을 조심하세요. 만약 5개 이상의 AssetBundle 다운로드가 필요하다면, 스크립트 코드에서 다운로드 큐를 만들고 관리하여 적은 수의 동시 다운로드를 유지하도록 해야합니다.



3.3.5 권장 사항

가능한 AssetBundle.LoadFromFile을 사용하세요. 이 API는 속도, 디스크 및 메모리 사용 측면에서 가장 효율적입니다.

AssetBundle의 다운로드나 패치가 필요하다면 유니티 5.3 이상의 프로젝트에서는 UnityWebRequest 사용을 권장합니다. 유니티 5.2 이하의 프로젝트에서는 WWW.LoadFromCacheOrDownload 사용을 권장합니다. 다음 챕터의 배포 부분에서 자세하게 설명하겠지만, 프로젝트 설치 프로그램에 포함 된 Bundle로 AssetBundle 캐시를 준비 할 수 있습니다.


UnityWebRequest 나 WWW.LoadFromCacheOrDownload를 사용 할 때 AssetBundle 로딩 후 다운로더 코드가 Dispose를 제대로 호출하도록 해야합니다. C#의 using statement를 사용하는것이 WWW나 UnityWebRequest를 안전하게 사용하는 가장 편리한 방법입니다.


실력있는 엔지니어링 팀이 존재하는 프로젝트는 특별한 방식의 캐싱이나 다운로드가 필요하다면 커스텀 다운로더를 고려할 수 있습니다. 커스텀 다운로더를 만드는것은 쉬운 엔지니어링 작업이 아닙니다. 커스텀 다운로더는 AssetBundle.LoadFromFile과 호환되어야 합니다. 자세한 사항은 다음 챕터의 배포 부분을 확인하세요.




3.4 AssetBundle에서 Asset 로드하기

UnityEngine.Object는 AssetBundle Object에 첨부된 3개의 API를 통해서 로드 될 수 있습니다. 이 API들은 동기적인 혹은 비동기적인 방식을 모두 가지고 있습니다.


LoadAsset (LoadAssetAsync)

LoadAllAssets (LoadAllAssetsAsync)

LoadAssetWithSubAssets (LoadAssetWithSubAssetsAsync)


이 API들의 동기적인 버전은 각각의 비동기적인 버전보다 최소 1프레임이라도 빠릅니다.


비동기 로드는 매 프레임마다 시분할 한도까지 다수의 Object를 로드합니다. 이 동작에 대한 기술적인 이유는 저레벨 로딩 상세 정보 부분에서 확인 가능합니다.


LoadAllAssets는 다수의 독립적인 UnityEngine.Object를 로드할 때 사용합니다. 이는 AssetBundle 내의 개부분 또는 모든 Object를 로드해야 할 때 사용해야합니다. 다른 2개의 API와 비효해서 LoadAllAssets는 LoadAsset을 여러번 호출하는것에 비해여 약간 빠릅니다. 따라서 한번에 로드 될 asset의 수가 많지만 AssetBundle의 66% 미안일 경우 AssetBundle을 작은 크기의 Bundle로 나누어 LoadAllAssets를 호출하는것을 고려해보세요.


LoadAssetWithSubAssets는 다수의 내장 Object를 가지고 있는 합성 Asset(애니메이션이 포함 된 FBX 모델이나 다수의 스프라이트가 포함 된 스프라이트 아틀라스 등)을 로드 할 때 사용해야합니다. 로드해야 할 Object들이 같은 Asset에서 비롯되지만 AssetBundle 내부에 다른 관련 없는 Object가 많다면 이 API를 사용하세요.


다른 경우 LoadAsset 또는 LoadAssetAsync를 사용하세요.



3.4.1 저레벨 로딩 상세 정보

UnityEngine.Object 로딩은 메인 스레드에서 수행됩니다: Object의 데이터는 워커 스레드가 저장소에서 읽어옵니다. 유니티 시스템에서 스레드에 민감한 부분을 건드리지 않는 항목은 워커 스레드에서 일어납니다. 예를 들어 메쉬에서 만들어지는 VBO나 텍스쳐 압축 해제와 같이 스크립팅이나 그래픽관련 부분들이 있습니다.


유니티 5.3 이후부터 Object 로딩은 병렬화 되었습니다. 워커 스레드에서 다수의 Object들이 역직렬화되고 처리되고 통합됩니다. Obejct의 로딩이 완료되면 Awake 콜백이 호출되며 해당 Object는 다음 프레임 부터 유니티 엔진에서 사용이 가능해집니다.


동기 AssetBundle.Load 메소드는 Object 로딩이 완료될 때 까지 메인 스레드를 정지시킵니다. 이는 시분할 Object 로딩이기 때문에 Object Integration은 프레임 시간의 밀리 초 이상을 차지하지 않습니다. 밀리 초의 수는 Application.backgroundLoadingPriority 설정에 의해서 결정됩니다.


● ThreadPriority.High: 프레임 당 최대 50 밀리 초

● ThreadPriority.Normal: 프레임 당 최대 10 밀리 초

● ThreadPriority.BelowNormal: 프레임 당 최대 4 밀리 초

● ThreadPriority.Low: 프레임 당 최대 2 밀리 초


유니티 5.2 이후부터 Object 로딩을 위한 프레임 시간의 한계까지 다수의 Object가 로드 됩니다. 다른 모든 요건이 동일하다고 가정하면, 비동기 방식의 asset 로딩 API는 비동기 요청과 Object가 엔진에서 사용 가능해지는 것 사이의 최소 1프레임 지연 때문에 동등한 동기 방식보다 무조건 더 오래 걸립니다.



3.4.2 AssetBundle 의존 관계

AssetBundle 사이의 의존 관계는 런타임 환경에 따라 2개의 다른 API를 사용하여 자동으로 추적됩니다. 유니티 에디터에서 AssetBundle 의존 관계는 AssetDatabase API를 통해 쿼리 가능합니다. AssetBundle 의 할당과 의존 관계는 AssetImpoter API를 통해서 접근하고 변경 가능합니다. 런타임에서 유니티는 AssetBundle이 빌드 되는 중에 생성되는 의존성 정보를 로드하기 위한 ScriptableObject 기반 AssetBundleManifest API와 같은 API를 제공합니다.


AssetBundle은 하나 이상의 부모 AssetBundle의 UnityEngine.Object가 다른 AssetBundle의 UnityEngine.Object를 참조한다면 다른 AssetBundle에 종속됩니다. Object간 참조에 대한 더 많은 정보는 Assets, Objets그리고 직렬화 챕터의 Object간 참조 부분을 확인하세요.


Assets, Objets그리고 직렬화 챕터 직렬화와 인스턴스 부분에서 설명한대로 AssetBundle은 AssetBundle에 포함된 Object의 파일 GUID와 로컬 ID로 식별되는 소스 데이터의 소스로 사용됩니다.


인스턴스 ID가 처음으로 참조될 때 Object가 로드 되고, AssetBundle이 로드 될 때 Object에 유효한 인스턴스 ID가 할당되기 때문에 AssetBundle의 로드 순서는 중요하지 않습니다. 대신, Object가 로드 되기 전에 Object의 의존 관계가 포함된 모든 AssetBundle을 로드하는 것이 중요합니다. 유니티는 부모 AssetBundle이 로드 될 때 하위 AssetBundle을 자동으로 로드하지 않습니다.


Example:

Material A가 Texture B를 참조한다고 가정. Material A는 AssetBundle 1에 패키징 되어있고 Texture B는 AssetBundle 2에 패키징 되어있음.




이 경우 AssetBundle 2는 AssetBundle 1에서 Material A를 로드하기 전에 로드 되어야 합니다.


이는 AssetBundle 2가 반드시 AssetBundle 1보다 먼저 로드되어야 함을 의미하지 않으며 Texture B가 명시적으로 AssetBundle 2에서 로드되어야 함을 의미하지 않습니다. 그저 AssetBundle 1에서 Material A를 로드 하기 전에 AssetBundle 2를 로드하면 됩니다.


하지만 유니티는 AssetBundle 1이 로드 될 때 AssetBundle 2를 자동으로 로드하지 않습니다. 이는 반드시 코드에서 수동으로 이루어집니다.


AssetBundle의 의존 관계에 대한 더 많은 정보는 메뉴얼을 참고하세요.



3.4.3 AssetBundle 매니페스트

BuildPipeline.BuildAssetBundle API를 사용해서 AssetBundle 빌드 파이프라인을 실행할 때, 유니티는 각 AssetBundle의 의존 관계 정보를 포함하여 Object를 직렬화 합니다. 이 데이터는 AssetBundleManifest 타입의 단일 Object를 포함하는 별도의 AssetBundle에 저장됩니다.


이 Asset은 AssetBundle이 빌드 될 때 상위 디렉토리와 동일한 이름을 가진 AssetBundle에 저장됩니다. 만약 프로젝트가 (projectroot)/build/Client/ 에 있는 폴더에 AssetBundle을 빌드한다면 매니페스트가 포함된 AssetBundle은 (projectroot)/build/Client/Client.manifest 로 저장됩니다.


매니페스트를 포함한 AssetBundle은 다른 AssetBundle과 똑같이 로드, 캐싱 또는 런로드가 가능합니다.


AssetBundleManifest Object는 GetAllAssetBundles API를 제공합니다. 이는 모든 매니페스트와 동시에 빌드된 모든 AssetBundle의 리스트와 특정 AssetBundle의 의존 관계를 쿼리 하는 2개의 메소드를 제공합니다.


AssetBundleManifest.GetAllDependencies AssetBundle의 하위 또는 하위의 하위 등 AssetBundle의 모든 계층적 의존 관계를 반환


AssetBundleManifest.GetDirectDependencies AssetBundle의 직접적인 하위 만 반환


이 두 API는 모두 문자열 배열을 할당합니다. 따라서 아껴서 사용해야하며 애플리케이션의 성능에 민감한 부분에서 사용하지 않아야 합니다.



3.4.4 권장 사항

대부분의 경우 플레이어가 애플리케이션에서 성능이 중요한 부분(메인 게임 영역)에 들어가기 전에 최대한 많은 Object들을 로드하는것이 좋습니다. 이는 특히 로컬 저장소에 대한 접근이 느리고 Object 오딩, 언로딩 등 메모리 변동으로 가비지 컬렉터가 실행되는 모바일 플랫폼에서 중요합니다.


애플리케이션이 동적이라 Object 로드 및 언로드가 필수인 프로젝트라면 Object와 AssetBundle의 언로드에 대해서 AssetBundle 사용 패턴로드 된 Asset의 관리 부분을 참고하세요.

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

유니티의 컴파일  (0) 2019.03.14
AssetBundle의 사용 패턴  (0) 2018.03.09
Resources 폴더  (0) 2018.03.05
Assets, Objects 그리고 직렬화  (0) 2018.03.04
AssetBundles 및 Resources 가이드  (0) 2018.03.03

+ Recent posts