AssetBundle의 사용 패턴      [원본 링크]


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



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



이전 챕터에서는 다양한 로딩 API의 저레벨 동작을 포함하는 AssetBundle의 기본 요소에 대해서 설명했습니다. 이번 챕터에서는 AssetBundle을 사용하는 다양한 측면에 대한 문제와 잠재적 해결 방법에 대해 이야기 합니다.




로드 된 Asset 관리


메모리에 민감한 환경에서는 로드 된 Object의 크기와 수를 신중하게 제어하는것이 중요합니다. 유니티는 Object가 활성화된 씬에서 제거될 때 자동으로 Object를 언로드 하지 않습니다. Asset은 특정 시간에 정리되며 수동으로 정리 실행이 가능합니다.


AssetBundle 자체는 신중하게 관리되어야 합니다. AssetBundle은 로컬 저장소의 파일에서 불러오기 때문에(유니티 캐시 또는 AssetBundle.LoadFromFile을 통해 로드 된 파일) 수십 킬로바이트 이상 소모하는 일이 잘 없을 정도로 적은 메모리 오버 헤드를 가집니다. 하지만 많은 수의 AssetBundle이 있다면 오버 헤드 문제가 발생할 수 있습니다.



대부분 프로젝트는 사용자가 레벨 재생과 같이 콘텐츠를 다시 경험할 수 있기 때문에 AssetBundle을 언제 로드하고 언로드할지 잘 알아야 합니다. AssetBundle이 잘못 언로드 된다면 메모리에서 Object 복제가 일어날 수 있습니다. AssetBundle의 잘못된 언로딩은 texture 누락 등 의도하지 않은 문제 동작이 일어날 수 있습니다. 이것이 가능한 이유를 알아보려면 Asset, Object 그리고 직렬화 챕터의 Object간 참조 부분을 참고하세요.



asset과 AssetBundle을 관리할 때 이해해야 할 가장 중요한 것은 AssetBundle.Unload를 호출할 때 unloadAllLoadedObjects 파라미터의 참과 거짓 설정에 따른 차이 입니다.



이 API는 호출 된 AssetBundle의 헤더 정보를 언로드 합니다. unloadAllLoadedObjects 파라미터는 AssetBundle에서 모든 Object를 언로드 할지를 결정합니다. 참으로 설정하면 AssetBundle에서 비롯된 모든 객체가 현재 씬에서 사용중이라도 즉시 언로드 됩니다.




예를 들어 material M이 AssetBundle AB에서 로드되었고 지금 M 이 활성 씬에 있다고 가정합니다.



만약 AssetBundle.Unload(true) 가 호출 된다면 M은 씬에서 지워지고 파괴된 뒤 언롣 됩니다.

하지만 만약 AssetBundle.Unload(false) 가 호출된다면 AB의 헤더 정보는 언로드 되지만 M은 씬에 남고 계속 작동합니다. AssetBundle.Unload(false)를 호출하는 것은 M과 AB 사이의 링크를 끊는 것 입니다. 만약 AB가 다시 로드 된다면, AB를 포함한 새로운 Object가 메모리에 로드됩니다.




만약 AB가다시 로드 된다면, AssetBundle의 새로운 헤더 정보가 다시 로드됩니다. 하지만 M은 새로운 AB에서 로드 된 것이 아닙니다. 유니티는 새로운 AB와 기존 M 사이의 링크를 정립하지 못합니다.



만약 AssetBundle.LoadAsset() 이 M을 리로드 하기 위해 불렸다면, 유니티는 오래된 M을 AB의 데이터 인스턴스로 인식하지 않습니다. 따라서, 유니티는새로운 M을 로드하고 씬에는 2개의 M이 있게 됩니다.


대부분 프로젝트에서 이 행동은 의도되지 않은 것 입니다. AssetBundle.Unload(true)를 사용하고 Object가 복제되지 않는 방법을 선택해야 합니다. 두가지 일방적인 방법은 다음과 같습니다.


1. 애플케이션의 라이프타임 중 로딩 스크린이나 레벨 사이같이 일시적인 AssetBundle이 언로드 되는 잘 정의된 포인트를 가지는 것 입니다. 이것이 간단하고 가장 일반적인 옵션입니다.


2. 각 Object들의 참조 카운트를 유지하고 모든 구성 Object가 사용중이지 않을 때 AssetBundle을 언로드 하는것 입니다. 이렇게 하면 애플리케이션이 메모리 복제 없이 각 Object를 언로드, 리로드 할 수 있습니다.


애플리케이션에서 AssetBundle.Unload(false)를 사용한다면, 각 Object는 2가지 방법으로만 언로드 가능합니다.


