섹션 6 : 기본적인 에셋 번들 사용법

유니티 5에서는 에셋 번들을 불러오는데 4가지의 API를 사용할 수 있다. 이 4가지 API의 동작은 에셋 번들이 로드되는 플랫폼과 에셋 번들이 빌드될 때 사용되는 압축방법(Uncompressed, LZMA, LZ4)에 따라 다르다.


작업에 사용할 4가지 API는 다음과 같다 :

- AssetBundle.LoadFromMemoryAsync

- AssetBundle.LoadFromFile

- WWW.LoadfromCacheOrDownload

- UnityWebRequest’s DownloadHandlerAssetBundle (Unity 5.3 or newer)


AssetBundle.LoadFromMemoryAsync

이 함수는 에셋 번들 데이터가 들어있는 바이트 형식의 배열을 가져온다. (선택사항)원한다면 CRC 값을 전달할 수 있다. 에셋 번들이 LZMA 방식으로 압축되어 있다면 번들을 로딩하는 동안 에셋 번들을 압축 해체해야 한다. LZ4 방식으로 압축된 에셋 번들은 압축되어 있는 상태에서도 로드가 가능하다.


다음은 이 메서드를 사용하는 방법의 예시이다.

using System.IO; using UnityEngine; public class AssetBundleLoadExample : MonoBehaviour { IEnumerator LoadFromMemoryAsync(string path) { AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path)); yield return createRequest; AssetBundle bundle = createRequest.assetBundle; var prefab = bundle.LoadAsset<GameObject>("MyObject");

Instantiate(prefab); } }

하지만 이것이 LoadFromMemoryAsync를 사용하는 유일한 방법은 아니다. File.ReadAllByte(path)는 바이트 배열을 얻은 임의의 절차로 대체될 수 있다.


AssetBundle.LoadFromFile

이 API는 로컬 저장소에서 압축되지 않은 에셋 번들을 로드할 때 매우 효율적이다. LoadFromFile은 압축되지 않았거나 청크 기반(LZ4)으로 압축된 번들의 경우 디스크로부터 직접 에셋 번들을 로드해 온다. 이 메서드로 완적히 압축된(LZMA) 에셋 번들을 로드하면 먼저 메모리에 올리기 전에 압축을 해제한다.


다음은 LoadFromFile을 사용하는 예제이다 :

using System.IO;
using UnityEngine;

public class LoadFromFileExample : MonoBehaviour
{
void Start()
    {
var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle"));
if (myLoadedAssetBundle == null)
{
Debug.Log("Failed to load AssetBundle!");
return;
}
var prefab = myLoadedAssetBundle.LoadAsset<GameObject>("MyObject");
Instantiate(prefab);
}
}

참고 :: 유니티 5.3 이하의 안드로이드 기기에서는 스트리밍 에셋 경로에서 에셋번들을 로드하려고 할때 이 API가 실패한다. 이는 해당 경로에 압축된 .jar 파일 내에 존재하기 때문이다. 유니티 5.4 이상에서는 Streaming Assets과 함께 이 API 호출을 사용할 수 있다.



WWW.LoadFromCacheOrDownload

이 API의 사용은 권장하지 않는다.(UnityWebRequest를 사용하라) 이 API는 원격 서버에서 에셋 번들을 다운로드하거나 로컬 에셋 번들을 로드하는데 유용하다. 이것은 구 버전의 API로 UnityWebRequest API를 사용할 것을 권장한다.


원격 위치에서 에셋 번들을 로드하면 에셋 번들이 자동으로 캐시된다. 만약 에셋 번들이 압축된 상태라면 작업 스레드에서 에셋 번들의 압축을 해제하여 캐시에 등록한다. 압축이 해제되고 캐시된 에셋 번들은 AssetBundle.LoadFromFile과 똑같이 로드된다.


다음은 LoadFromCacheOrDownload를 사용하는 예제이다.

using UnityEngine;
using System.Collections;

public class LoadFromCacheOrDownloadExample : MonoBehaviour
{
IEnumerator Start()
{
while (!Caching.ready)
yield return null;

var www = WWW.LoadFromCacheOrDownload("http://myserver.com/myassetBundle.unity3d", 5);
yield return www;
if (!string.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
yield return null;
}
var myLoadedAssetBundle = www.assetBundle;

var asset = myLoadedAssetBundle.mainAsset;
}
}

AssetBundle의 바이트를 WWW 객체에 캐싱하는 메모리 오버헤드가 발생하기 때문에 WWW.LoadFromCacheOrDownload를 사용하는 모든 개발자는 AssetBundle의 크기가 몇 메가(원문에서는 a few megabytes) 수준을 넘지 않게 유지하는 것이 좋다. 또한 모바일 기기와 같이 메모리가 제한된 플랫폼에서 작업하는 개발자는 메모리 스파이크를 피하기 위해 코드가 한 번에 하나의 에셋 번들만 다운로드하도록 하는 것이 좋다.


