アセットバンドルの依存性
AssetBundle Manager

アセットバンドルを使いこなす

アセットバンドルのロードには 4種類の API が使用できる。それらの挙動は、バンドルがロードされるプラットフォームと、アセットバンドルがビルドされたときに使用された圧縮方法 (非圧縮、LZMA、LZ4) によって異なります。

以下の 4 つの API があります。

AssetBundle.LoadFromMemoryAsync

AssetBundle.LoadFromMemoryAsync

この関数は、該当のアセットバンドルのデータを含むバイトの配列を 1 つ取得します。必要であれば CRC 値を 1 つ渡すことも可能です。バンドルが LZMA 圧縮されていれば、そのアセットバンドルは読み込み中に解凍されます。 LZ4 圧縮されたバンドルは圧縮された状態のまま読み込まれます。

以下は、このメソッドの使用方法の一例です。

using UnityEngine;
using System.Collections;
using System.IO;

public class Example : 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.ReadAllBytes(path) は、バイト配列のどんな取得方法にでも置き換えることができます。

AssetBundle.LoadFromFile

AssetBundle.LoadFromFile

この API は、非圧縮バンドルをローカルストレージから読み込む際に使用すると非常に効率的です。 LoadFromFile は、バンドルが非圧縮の場合やチャンクベースの圧縮方式(LZ4)で圧縮されている場合に、ディスクから直接バンドルを読み込みます。完全圧縮(LZMA)バンドルをこのメソッドで読み込む場合は、バンドルが解凍されてからメモリに読み込まれます。

以下は LoadFromFile の使用方法の一例です。

public class LoadFromFileExample extends MonoBehaviour {
    function 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);
    }
}

(注記) Android デバイスで、 バージョン 5.3 またはそれ以前の Unity をお使いの場合、この API で Streaming Assets パスからアセットバンドルを呼び出そうとすると失敗します。これは、このパスのコンテンツが圧縮 .jar ファイル内に存在しているためです。 Unity 5.4 以降では、 Streaming Assets にも API コールを問題なく使用できます。

WWW.LoadFromCacheOrDownload

WWW.LoadFromCacheOrDownload

廃止予定です。(UnityWebRequest をご使用ください。)

この 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", 5);
        yield return www;
        if(!string.IsNullOrEmpty(www.error))
        {
            Debug.Log(www.error);
            yield return;
        }
        var myLoadedAssetBundle = www.assetBundle;

        var asset = myLoadedAssetBundle.mainAsset;
    }
}

アセットバンドルのバイトを WWW オブジェクト内にキャッシュすることでメモリオーバーヘッドが発生するため、 WWW.LoadFromCacheOrDownload を使用する場合は必ず、アセットバンドルのサイズを小さく(最大で数メガバイト)抑えることが推奨されます。また、モバイル端末などメモリ容量が限られるプラットフォームの場合は、メモリスパイクの発生を避けるために、一度にダウンロードされるアセットバンドルが 1 つだけになるようにコードを書くことも推奨されます。

キャッシュフォルダーにそれ以上のファイルをキャッシュするための領域がない場合は、 LoadFromCacheOrDownload によって、最も長期間使用されていないアセットバンドルをキャッシュから反復的に削除し、新しいアセットバンドルを格納するための領域を空けることができます。(例えばハードディスクが一杯になっている場合や、キャッシュ内の全ファイルが使用中である場合などで)空き領域を作ることができない場合は、 LoadFromCacheOrDownload() を使用するとキャッシングを行わずにファイルをメモリ内にストリームすることができます。

LoadFromCacheOrDownload を強制実行するには、バージョン パラメーター(2 つ目のパラメーター)が変更される必要があります。アセットバンドルは、関数に渡されたバージョンがその時点でキャッシュされているアセットバンドルのバージョンと一致した場合にのみ、キャッシュから読み込まれます。

UnityWebRequest

UnityWebRequest

UnityWebRequest には、アセットバンドル専用の API コールがあります。この使用を開始するには、 UnityWebRequest.GetAssetBundle を使用してウェブ リクエストを作成する必要があります。リクエストを返した後に、そのリクエスト オブジェクトを DownloadHandlerAssetBundle.GetContent(UnityWebRequest) 内に渡します。この GetContent コールがアセットバンドル オブジェクトを返します。

また、バンドルの読み込み後に DownloadHandlerAssetBundle クラスの assetBundle プロパティを使用することで、 AssetBundle.LoadFromFile の効率性をもってアセットバンドルを読み込むことができます。

以下の例では、2 つのゲームオブジェクトを含むアセットバンドルを読み込み、それらをインスタンス化しています。この処理は StartCoroutine(InstantiateObject()); を呼び出すだけで開始できます。

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 は、読み込むアセットのタイプです。

