【Unity】AssetBundleは依存しているものを先に読み込まないとバグるよって話

あるAssetBundleが別のAssetBundleに依存している場合、依存しているものを先に読み込まないとバグります。
AssetBundleの基本的な使い方になりますが、改めてこの挙動を確認してみます。

Unity2018.3.1

前提

前提として、この記事では以下のようにPrefabとそのPrefabが参照するMaterialをAssetBundleにすることを考えます。

f:id:halya_11:20190401151527p:plain

またAssetBundleのビルドやロードの手順は割愛します。
ビルドについてはAssetBundle Browserを使用しているので必要に応じて次の記事を参考にしてください。

light11.hatenadiary.com

まず依存関係のないAssetBundleを読み込んでみる

まずPrefabとMaterialを一つのAssetBundleにまとめてみます。
この場合AssetBundleは一つしかないので、もちろんAssetBundle間の依存関係はありません。

f:id:halya_11:20190401194921p:plain

この状態でAssetBundleをビルドすると、PrefabとMaterialの入ったAssetBundleが生成されます。
生成されたAssetBundleを下記のスクリプトで読み込んでみます。

var assetBundle = AssetBundle.LoadFromFile(assetBundlePath);
var prefab = assetBundle.LoadAsset<GameObject>("Assets/AssetBundle/Prefab.prefab");
Instantiate(prefab);
assetBundle.Unload(false);

結果は次の通り、正常にAssetBundleが読み込まれました。

f:id:halya_11:20190401195352p:plain

依存先をロードしてからロードを行わないとバグる

次にPrefabとMaterialに別々のAssetBundle名を付けてビルドしてみます。

f:id:halya_11:20190401201800p:plain

この状態でビルドすると二つのAssetBundleが作られます。
そしてPrefabはMaterialに依存しています。

ビルドしたフォルダ直下にある、フォルダ名と同じ名前の.manifestファイルを開くと、
AssetBundleの一覧とそれらの依存関係がわかります。
(ちなみにこの.manifestはランタイムでは使いません。)

ManifestFileVersion: 0
CRC: 1488167663
AssetBundleManifest:
  AssetBundleInfos:
    Info_0:
      Name: example/material
      Dependencies: {}
    Info_1:
      Name: example/prefab
      Dependencies:
        Dependency_0: example/material

この状態で、前節のスクリプトを使ってPrefabのAssetBundleを読み込んでみます。

f:id:halya_11:20190401201628p:plain

Prefabは読みこめたもののMaterialへの依存関係が解決されていないため、ピンクになってしまいました。

依存関係を解決してロードする

あるAssetBundleの中のAssetをロードするときには、そのAssetが依存しているAssetBundleを先に全て読み込む必要があります。
つまり今回の例では、まずMaterialのAssetBundleを読み込んで、そのあとにPrefabのAssetBundleからPrefabを読み込む必要があります。

また、AssetBundleの依存関係は、ビルドしたフォルダの直下にある、
フォルダと同名のAssetBundle(.manifestファイルではなくAssetBundleの方)をロードすることで取得できます。

// ルートにあるAssetBundleからAssetBundleManifestを読み込む
var rootBundle = AssetBundle.LoadFromFile(rootBundlePath);
var manifest = rootBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
rootBundle.Unload(false);

// 読み込み対象のAssetBundleが依存しているAssetBundleの一覧を取得してロード
var dependencies = manifest.GetAllDependencies("example/prefab");
var dependencyBundles = new List<AssetBundle>();
foreach(var dependency in dependencies)
{
    dependencyBundles.Add(AssetBundle.LoadFromFile(Path.Combine(assetBundleFolderPath, dependency)));
}

// 読み込み対象のAssetBundleをロード > インスタンス化 > アンロード
var assetBundle = AssetBundle.LoadFromFile(assetBundlePath);
var prefab = assetBundle.LoadAsset<GameObject>("Assets/AssetBundle/Prefab.prefab");
Instantiate(prefab);
assetBundle.Unload(false);

// 依存関係にあるAssetBundleをアンロード
foreach (var dependencyBundle in dependencyBundles) {
    dependencyBundle.Unload(false);
}

補足として、上記ではPrefabのAssetBundleを最後に読み込んでいますが、
依存関係が評価されるのはあくまでAssetBundle.LoadAsset()の時点です。
そのため、PrefabのAssetBundleのロードは必ずしも最後にやらなくてもOKです。

これを実行すると、

f:id:halya_11:20190401204246p:plain

AssetBundle同士の依存関係が解決されて正常にロードが完了しました。

まとめ

  • AssetBundle同士の依存関係がある場合には依存しているAssetBundleを先にロードしておく必要がある
  • 依存関係の情報はルートにあるAssetBundleに書かれている

ということがわかりました。