만약 캐시 폴더에 추가 파일을 캐시할 여유 공간이 없다면, LoadFromCacheOrDownload는 새 에셋 번들을 저장할 충분한 공각이 확보될 때까지 캐시에서 사용된 시점이 가장 오래된 에셋 번들을 삭제할 것이다. 더 이상 공간을 만들 수 없는 경우(하드 디스크가 가득 찼거나 캐시의 모든 파일이 현재 사용 중인 경우), LoadFromCacheOrDownload는 캐싱을 하지 않고 파일을 메모리에 스트리밍할 것이다.


LoadFromCacheOrDownload를 강제하려면 version 매개변수(두 번째 매개변수)를 변경해야 한다. 에셋 번들은 함수로 전달된 버전이 현재 캐시된 에셋 번들의 버전과 일치하는 경우에만 캐시에서 로드된다.



UnityWebRequest

UnityWebRequest는 에셋 번들을 처리하기 위한 특정 API를 가지고 있다. 먼저 UnityWebRequest.GetAssetBundle을 사용하여 웹 요청을 생성하면, 요청을 반환한 후 요청 객체를 DownloadHandlerAssetBundle.GetContent(UnityWebRequest)로 전달한다. 이 GetContent함수를 호출하면 AssetBundle 객체를 반환한다.


AssetBundle.LoadFromFile와 같은 효율성으로 AssetBundle을 로드하기 위해 에셋 번들을 다운로드한 이후에 DownloadHandlerAssetBundle 클래스에서 assetBundle 속성을 사용할 수도 있다.


아래의 예제는 두 개의 GameObject가 포함된 에셋 번들을 로드하고 인스턴스화(instantiate)하는 방법을 보여준다.

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class UnityWebRequestExample : MonoBehaviour
{
IEnumerator InstantiateObject()
{
string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
yield return request.Send();

AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);

GameObject cube = bundle.LoadAsset<GameObject>("Cube");
GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");

Instantiate(cube);
Instantiate(sprite);
}
}

UnityWebRequest를 사용하면 개발자가 다운로드한 데이터를 보다 유연하게 처리하고 불필요한 메모리 사용을 없앨 수 있다는 이점이 있다. 이것은 UnityEngine.WWW 클래스보다 최신의 API이다.


에셋 번들에서 에셋 불러오기

에셋 번들을 성공적으로 다운로드했으므로, 원하는 에셋을 불러올 차례이다.


제네릭 코드의 일부분 :

T objectFromBundle = bundleObject.LoadAsset<T>(assetName);

여기서 T는 불러오려는 에셋의 타입이다.


에셋을 불러오는 방법을 결정하는 몇 가지 옵션이 있는데 LoadAsset, LoadAllAsset과 각각에 대응되는 비동기 방식인 LoadAssetAsync, LoadAllAssetAsync가 그것이다.


에셋 번들에서 에셋을 동기적으로 불러오는 방법은 다음과 같다.


하나의 GameObject를 불러오기 :

GameObject gameObject = loadedAssetBundle.LoadAsset.<GameObject>(assetName);

모든 에셋 불러오기 :

Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();


LoadAsset과 LoadAllAsset 메서드가 객체 타입이나 객체 배열을 반환한다면 비동기 메서드인 LoadAssetAsync와 LoadAllAssetAsync는 AssetBundleRequest를 반환한다. 불러온 에셋에 액세스하기 전에 비동기 작업이 완료될 때까지 기다려야 한다.


단일 에셋 불러오기 :

AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);

yield return request;

var loadedAsset = request.asset;

모든 에셋 불러오기 :

AssetBundleRequest request = loadedAssetBundleObject.LoadAllAssetAsync();

yield return request;

var loadedAssets = request.allAssets;

에셋이 로드된 이후에는 유니티의 오브젝트와 마찬가지로 사용할 수 있다.



에셋 번들 매니페스트 불러오기

에셋 번들 매니페스트를 불러오는 것은 애셋 번들을 종속성을 다룰 때 매우 효율적이다.


사용가능한 AssetBundleManifest 객체를 얻으려면 그 추가 에셋 번들(폴더에 있는 것과 동일한 이름의 폴더에 있는)을 로드하고 AssetBundleManifest 타입의 객체를 로드해야 한다.


매니페스트를 로드하는 작업 자체는 에셋 번들의 다른 에셋과 동일하게 수행된다 :

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

위의 예제에서 매니페스트 객체를 통해 AssetBundleManifest API 호출에 액세스할 수 있다. 여기에서 매니페스트를 사용하면 만든 에셋 번들에 대한 정보를 얻을 수 있다. 이 정보에는 종속성 데이터, 해시 데이터 및 에셋 번들의 배리언트(Variant, 변형) 데이터가 포함된다.


이전 섹션에서 에셋 번들의 종속성에 대해서 설명했듯이, 에셋 번들이 다른 에셋 번들에 종속성을 가지고 있다면 원래의 번들에서 에셋을 불러오기 전에 해당 번들을 불러와야 한다. 매니페스트 객체는 로딩 종속성을 동적으로 찾을 수 있도록 해준다. "assetBundle"이라는 이름의 에셋 번들에 대한 모든 종속성을 불러오려고 한다고 가정해보자.

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);

AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

string[] dependencies = manifest.GetAllDependencies();

foreach(string dependency in dependencies)

{

AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency);

}

이제 에셋 번들, 에셋 번들 종속성 및 에셋을 불러왔으므로 불러와진 에셋 번들 모두를 관리하는 방법에 대해 이야기해보자.



불러온 에셋 번들 관리하기 (Managing Loaded Asset Bundles)

유니티는 활성화된 씬에서 오브젝트가 제거되었을때 오브젝트를 자동으로 언로드(Unload)하지 않는다. 에셋 정리는 특정 시간에 자동적으로 이루어지며, 수동으로도 실행될 수 있다.


에셋 번들을 로드하고 언로드해야할 때를 아는 것이 중요하다. 에셋 번들을 잘못 언로드하면 메모리 상에서의 오브젝트와 텍스처 누락등의 바람직하지 않은 상황이 발생할 수 있다.


에셋 번들 관리에 있어서 이해해야할 가장 중요한 점은 AssetBundle.Unload(bool) 함수를 언제 호출해야 하는가와 함수의 매개변수에 true 혹은 false 어떤 인자를 전달해야 하는가이다. Unload는 에셋 번들을 언로드하는 비정적(non-static)함수이다. 이 API는 호출중인 에셋 번들의 헤더 정보를 언로드 한다. 이 매개변수는 이 에셋 번들로부터 인스턴스화된 모든 오브젝트를 언로드할지에 대한 여부를 나타낸다.


에셋 번들로부터 불러온 오브젝트에 대해 AssetBunble.Unload(true)를 사용했는데, 만약 이 오브젝트가 현재 활성화된 씬에서 사용중이라면, 이것은 앞서 이야기 한것처럼 텍스처 누락의 원인이 될 수 있다.


아래와 같이 머티리얼 M이 에셋 번들 AB에서 로드되었다고 가정해보자.

만약 AB.Unload(true)가 호출된다면, 활성화된 씬에서 M의 모든 인스턴스는 언로드되고 파괴된다.


대신에 AB.Unload(false)를 호출하면 M과 AB의 현재 인스턴스 연결이 끊어진다.

만약 AB.LoadAsset()을 호출하여 AB를 다시 로드한다고 하여도, 유니티는 새로 로드된 머티리얼에 기존에 존재하는 복사본 M을 연결시켜주지 않는다. 그 대신에 M의 2개의 복사본이 로드된다.

일반적으로 AssetBundle.Unload(false)를 사용하면 좋은 상황이 발생하지 않는다. 대부분의 프로젝트는 AssetBundle.Unload(true)를 사용하여 오브젝트를 메모리에 복제하지 않아야한다.


대부분의 프로젝트는 AssetBundle.Unload(true)를 사용하고 오브젝트가 중복되지 않도록 보장하는 메서드를 채택해야한다. 두 가지 일반적인 방법은 다음과 같다 :

- 응용 프로그램의 실행 시간 동안에 발생하는 레벨 사이 혹은 로딩 화면과 같이 짧은 틈 사이에 에셋 번들이 언로드되는 지점을 정의한다.

- 개별의 오브젝트에 대해 참조 카운트를 유지하고 모든 구성 오브젝트가 사용되지 않는 경우에만 에셋 번들을 언로드한다. 이 방법을 사용하면 응용 프로그램이 메모리를 복제하지 않고 개별 오브젝트를 언로드하고 다시 로드할 수 있다.


응용 프로그램에서 AssetBundle.Unload(false)를 반드시 사용해야만 한다면, 개별 오브젝트는 다음의 2가지 방법으로만 언로드해야 한다.

- 씬과 코드에서 원하지 않는 오브젝트에 대한 모든 참조를 제거해야 한다. 이 작업이 끝나면 Resources.UnloadUnusedAssets 함수를 호출한다.

- 씬을 additive 방식이 아닌 방법으로 로드한다. 그렇게 하면 현재 씬의 모든 오브젝트가 파괴되고 Resources.UnloadUnusedAssets가 자동으로 호출된다.


에셋 번들과 종속성 그리고 에셋에 대한 불러오기를 직접 관리하지 않으려고 하는 경우, 에셋 번들 관리자가 필요할 수 있다.

  1. 임대운 2019.02.09 19:30

    안녕하세요. 에셋 번들 공부 중에 궁금한게 있어서 질문 드립니다.

    LoadFromCacheOrDownload 와 UnityWebRequest는 완전히 똑같이 작동하나요?
    지금 회사 코드에 LoadFromCacheOrDownload 이 메서드를 많이 사용하는데 ios 자체 caching 문제로 httpHeader에 no-cache를 추가할려고 하는데
    LoadFromCacheOrDownload에는 어떻게 설정할지 알수가 없어서요.
    그래서 UnityWebRequest을 사용해서 no-cache를 설정할려고 합니다.

+ Recent posts