Version: 2021.3
言語: 日本語
アセットバンドルの依存性
アセットバンドルの圧縮

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

AssetBundle (アセットバンドル) のロードには 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 の使用方法の一例です。

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);
    }
}

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

UnityWebRequestAssetBundle

UnityWebRequestAssetBundle

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

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

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

IEnumerator InstantiateObject()
{
    string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName; 
    UnityEngine.Networking.UnityWebRequestAssetBundle request 
        = UnityEngine.Networking.UnityWebRequestAssetBundle.GetAssetBundle(uri, 0);
    yield return request.SendWebRequest();
    AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
    GameObject cube = bundle.LoadAsset<GameObject>("Cube");
    GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");
    Instantiate(cube);
    Instantiate(sprite);
}

アセットバンドルからアセットをロードする

アセットバンドルのダウンロードに成功したら、アセットのロードを開始することができます。

汎用コード スニペット

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));
}

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

ロードされたアセットバンドルの管理

ノート: Addressable Assets パッケージは、アセットバンドル、依存関係、アセットのロードを管理するための既製のシステムを提供します。Unity は、アセットバンドルを自分で管理するよりも Addressable を使うことを推奨します。

Unity Learn の ロードされたアセットバンドルの管理に関するチュートリアル も参照してください。

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

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

アセットバンドルの管理について理解すべき最大のポイントは、AssetBundle.Unload(bool) – またはAssetBundle.UnloadAsync(bool) – をいつ呼び出すか、そして関数呼び出しに true または false を渡すべきかということです。 Unload は、アセットバンドルをアンロードする静的ではない関数です。この API は、呼び出されるアセットバンドルのヘッダー情報をアンロードします。引数は、このアセットバンドルからインスタンス化されたすべてのオブジェクトもアンロードするかどうかを示します。

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

下の図のようにアセットバンドル AB からマテリアル M がロードされ、プレハブ P で使用されるとします。

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

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

後で AB が再度ロードされ AB.LoadAsset() が呼び出されても、Unity は既存の M のコピーを新しくロードされたマテリアルに再リンクしません。

プレハブ P の別のインスタンスを作成すると、既存の M のコピーは使用されず、代わりに M の 2 つのコピーがロードされます。

一般的に、AssetBundle.Unload(false) を使用しても、理想的な状況にはなりません。ほとんどのプロジェクトで、AssetBundle.Unload(true) を使用し、オブジェクトが重複しない方法を採用するべきです。一般的な方法は 2 つあります。

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

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

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

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

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

アセットバンドルの依存性
アセットバンドルの圧縮