Version: 2020.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 が自動的に実行されます。

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