1. 씬과 코드에서 원하지 않는 Object에 대한 참조를 모두 제거합니다. 이것이 완료된 후 Resources.UnloadUnusedAssets를 호출합니다.


2. 씬을 지금 씬에 추가되지 않게 로드합니다. 이는 지금 씬의 모든 Object를 파괴하고 Resources.UnloadUnusedAssets를 자동 호출 합니다.


만약 프로젝트에 Object들이 로드, 언로드 될 때 게임 모드나 레벨 사이와 같이 유저가 기다릴 수 있는 잘 정의된 지점이 있다면, 최대한 많은 Object들이 언로드 되고 새로운 Object를 로드하게 해야합니다.


가장 간단한 방법은 프로젝트의 별개 청크들을 씬에 패키징 하고 모든 의존 관계가 있는것을 씬과 함께 AssetBundle로 빌드하는 것 입니다. 이렇게 하면 애플리케이션이 로딩 씬에 들어갈 때 이전 씬의 모든 AssetBundle을 언로드 하고 새로운 씬을 포함안 AssetBundle을 로드하게 됩니다.


이것이 가장 간단한 흐름이지만, 일부 프로젝트는 보다 복잡한 AssetBundle 관리가 필요합니다. 모든 프로젝트가 다르기 때문에 보편적인 AssetBundle 디자인 패턴은 없습니다.


AssetBundle에 Object를 어떻게 그룹화 하여 넣을 지 결정할 때 Object들이 동시에 로드되고 업데이트 되는 것들을 AssetBundle에 같이 넣는것이 일반적으로 좋습니다. 롤 플레잉 게임을 예로 들어보자면, 각 맵과 컷씬은 씬 별로 AssetBundle 그룹화 시킬 수 있지만 일부 Object들은 대부분 씬에서 필요합니다. AssetBundle은 초상화, 인게임 UI, 다양한 캐릭터 모델과 텍스처를 제공하도록 제작 될 수 있습니다. 후자의 Object와 Asset은 애플리케이션 시작 시 로드하여 계속 남아있도록 하는 AssetBundle로 그룹화 될 수 있습니다.


유니티에서 언로드 된 AssetBundle에서 Object를 리로드 하려고 할 때 또 다른 문제가 생길 수 있습니다. 이 경우, 리로드는 실패하고 Object는 유니티 에디터 하이어라키에 (missing) Object로 나타납니다.


이는 주로 모바일 앱이 중지되거나 PC를 잠그는 등의 경우 유니티가 그래픽 건텍스트에 대한 컨트롤을 잃고 되찾을 때 일어납니다. 이 경우 유니티는 텍스처와 쉐이더를 GPU에 다시 업로드 해야합니다. 만약 이 asset들에 대한 소스 AssetBundle을 사용할 수 없는 경우, 애플리케이션은 씬에서 Object를 자홍색으로 렌더링합니다.




4.2 배포

프로젝트의 AssetBundle을 클라이언트에 배포하는것에는 2가지 기본적인 방밥이 있습니다. 프로젝트와 동시에 설치하거나 설치 후 다운로드하는 것 입니다.


AssetBundle을 설치와 함께 또는 설치 후 제공하는것은 프로젝트를 실행 할 플랫폼의 기능과 제한사항에 의해 결정됩니다. 모바일 프로젝트는 최초 설치 크기를 줄이고 무선 다운로드 수를 제한 이하로 줄이기 위해서 설치 후 다운로드를 주로 선택합니다. 콘솔이나 PC 프로젝트는 보통 최초 설치시 AssetBundle을 제공합니다.


적절한 아키텍처를 사용하면 AssetBundle의 최초 전달 방법과 상관 없이 새로운 패치나 수정사항 적용이 가능합니다. 이에 관련된 더 많은 정보는 유니티 메뉴얼의 AssetBundle로 패치하기 문서를 참고하세요.



4.2.1 프로젝트화 함께 제공하기

AssetBundle을 프로젝트와 함께 제공하는것은 추가적인 다운로드 관리 코드가 필요없기 때문에 가장 간단한 방법입니다. 프로젝트의 AssetBundle을 설치와 동시에 포함하는 것 에는 2가지 주요 이유가 있습니다.


● 프로젝트의 빌드 횟수를 줄이고 더 단순한 반복 개발을 가능하게 합니다. 만약 AssetBundle들이 애플리케이션과 별도로 업데이트 될 필요가 없다면 AssetBundle은 Streaming Assets에 저장하여 애플리케이션에 포함 할 수 있습니다. 아래의 Streaming Assets 부분을 참고하세요.