アセットの読み込み方法はいくつかあります。 LoadAssetLoadAllAssets および、それらの Async バージョンである LoadAssetAsyncLoadAllAssetsAsync` です。

アセットバンドルから同期的にアセットを読み込む方法は以下の通りです。

単一のゲームオブジェクトを読み込むには以下を使用します。

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

全てのアセットを読み込むには以下を使用します。

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

上述のメソッドは読み込むオブジェクトのタイプかオブジェクトの配列を返しますが、非同期メソッドは AssetBundleRequest を返します。アセットへのアクセスは、この処理が完了するのを待ってから行う必要があります。アセットは以下を使用して読み込めます。

AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);
yield return request;
var loadedAsset = request.asset;

または

AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request;
var loadedAssets = request.allAssets;

アセットの読み込みが終わったら、準備完了です。読み込まれたオブジェクトを、その他のオブジェクトと同じように Unity 上で使用することができます。

AssetBundle Manifest を読み込む

アセットバンドル マニフェストを読み込むと非常に役立ち得ます。特にアセットバンドルの依存関係を扱う場合には有益です。

使用可能な AssetBundleManifest オブジェクトを取得するには、追加のアセットバンドル(その格納されているフォルダーと同じ名前のアセットバンドル)を読み込み、 AssetBundleManifest タイプのオブジェクトを 1 つ、そこから読み込む必要があります。

マニフェスト自体の読み込みは、その他通常のアセットをアセットバンドルから読み込む場合と全く同じように行われます。

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

これで、上記の例にあるマニフェストオブジェクトによって AssetBundleManifest API コールが使用できるようになりました。ここからは、ビルドしたアセットバンドルの情報を、マニフェストを使用して取得することができます。この情報には、該当のアセットバンドルの依存関係データ、ハッシュデータ、バリアントデータが含まれます。

先の「アセットバンドルの依存」の項で、「あるバンドルが別のバンドルに依存している場合は、それらのバンドルは、元のバンドル内のアセットを読み込む前に読み込む必要がある」ことを述べましたが、マニフェストオブジェクトは、読み込み中の依存の動的な特定を可能にします。例えば、 “assetBundle” という名前のアセットバンドルの依存を全て読み込みたいと仮定しましょう。

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
string[] dependencies = manifest.GetAllDependencies("assetBundle"); //依存するものを取得するバンドル名を渡します
foreach(string dependency in dependencies)
{
    AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency));
}

これでアセットバンドル、アセットバンドルの依存、アセットの読み込みが完了しました。次は、読み込んだアセットバンドルの管理についての解説です。

読み込み済みアセットバンドルの管理

Unity Learn の 読み込み済みアセットバンドルの管理に関するチュートリアル もご覧ください。

Unity は、アクティブなシーンから削除されたオブジェクトを自動的にアンロードしません。アセットのクリーンアップは特定の時点でトリガーされますが、手動でトリガーすることも可能です。

アセットバンドルの読み込みとアンロードを行うタイミングを把握することが重要です。アセットバンドルの不適切なアンロードは、メモリ内におけるオブジェクトの重複やテクスチャの欠落など、望ましくない結果を引き起こす可能性があります。

アセットバンドルの管理に当たっては、

AssetBundle.Unload(bool); を呼び出すタイミングを理解することと、この関数コールに True と False のどちらを渡すのかを理解することが重要です。 Unload は、アセットバンドルをアンロードする非静的関数です。この API は、呼び出されているアセットバンドルのヘッダー情報をアンロードします。引数は、そのアセットバンドルからインスタンス化された全てのオブジェクトもアンロードするかどうかを示します。

AssetBundle.Unload(true) は、アセットバンドルから読み込んだすべてのオブジェクト (とその依存) をアンロードします。これには、コピーしたゲームオブジェクト (インスタンス化したゲームオブジェクトなど) は含まれません。それらはもはや、アセットバンドルには属さないからです。AssetBundle.Unload(true) を呼んだとき、アセットバンドルから読み込まれ、かつ、まだアセットバンドルに属しているテクスチャは、シーンのゲームオブジェクトから消滅します。Unity はこれを欠落したテクスチャとして扱います。

マテリアル M がアセットバンドル AB から読み込まれると仮定します(以下画像参照)。

AB.Unload(true) が呼び出されると、アクティブシーン内の M のインスタンスも全てアンロードされ、破棄されます。

一方、 AB.Unload(false) を呼び出した場合、現在の M および AB のインスタンスのチェーンが破壊されます。

後に AB が再度読み込まれて AB.LoadAsset() が呼び出された場合、 Unity は、 M の既存のコピーを新しく読み込まれたマテリアルに再リンクすることはありません。その代わりに M が 2 つ読み込まれることになります。

基本的に、 AssetBundle.Unload(false) の使用は望ましい結果をもたらしません。ほとんどのプロジェクトにおいては、メモリ内におけるオブジェクトの重複を避けるため、 AssetBundle.Unload(true) の使用が推奨されます。

ほとんどのプロジェクトでは、 AssetBundle.Unload(true) を使用して、オブジェクトの重複が起こらないメソッドを採用することが推奨されます。以下は一般的な 2 つのメソッドです。

  • アプリケーションのライフタイム内で、一時的なアセットバンドルをアンロードする丁度良いタイミングを設定する。例えばステージとステージの間やロード画面など。

  • 個々のオブジェクトの参照カウントを維持し、アセットバンドルに含まれるオブジェクトがひとつも使用中でない場合にのみ、アセットバンドルをアンロードする。この方法だと、メモリの重複を起こすことなく、アプリケーションのオブジェクトを個々にアンロードおよび再読み込みできます。

どうしても AssetBundle.Unload(false) の使用が必要なアプリケーションの場合、個々のオブジェクトをアンロードする方法は次の 2 つのみになります。

  • 不要なオブジェクトへの参照を、シーン内とコード内の両方で削除し、その後 Resources.UnloadUnusedAssets を呼び出す。

  • 非加算的にシーンを読み込む。これにより、現在のシーン内の全てのオブジェクトが破棄されて Resources.UnloadUnusedAssets が自動的に実行されます。

アセットバンドル、依存、アセットの管理を自分で行いたくない場合は、 AssetBundle Manager (アセットバンドル マネージャー)を使用する必要があるかもしれません。


アセットバンドルの依存性
AssetBundle Manager