본문 바로가기

TOOL/Unity

Asset bundle 사용법

Unity 5 기준 Asset bundle을 load 하는 방법은 4가지의 API를 사용할 수 있다.

4가지 API의 동작은 Asset bundle이 load되는 flatform과 build될때 사용되는 압축방법(Uncompressed, LZMA, LZ4)에 따라 다르다.


작업에 사용할 4가지 API

-AssetBundle.LoadFromMemoryAsync

-AssestBundle.LoadFromFile

-WWW.LoadfromChacheOrDownload  (사용하면 안됨, 아래있는 UnityWebRequest로 사용할것 / 이유는 다른글에서 설명했음)

-UnityWebRequest, DownloadHandlerAssetbundle (Unity 5.3 or newer)





AssetBundle.LoadFromMemoryAsync


이 함수는 asset bundle data가 들어있는 byte type array를 가져온다.

원한다면 CRC(Cyclic Redundancy Check)값을 전달할 수 있다.

Asset bundle이 LZMA방식으로 압축되어 있다면 bundle을 loading하는 동안 asset bundle을 압축 해제해야 한다.

LZ4방식으로 압축된 asset bundle은 압축되어 있는 상태에서도 load가 가능하다.


P.S) CRC값 : 네트워크등을 통하여 데이터를 전송할 떄 전송된 데이터에 오류가 있는지를 확인하기 위한 체크값




    
using UnityEngine;
using System.IO;

public class AssetBundleLoadExample : MonoBehaviour
{
    IEnumerator LoadFromMemoryAsync(string path)
    {
        AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path)); //Asset bundle load
        yield return createRequest;

        AssetBundle bundle = createRequest.assetBundle;

        var prefab = bundle.LoadAsset("MyObject"); //asset bundle에서 사용하고 싶은 object find
        Instantiate(prefab); //prefab instance화

    }
}



AssetBundle.LoadFromFile


Local 저장소에서 압축되지 않은 asset bundle을 load할때 매우 효율적이다. 

LoadFromFile은 압축되지 않았거나 Chunk base인 LZ4으로 압축된 bundle의 경우 disk로부터 직접 asset bundle을 load해온다. 

이 매소드로 LZMA로 압축된 asset bundle을 load하면 먼저 memory에 올리기 전에 압축을 해제한다.

(즉 LZMA로 압축된건 압축을 해제한 후에 사용하기때문에 비효율적)




    
using UnityEngine;
using System.IO;

public class AssetBundleLoadExample : MonoBehaviour
{
    private void Start()
    {
        var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle")); 
        //Asset bundle load

        if(myLoadedAssetBundle == null)
        {
            Debug.Log("Failed to load Asset bundle");
            return;
        }
        var prefab = myLoadedAssetBundle.LoadAsset("MyObject"); //asset bundle에서 사용하고 싶은 object find
        Instantiate(prefab); //prefab instance화
    }
}


P.S) Unity 5.3이하의 안드로이드 기기에서는 Streaming Asset Path에서 Assetbundle을 load하려고 할때 이 API가 실패한다.

이는 해당 path에 압축된 .jar file 내부에 존재하기 때문이다. 

Unity 5.4 이상에서는 Streaming Asset과 함께 해당 API를 사용할 수 있다.





WWW.LoadfromChacheOrDownload  (UnityWebRequest를 사용해라)


원격 위치에서 asset bundle을 load하면 asset bundle이 자동으로 cache된다. 만약 asset bundle이 압축된 상태라면 

작업 thread에서 asset bundle의 압축을 해제하여 cache에 등록한다.

압축이 해제되고 cache된 asset bundle은 AssetBundle.LoadFromFile과 동일하게 load된다.


Asset bundle의 byte를 WWW객체에 caching하는 memory overhead가 발생하기 때문에 WWW.LoadFromCachOrDownload를 사용하는 모든

개발자는 AssetBundle의 크기가 몇메가 수준을 넘지 않게 유지해야하고 또한 Mobile같이 memory가 제한된 platform에서 작업하는 개발자는

