콘텐츠로 이동

Unity 에셋 번들의 구조와 빌드·로드 흐름

Unity의 에셋 번들은 프로젝트 안의 프리팹, 이미지 아틀라스, 사운드 등 스크립트를 제외한 리소스를 묶을 수 있는 파일 단위다.

Unity는 Asset Labels로 이름 단위의 번들을 지정할 수 있게 하고, Variant 옵션으로 타겟에 따라 다른 리소스를 쓰게 할 수 있다.

에셋 번들은 header와 data segment로 나뉜다. 헤더는 에셋 번들의 식별 정보인 id와 인덱스 정보를 매핑한 lookup table로 구성되고, 데이터 세그먼트에는 여러 에셋 정보가 담긴다.

  • LZMA: 에셋을 통째로 압축한다.
  • LZ4(권장): 에셋을 chunk 단위로 나누어 압축한다.
  1. 에셋 번들 이름 설정 등으로 번들을 묶는다.
  2. 묶인 에셋을 빌드한다.
  3. 빌드 결과를 S3 같은 외부 저장소에 업로드한다.
  4. 애플리케이션 실행 중 필요할 때 UnityWebRequest로 내려받아 로드한다.

번들링으로 초기 애플리케이션 용량을 낮출 수도, 높일 수도 있다. 번들을 너무 잘게 만들면 빌드 비용과 다운로드 비용이 커지고, 너무 크게 만들면 런타임 메모리 비용이 커진다. 번들 용량은 로딩 시간에도 영향을 준다.

이 프로젝트에서는 에셋을 세 영역으로 나눴다.

  • default assets: 반드시 로컬에 있어야 하는 에셋
  • local assets: 필요에 따라 로컬에 두거나 다운로드하는 에셋
  • assets

서비스 중에 특정 이미지가 잘못되었다면 파일을 고친 뒤 애플리케이션을 새로 배포하지 않고 에셋 번들만 빌드해 올리면 된다. 슬롯마다 DLC처럼 필요할 때 받아서 로드하고, 필요 없으면 unload해 메모리 부족을 예방할 수 있다.

A 번들의 오브젝트가 B 번들의 오브젝트를 참조하면 의존성이 생긴다. 각 번들의 .manifest와 플랫폼 manifest가 이 관계 정보를 가진다. 번들을 나눌 때는 의존성이 생기지 않게 한다.

여러 번들이 참조하는 에셋을 어느 번들에도 지정하지 않으면, Unity는 빌드할 때 그 에셋을 참조하는 번들마다 복사해 넣는다. 공용 셰이더나 기본 UI 아틀라스처럼 여러 곳에서 쓰는 에셋은 별도의 공용 번들로 묶어 중복을 없앤다. 공용 번들은 Unity가 자동으로 먼저 로드하지 않으므로, 의존하는 번들보다 먼저 로드한다. 의존 번들 목록은 AssetBundleManifest.GetAllDependencies로 얻는다(Unity 매뉴얼: AssetBundle dependencies).

LOBBY에서 GAME으로 씬을 전환할 때 Unity는 Resources.UnloadUnusedAssets를 호출하는데, 순서가 다음과 같다.

  1. GAME 로드
  2. LOBBY 해제(Destroy)

그래서 특정 시점에는 GAME과 LOBBY의 리소스가 모두 메모리에 올라와 메모리 사용량이 일시적으로 커진다. 이를 막으려고 GAME과 LOBBY 사이에 상대적으로 가벼운 SLOT_LOADING 씬을 두었다. 에셋 번들도 slot_1_loading과 slot_1_처럼 로딩용과 본편으로 나눠 구성했다.

비슷하게 게임을 처음 실행할 때는 OS가 제공하는 스플래시를 보여 주거나 가벼운 씬을 먼저 올려, 디스크의 리소스가 메모리에 올라올 시간을 번다.

UnityWebRequestAssetBundle.GetAssetBundle에 Hash128이나 uint 버전을 넘기면, 내려받은 번들이 디스크 캐시에 저장되고 Caching API로 관리된다. 같은 이름과 버전의 번들을 다시 요청하면 다운로드 없이 캐시에서 로드한다. 이 해시는 버전 식별자일 뿐이고 Unity가 다운로드 중에 번들 내용으로 해시를 계산해 비교하지 않으므로, 새 번들을 배포할 때마다 넘기는 값을 바꿔야 한다. 플랫폼 manifest의 GetAssetBundleHash로 번들별 해시를 얻어 이 값으로 쓴다. 내용 검증은 CRC로 한다(Unity 매뉴얼: AssetBundle caching, Scripting API: Caching).

StreamingAssets는 다운로드 캐시와 다른 개념이다. 빌드할 때 Assets/StreamingAssets 폴더의 파일이 그대로 앱에 포함되고, 런타임에는 읽기 전용이다. 서버 없이 첫 실행에 꼭 필요한 번들을 여기에 넣어 두고 앱 시작 시 바로 로드하는 용도로 쓴다. 경로는 Application.streamingAssetsPath로 얻고, Android와 Web에서는 파일 시스템 API로 직접 읽을 수 없어 UnityWebRequest로 읽는다(Unity 매뉴얼: StreamingAssets).

앱에 포함한 번들은 플랫폼별로 다음 StreamingAssets 경로에 있다.

#if UNITY_ANDROID
path = "jar:file//" + Application.dataPath + "!/assets";
#elif UNITY_IOS
path = Application.dataPath + "/Raw";
#else
path = Application.dataPath + "/StreamingAssets";
#endif

AssetBundle Browser를 임포트하면 에디터 안에서 전체 에셋 번들 구성을 확인할 수 있다.