● 업데이트 가능한 콘텐츠의 초기 버전을 제공합니다. 이것은 보통 초기 설치 후 최종 사용자의 시간을 절약하거나 나중에 패치할 때 기본으로 사용합니다. Streaming Assets는 이 경우 이상적이지 않습니다. 하지만 커스텀 다운로딩, 캐싱 시스템을 작성하는 것이 옵션이 아니라면, 업데이트 가능한 콘텐츠의 초기 리비전을 Streaming Assets의 유니티 캐시에 로드 할 수 있습니다.


4.2.1.1 Streaming Assets

설치 시 유니티 애플리케이션에 AssetBundle을 포함한 다양한 유형의 콘텐츠를 포함시키는 가장 쉬운 방법은, 프로젝트 빌드 시 콘텐츠를 /Assets/StreamingAssets/ 폴더에 빌드하는 것 입니다. 빌드 시 StreamingAssets 폴더에 포함된 모든 콘텐츠는 최종 애플리케이션에 포함됩니다.


런타임 중 로컬 저장소의 StreamingAssets 폴더에 대한 모든 경로는 Application.streamingAssetsPath 를 통해서 접근 가능합니다. AssetBundle은 대부분 플랫폼에서 AssetBundle.LoadFromFile을 통해 로드 가능합니다.


안드로이드 개발자: 안드로이드에서는 StreamingAssets 폴더가 APK에 저장되며 APK가 압축된 방식에 따라 로드될 때 시간이 더 걸릴 수 있습니다. 압축 시 사용된 알고리즘은 유니티 버전에 따라 다를 수 있습니다. 7-zip 과 같은 아카이버를 사용하여 파일이 압축되어있는지 알아내기위해 APK를 열 수 있습니다. 압축되어 있다면, AssetBundle.LoadFromFile() 은 느려질 수 있습니다. 이럴 경우 UnityWebRequest.GetAssetBundle을 사용하여 캐싱 된 버전을 검색할 수 있습니다. UnityWebRequest를 사용하면 AssetBundle이 첫 실행 때 압축 해제되어 캐싱되므로 실행 속도가 빨라집니다. 이 경우 AssetBundle이 캐시에 복사되어 더 많은 저장 장소를 필요하다는 것을 기억하세요. 다른 방법으로, 빌드 시 Gradle 프로젝트를 익스포트하여 AssetBundle 확장을 추가 할 수 있습니다. 그 다음 build.gradle 파일을 편집하여 해당 확장을 noCompress 섹션에 추가 할 수 있습니다. 이것을 완료하면, AssetBundle.LoadFromFile()을 압축 해제 비용 없이 사용할 수 있습니다.


참고: 일부 플랫폼에서 Streaming Assets는 쓰기 가능한 위치가 아닙니다. 만약 프로젝트의 AssetBundle이 설치 후 업데이트 되어야 한다면 WWW.LoadFromCacheOrDownload를 쓰거나 커스텀 다운로더를 작성해야합니다.


4.2.2 설치 후 다운로드

모바일 디바이스에 AssetBundle을 전달하는 가장 좋은 방법은 앱 설치 후에 다운로드 받는 것 입니다. 이는 또한 애플리케이션 설치 후 콘텐츠 업데이트를 위해 전체 재설치를 하지 않도록 합니다. 많은 플랫폼에서, 애플이케이션 바이너리는 고비용의 긴 재 인증 과정을 거쳐야 합니다. 따라서, 설치 후 다운로드릉 위한 좋은 시스템을 개발하는 것은 중요합니다.