메모리 스파이크(?)를 피하기 위해 코드가 한번에 하나의 asset bundle만 download하도록 하는 것이 좋다.


만약 cache folder에 추가 file을 caching할 여유 공간이 없다면 LoadFromCacheOrDownload는 새 asset bundle을 저장할 충분한 공간이

확보될 떄 까지 cache에서 사용된 시점이 가장 오래된 asset bundle을 삭제할 것이다.

더이상 공간을 만들 수 없는경우(hard disk가 가득 찼거나 cache의 모든 file이 현재 사용중인경우) LoadFromCacheOrDownload는

chaching을 하지 않고 file을 memory에 streaming할 것이다.


WWW.LoadFromCacheOrDownload(m_downloadUrl, m_hash); 이런식으로 사용되는데

여기서 m_hash는 bundleManifest.GetAssetBundleHash(bundleName)의 리턴값으로 넣는다.

그래서 해당 hash값으로 이전버전과 이후버전의 해쉬값을 비교해서 다르면 새로다운받고

같으면 캐쉬에 있는걸 사용한다는거 같다.




UnityWebRequest


UnityWebRequest는 asset bundle을 처리하기 위한 특정 API를 가지고 있다.

먼저 UnityWebRequest.GetAssetBundle을 사용하여 Web Request를 생성하면 Request를 return한 후 요청 객체를 

DownloadHandlerAssetBundle.GetContent(UnityWebRequest)로 전달한다.

이 GetContent함수를 호출하면 AssetBundle 객체를 return 한다.


AssetBundle.LoadFromFile과 같은 효율성으로 AssetBundle을 load하기 위해 asset bundle을 load하기위해 assset bundle을 download한 이후

DownloadHandlerAssetBundle class에서 assetbundle속성을 사용할 수도 있다.




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

public class UnityWebRequestExample : MonoBehaviour
{
    IEnumerator InstantiateObject()
    {
        string uri = "file:///" + Application.dataPath + "/Assetbundles/" + assetBundleName; 
        //load할 asset bundle의 위치를 설정

        UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0); 
        //asset bundle load

        yield return request.Send(); //server 에 request

        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request); //request 한 asset bundle을 받아옴

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

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



UnityWebRequest를 사용하면 개발자가 download한 data를 보다 유연하게 처리하고 불필요한 memory사용을 없앨 수 있다는 장점이 있다.




Asset bundle에서 Asset Load하기



    
T objectFromBundle = bundleObject.LoadAsset(assetName);



Asset을 load하는 방법을 결정하는 몇가지 Option이 있는데 

동기방식인 LoadAsset, LoadAllAsset과 

비동기 방식인 LoadAssetAsync, LoadAllAssetAsync가 있다.


Asset bundle에서 asset을 동기적으로 불러오는 방법은 다음과 같다.


    
GameObject gameObject = loadedAssetBundle.LoadAsset.(assetName); // 하나의 GameObject를 load
Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets(); //모든 Asset load



Asset이 load된 이후에닌 unity object와 마찬가지로 사용할 수 있다.




Asset bundle Manifest load하기



Asset bundle Manifest를 load하는것은 asset bundle의 dependency(종속성)를 다룰 때 매우 효율적이다.

Usable AssetBundle Manifest객체를 얻으려면 그 추가 asset bundle(folder에 있는 것과 동일한 이름의 folder에 있는)을 load하고

AssetBundle Manifest type의 객체를 load해야한다.


Manifest를 load하는 작업 자체는 asset bundle의 다른 asset과 동일하게 수행된다.


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


위의 예제에서 manifest 객체를 통해 AssetBundleManifest API호출에 Access할 수 있다. 여기서 manifest를 사용하면

만든 asset bundle에 대한 information을 얻을 수 있다.

이 information에는 dependency data, hash data, asset bundle의 variant(변형) data가 포함된다.


Asset bundle이 다른 Asset bundle에 dependency를 가지고 있다면 원래의 bundle에서 asset을 load하기 전에 해당 bundle을

