WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

Unity 5 アセットバンドルTips (ビルド編)

こんにちは、エンジニアの成田です。

今回はUnity 5のアセットバンドルを業務で扱った時に気づいたいくつかのTipsを書くことにしました。

1.ビルドマップを使用したアセットバンドルビルド

まず基本的なアセットバンドルのビルド手順について簡単に説明します。

Unity 5ではアセットバンドルのビルドを行う前に「各アセットバンドルにどのアセットを含めるか」という指定をインスペクタで行えるようになりました。
0
設定が終わったらビルドを行いますが、リファレンスに書いてある通り、いざビルドする時には既存のようにエディタスクリプトを書かないといけません。
リファレンス - 5.x における BuildingAssetBundle

正直、そこも最初からエディタでやれるようにしておいてくれても……(^_^;)と思いますがまあそこは良しとしましょう。
で、エディタスクリプトには次のように記述します。

BuildPipeline.BuildAssetBundles ("AssetBundles");  

これで、インスペクタ上で設定したアセットバンドル設定に従ってアセットバンドルがプロジェクトディレクトリの下のAssetBundlesディレクトリに作成されます。
これが基本となるビルド方法です。

さて、リファレンスでBuildPipeline.BuildAssetBundlesを見てみるともう一つ異なるシグネチャのメソッドが定義されていることに気づきます。

public static AssetBundleManifest BuildAssetBundles(  
    string outputPath,  
    BuildAssetBundleOptions assetBundleOptions = BuildAssetBundleOptions.None,  
    BuildTarget targetPlatform = BuildTarget.WebPlayer);  
  
// ↓こちら  
public static AssetBundleManifest BuildAssetBundles(  
    string outputPath,  
    AssetBundleBuild[] builds,  
    BuildAssetBundleOptions assetBundleOptions = BuildAssetBundleOptions.None,  
    BuildTarget targetPlatform = BuildTarget.WebPlayer);  

AssetBundleBuildの配列が指定できるバージョンです。AssetBundleBuildクラスはリファレンスによるとアセットバンドルビルドマップエントリと書いてあります。つまりどういうことかと言えば、コードからでも各アセットバンドルに含めるアセットを指定できるわけです。
使い方はこんな感じ。

AssetBundleBuild[] buildMap = new AssetBundleBuild[2];  // アセットバンドルの数  
  
buildMap[0].assetBundleName = "enemybundle";  // 1つ目のアセットバンドル名は"enemybundle"  
String[] enemyAssets = new String[2];  
enemyAssets[0] = "enemy1";  
enemyAssets[1] = "enemy2";  
buildMap[0].assetNames = enemyAssets;  // "enemybundle"に含めるアセットのリスト  
  
buildMap[1].assetBundleName = "herobundle";  // 2つ目のアセットバンドル名は"herobundle"  
String[] heroAssets = new String[1];  
heroAssets[0] = "hero";  
buildMap[1].assetNames = heroAssets;  // "herobundle"に含めるアセットのリスト  
  
BuildPipeline.BuildAssetBundles("AssetBundles", buildMap);  

ちなみにビルドマップによるビルドを行う時には、インスペクタのアセットバンドル設定は何の影響も及ぼしません。完全に無視されます。

このビルドマップのビルドが何に役に立つのかですが、ここに書いてあるようなコンテンツ保護のために二重にアセットバンドルをビルドする時などには、1度目のビルドで吐き出した一時アセットバンドルファイルをコード上で指定することで多重ビルドをまとめて自動化することができます。

ビルドマップバージョンで非ビルドマップバージョンを再現すると次のようになります。

public class CreateAssetBundles  
{  
    [MenuItem ("Assets/Build AssetBundles")]  
    static void BuildAllAssetBundles ()  
    {  
        BuildPipeline.BuildAssetBundles ("AssetBundles");  
    }  
  
    [MenuItem ("Assets/Build AssetBundles2")]  
    static void BuildAllAssetBundles2 ()  
    {  
        string[] assetBundleNames = AssetDatabase.GetAllAssetBundleNames ();  
        AssetBundleBuild[] builds = new AssetBundleBuild[assetBundleNames.Length];  
        for (int i = 0; i < builds.Length; i++) {  
            builds[i].assetBundleName = assetBundleNames[i];  
            builds[i].assetNames = AssetDatabase.GetAssetPathsFromAssetBundle (assetBundleNames [i]);  
        }  
        BuildPipeline.BuildAssetBundles ("AssetBundles2", builds);  
    }  
}  

上の BuildAllAssetBundles() と BuildAllAssetBundles2() は出力が等しくなります。

2.フォルダにアセットバンドル設定を行う

上でも述べたように、インスペクタからアセットバンドル設定を行う時、通常は各アセットに対してそれぞれ設定を行います。
0

ですが、実はフォルダに対してもアセットバンドル設定を行うことは可能です。
1

フォルダに対してアセットバンドル設定を行うと、そのフォルダ以下にある全アセットに対して同じ設定が適用されます。上の画像の例ではtextフォルダ配下の全アセットがdataアセットバンドルに含まれます。こうするとアセットがたくさんある時にまとめて設定でき便利です。

ただいくつか欠点もあります。
一つは、フォルダにアセットバンドル設定を行ってもフォルダ配下の各アセットのインスペクタには反映されないこと。
プロジェクトの階層が深い場合、インスペクタ上ではアセットバンドル設定が行われていなくても、親フォルダに設定が行われていたりすると混乱するでしょう。

さらに次のような階層の時を考えてみてください。

[Assets]───[Resources]───[main]─┬─[sub]───asset_b  
                                     └─asset_a  

例えばmainフォルダにmainアセットバンドルを設定して、subフォルダにsubアセットバンドルを設定すると各アセットはどれに含まれることになるでしょうか。
答えはasset_aはmainアセットバンドルに含まれ、asset_bはsubアセットバンドルにのみ含まれます。asset_aやasset_bのインスペクタを見てもアセットバンドルは未設定のままなので、ビルド結果が大変分かりづらいことになります。

とはいえ、これはインスペクタが対応すれば良い話なので将来的に改善されるのかもしれません。

もう一つは、コードからアセットバンドルに含まれるアセットを列挙することが難しくなることです。
UnityEditor.AssetDatabaseクラスを利用するとアセットバンドルに設定されているアセットのパスを取得することができます。
例えば、

string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundle ("enemybundle");  
foreach (string path in assetPaths) {  
   Debug.Log(path);  
}  
  
// 出力  
// Assets/Resources/enemy1.prefab  
// Assets/Resources/enemy2.prefab  

という拍子です。
ですが、フォルダにアセットバンドル設定を設定していると、取得できる値も

Assets/Resources/main  

のようにフォルダを指すようになります。アセットバンドルに含まれるアセットを列挙するためには、フォルダ配下のファイルをサブディレクトリも含めて検索すれば大丈夫のように思いますが、上で挙げた例を思い出してください。

[Assets]───[Resources]───[main]─┬─[sub]───asset_b  
                                     └─asset_a  

こういう階層の場合には、sub配下になっているasset_bはmainアセットには含まれないため、単純なファイル列挙では余分に列挙してしまいます。

このような欠点があるため、フォルダへのアセットバンドル設定は用途などを考慮して行った方が良いかもしれません。


次回はアセットバンドルを利用する側のTipsを書く予定です。