AssetBundle을 전달하는 가장 간단한 방법은 웹서버에 두고 UnityWebRequest를 사용하는 것 입니다. 유니티는 다운로드한 AssetBundle을 로컬 저장소에 자동으로 캐시합니다. 만약 다운로드한 AssetBundle이 LZMA 압축되어 있다면, 다음에 빠르게 로딩하기 위해서 압축 해제되거나 LZ4로 재압축 하여 (이는 Caching.compressionEnabled 설정에 따름 저장됩니다. 캐시가 가득 찬다면, 유니티는 최근 가장 사용하지 않은 AssetBundle을 캐시에서 제거합니다. 자세한 사항은 빌트인 캐싱 부분을 참고하세요.


보통 가능하다면 UnityWebRequest 사용을 권장합니다. 만약 유니티 5.2 이하의 버전을 사용한다면 WWW.LoadFromCacheOrDownload 를 사용하세요. 빌트 인 API의 성능이나 메모리 사용량, 캐싱 동작 등이 프로젝트에 적합하지 않거나 플랫폼에 적합하지 않다면 커스텀 다운로드 시스템을 개발하세요.


UnityWebRequest 또는 WWW.LoadFromCacheOrDownload 의 사용이 제한되는 상황

● AssetBundle 캐시에 대한 세밀한 제어가 필요할 때

● 프로젝트에 커스텀 압축 방식이 필요할 때

● 비활성화 상태에서의 데이터 스트리밍과 같이 특정 플랫폼 별 API를 사용해야 할 경우

예: IOS 백그라운드 테스크 API 를 사용

● 유니티가 적절한 SSL 지원을 제공하지 않는 플랫폼(PC와 같은)에 SSL을 통해 AssetBundle을 전달해야 하는 경우



4.2.3 빌트 인 캐싱

유니티는 AssetBundle 버전 번호를 인수로 한 오버로드를 가진 UnityWebRequest API를 통해 다운로드 된 AssetBundle을 캐시하는 데 사용 가능한 빌트 인 AssetBundle 캐싱 시스템이 있습니다. 이 번호는 AssetBundle 내부에 저장되지 않고, AssetBundle 시스템에 의해 생성되지 않습니다.


캐싱 시스템은 UnityWebRequest에 전달한 마지막 버전 번호를 계속 추적합니다. API가 버전 번호와 같이 호출되면 캐싱 시스템은 버전 번호와 비교하여 캐시 된 AssetBundle이 있는지 확인합니다. 이 번호가 일치하면 시스템은 캐시 된 AssetBundle을 로드합니다. 만약 번호가 일치하지 않거나 캐시 된 AssetBundle이 없으면, 유니티는 새로 다운로드 합니다. 다운로드 된 AssetBundle은 새로운 버전 번호와 연결됩니다.


캐싱 시스템의 AssetBundle은 파일 이름으로만 식별됩니다. 다운로드 된 전체 URL로 구분되지 않습니다. 이는 CDN 처럼 같은 이름의 AssetBundle 파일을 여러 장소에 저장할 수 있다는 것을 의미합니다. 파일 이름이 동일하면 캐싱 시스템은 같은 AssetBundle로 인식합니다.


AssetBundle에 버전 번호를 할당하는 적절한 방법을 결정하고 이러한 번호를 UnityWebRequest에 전달하는 것은 각 애플리케이션에 달려있습니다. 번호는 CRC값과 같이 일종의 고유한 식별자 입니다. AssetBundleManifest.GetAssetBundleHash() 또한 이러한 용도로 사용 가능하지만, 이 함수를 버전 관리에 사용하지 않기를 권장합니다. 이는 추정값을 제공할 뿐 실제 hash 연산을 하지 않습니다.


자세한 사항은 AssetBundle로 패치하기 부분을 참고하세요.


유니티 2017.1 이후 버전에서는 개발자가 여러 캐시에서 활성 캐시를 선택 할 수 있도록 세분화된 제어 기능을 제공하기 위해서 Caching API가 확장되었습니다. 이전 버전의 유니티에서는 캐시된 항목을 제거하기 위해서 Caching.expirationDelay Caching.maximumAvailableDiskSpace 를 수정했습니다. (이러한 속성은 유니티 2017.1에서 Cache 클래스에 남아있습니다)


expirationDelay는 AssetBundle이 자동으로 삭제되기 전에 경과해야 하는 최소 시간(초) 입니다. 이 시간동안 AssetBundle에 대한 접근이 없으면 자동으로 삭제됩니다.


maximumAvailableDiskSpace는 expirationDelay 이전에 사용 된 AssetBundle 삭제를 시작하기 전에 캐시가 사용할 수 있는 로컬 저장소의 공간을 지정합니다.  한계에 도달하면, 유니티는 캐시에서 최근 가장 적게 사용 된 (또는 Caching.MarkAsUsed로 표시 한) AssetBundle을 삭제합니다. 유니티는 새로운 다운로드에 충분한 공간을 확보할 때 까지 캐시된 AssetBundle을 삭제할 것입니다.


4.2.3.1 캐시 준비하기

AssetBundle은 파일 이름으로 식별되기 때문에, 애플리케이션과 함께 제공되는 AssetBundle로 캐시를 준비 할 수 있습니다. 이것을 하기위해서 초기 또는 기본 보전의 AssetBundle을 /Assets/StreamingAssets/ 에 저장하세요. 이 과정은 프로젝트와 함께 제공하기 부분에 설명한 것과 동일합니다.


애플리케이션이 처음 실행 될 때 캐시는 Application.StreamingAssetsPath로 로드 되는 AssetBundle로 채워질 수 있습니다. 그 후, 애플리케이션은 UnityWebRequest를 정상적으로 호출 할 수 있습니다. (UnityWebRequest는 AssetBundle을 StreamingAssets 경로에서 불러올 수 있습니다.)

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

유니티의 컴파일  (0) 2019.03.14
AssetBundle의 기본 요소  (0) 2018.03.07
Resources 폴더  (0) 2018.03.05
Assets, Objects 그리고 직렬화  (0) 2018.03.04
AssetBundles 및 Resources 가이드  (0) 2018.03.03

+ Recent posts