load해야 한다.

manifest 객체는 loading dependency를 동적으로 찾을 수 있도록 해준다.

예를들어 "assetBundle"이라는 이름의 asset bundle에 대한 모든 dependency를 load한다고 가정해보자



    
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset("AssetBundleManifest");
string[] dependencies = manifest.GetAllDependencies();
foreach(string dependency in dependencies)
{
    AssetBundle.LoadFromFile(Path.Combine(assetbundlePath, dependency));
}


이제 Asset bundle, Asset bundle dependency, asset을 load했으므로 load한 asset bundle을 모두 관리하는 방법에 대해 알아보겠다.





불러온 Asset Bundle 관리하기


Unity는 활성화 된 Scene에서 object가 제거되었을때 object를 자동으로 Unload하지 않는다.

Asset 정리는 특정 시간에 자동적으로 이루어지며 수동으로도 실행될 수도 있다.(GC말하는듯)


Asset bundle을 load하고 unload 해야할 때를 아는것이 중요하다.

Asset bundle을 잘못 unload하면 memory상에서의 object와 texture 누락 등 의도하지 않는 상황이 발생할 수 있다.


Asset bundle 관리에 있어서 이해해야할 가장 중요한 점은 AssetBundle.Unload(bool) 함수를 언제 호출해야 하는가와

함수의 매개변수에 true 혹은 false 어떤 인자를 전달해야 하는가 이다.

Unload함수는 asset bundle을 unload하는 non-static(비정적) 함수이다.

이 API는 호출중인 asset bundle의 header information을 unload한다.

이 매개변수는 이 asset bundle로 부터 instance화 된 모든 오브젝트를 unload할지에 대한 여부를 나타낸다.


Asset bundle로부터 load한 object에 대해 AssetBundle.Unload(true)를 사용했는데, 만약 이 object가 현재 활성화된 Scene에서 사용중이라면

이것은 앞서 이야기 한것처럼 texture 누락의 원인이 될 수 있다.




Material M이 Asset bundle AB에서 Load되었다고 가정하자

만약 AB.Unload(true)가 호출된다면, 활성화된 씬에서 M의 모든 instance는 unload되고 파괴된다.

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


만약 AB.LoadAsset()을 호출하여 AB를 다시 Load한다고 하여도 Unity는 새로 load된 Material에 기존에 존재하는 복사본 M을 연결시켜주지 않는다.

그 대신 M의 2개의 복사본이 load가 된다.



일반적으로 AssetBundle.Unload(false)를 사용하면 좋은 상황이 발생하지 않는다.

대부분의 project는 AssetBundle.Unload(true)를 사용하여 object를 memory에 복제하지 않아야 한다.


대부분의 project는 AssetBundle.Unload(true)를 사용하고 Object가 중복되지 않도록 보장하는 매소드를 채택해야한다.

두가지 일반적인 방법은 다음과 같다.


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


- Scene과 Code에서 원하지 않는 object에 대한 모든 reference를 제거해야한다.

 이 작업이 끝나면 Resources.UnloadUnusedAssets함수를 호출한다.


- Scene을 additive방식이 아닌 방법으로 load한다.

  그렇게 하면 현재 씬의 모든 object가 파괴되고 Resources.UnloadUnusedAssets가 자동으로 호출된다.



Asset bundle과 dependency, asset load를 직접 관리하지 않으려고 하는 경우 

Asset bundle manager가 필요하다.








참조

https://wergia.tistory.com/32


Asset bundle dependency

https://wergia.tistory.com/31?category=737654

'TOOL > Unity' 카테고리의 다른 글

Mono / Mono Behaviour  (0) 2019.03.05
IL2CPP / GCC / C# C++ Assembly 변환과정  (0) 2019.03.05
Asset Bundle Manager  (0) 2019.02.27
대표적인 Memory leak 발생 유형  (0) 2019.02.26
Asset Bundle  (0) 2019.02.26