View on GitHub

adventure-cube

Asset Bundle とは?

Unity内部のAsset(データ)をパッキングしてDL可能にすることで、
ゲーム中のアセットを追加したり、差し替えたりすることが可能になる仕組みです。

そもそも必要になる理由について

なぜ必要になるのでしょう?
主に、2つの理由があります。

Resourcesの限界問題

ゲーム中動的に呼び出したいPrefabやリソースなどは、Resourcesにおいていくと習ったはずです。
でもリリース時にこのままだと問題になってきます。
この項では、「Resourcesフォルダがそもそもビルドされた後にどうなるのか? 」の解説がまず必要になってきます。

そもそも、Resources内に置かれたファイルは、そもそも以下のように処理されています。

前者は問題にならないケースもあるので、別の項で説明していきます。
後者はそもそも論として結構問題で、アプリ起動時にResourcesのファイルも一緒に展開するので、起動時間やメモリ使用量が大きくなって、起動時間が1分とかになり、最悪審査に通らないことが考えられます。
ファイルサイズがそんなに膨らまないゲーム(Resourcesで読んだファイルでゲームが完結するもの)であれば問題はないと思いますが、多くのゲームはバリエーションやレベルデザインのためにリソースを潤沢に用意すると思われます。
そのため配布型のコンテンツでは、この問題が存在する限りAssetBundleを使用する必要が出てきます。

ゲーム更新時の都合的な問題

そもそも作りきりで一切アップデートをかけない場合は、全部ビルトインでもいいと思います。
(Resourcesの読み込み問題がなかったと仮定しての話になりますが)
ただ悲しいかな、多くのゲームは実情としてはそうはならないと思うのです。

ゲームの更新は、主に以下のタイミングで行われます。

バグfixは、当然リソースやデータのバグfixも対象になります。
プログラムの更新は、書き替えたらビルドしてexe書き換える必要があります。
これは、たとえばバグfixで1行変えただけでも、プログラム(アプリ容量)が50MBであれば、50MBの更新が必要になります。
ここはちょっとしょうがないコストだと思います。

しかし、リソースが全部ビルトインだったとした場合、同様の現象が発生します。
バトル中のパラメータを1つ更新するだけで、アプリを再度おとしなおす必要が発生するということです。
または、プログラムのみの更新だったとしても、アプリケーション内にリソースが内包されている場合は、同様に落としなおしが必要になります。
これ、ちょっと違うんじゃないの、と思いますよね。変更のないデータは本来そのまま持きたいですよね。

これはアプリストアのゲームでも、コンシューマゲームのパッチでも、同じです。

よって、そういう部分のデータをAssetBundleでうまい事区分けして、更新対応を必要最低限のダウンロードで済ませるようにする、という手法のために、AssetBundleを使用する方法がとられます。

ワークフローの変化

そもそも開発時のワークフローは極力変えたくないですよね。

AssetBundleは、パッキングするのでビルドが必要になります。
これはリソースの圧縮作業も含むので、地味に時間がかかります。
リソースが多くなるほど時間がかかるので、ビルドのたびに更新してると…うーん…みたいな感じですよね。
そもそもローカルで確認するときにも、毎回更新していたらやってらんないです。

なので、だいたいの開発現場では
-Resourcesで動かすモード(開発用) -AssetBundleで動かすモード(テスト用)

を作ったうえで、
-AssetBundleのビルド(必要になったときに) -AssetBundleはすでに作ったものを参照するインクリメンタルなビルド(毎日更新/頻度高) -AssetBundleのビルドも含んだフルリビルド(定期更新/提出前確認)

をやるんじゃないかと思います。

プログラマがResources以下を全く触らないというケースはないはずです。
デザイナはそもそもビルドで作業とまるの嫌だと思うので、開発環境をAssetBundleで動かすのは絶対にないと思います。
なので、多くの場合では、EditorはResources参照で、ビルドはAssetBundle参照で動かす、といった環境が想像できるでしょう。

AssetBundleのビルド、設定方法

設定方法は以下になります。公式の手順と同じです。

AssetBundle (アセットバンドル) へのアセットの割り当ては、以下の手順で行います。

1. Project ウィンドウから、バンドルに割り当てたいアセットを選択します。
2. Inspector ウィンドウで該当オブジェクトを確認してください。
3. Inspector ウィンドウの下部に、アセットバンドルとバリアントを割り当てるセクションがあります。左側のドロップダウンでアセットバンドルを割り当て、右側のドロップダウンでバリアントを割り当てます。
4. 左側のドロップダウンで None をクリックすると、現在登録されているアセットバンドル名が表示されます。
5. New をクリックして新しいアセットバンドルを作成します。
6. 任意のアセットバンドル名を入力してください。アセットバンドル名によって、フォルダー構造を表現できます。サブフォルダーを加えるには、フォルダー名を / で区切ります。例えば、environment/forest というアセットバンドル名は、environment というサブフォルダー配下に forest という名前のバンドルを作成します。
7. アセットバンドル名の選択または作成後、バリアント名の割り当てや作成を行いたい場合は、右側のドロップダウンメニューから同じ手順で行うことができます。バリアント名はアセットバンドルのビルドには必須ではありません。

※インスペクタの下のウインドウは、サイズ変更で引き延ばさないと出てこないかもしれません。

ビルドは、ワークフローに書かれてあったビルドスクリプトをコピペでできます。
以下のようにプラットフォームは引数で拾うようにすれば、コマンドラインやCIツールからもいい感じに呼べます。

public class BuildCommand
{
    [MenuItem("Assets/Build AssetBundles")]
    public static void BuildAllAssetBundles()
    {
        //プラットフォーム、オプション
        BuildTarget platform = BuildTarget.StandaloneWindows;
        var outPath = "Assets/AssetBundle";

        // Jenkins(コマンドライン)の引数をパース
        var args = System.Environment.GetCommandLineArgs();
        for (int i = 0; i < args.Length; i++)
        {
            switch (args[i])
            {
                case "-outPath":
                    outPath = args[i + 1];
                    break;
                case "-platform":
                    switch (args[i + 1])
                    {
                        case "Android":
                            platform = BuildTarget.Android;
                            break;

                        case "Windows":
                            platform = BuildTarget.StandaloneWindows;
                            break;

                        case "Switch":
                            platform = BuildTarget.Switch;
                            break;
                    }
                    break;
                default:
                    break;
            }
        }

        if (!Directory.Exists(outPath))
        {
            Directory.CreateDirectory(outPath);
        }
        BuildPipeline.BuildAssetBundles(outPath,
                                        BuildAssetBundleOptions.None,
                                        platform);
    }
}

ちなみに、個々のアセットバンドルを、個別にビルドスクリプトを書いてビルドすることもできるようです。BuildAssetBundlesの第2引数にファイルリストを渡します。
単一のAssetBundleの追加や更新をしたいときに便利そうです。
今回はまあいいかって感じで飛ばします。
依存関係などを勝手に探して解決して自動で作るという使い方も考えられそうですが、そもそも作る時間かかるし、リソースの管理ができない(不適切なリソースが作られる)意味で現実的ではないと思います。
手間でも設定は手動でやって、チェッカ(AssetBundle登録されていないファイルを探す/依存関係チェック)を自動化するなどした方がいいでしょう。

Asset Bundle Manager

おそらく、AssetBundleを自前処理する場合はつくる事になります。
後述するAddressablesはこれを代行します。

主にやるべき内容は3つです

1. ResourcesとAssetBundleの読み替え

上のワークフローで示した、リソース置き場を読み替える機能です。

を用意する必要があります。
AssetBundleから読み出すためには、どのAssetBundleにリソースが入っているかを知っておかなければなりません。
なので、アセット内包リストなど、なんらかの情報を事前に持っておく必要があります。

2. 依存関係の解決(運用でカバーもナシではないが…)

アセットバンドルを読み込む際に、順番や内容に関してを保証する機能です。
特にテクスチャはプラットフォームごとに出し分けることが多いので、依存解決が必要になります。そーゆーのをまとめて一つにパッケージしてもいいと思いますが、共通テクスチャなどがある場合に面倒なので、処理を楽にするためにテクスチャは分けるケースが多いかもしれません。
例) テクスチャ(+マテリアル)→モデルの順番に読んでいく、など。

しくった場合、ピンク色になります。

3. キャッシュのインテリジェントな管理

AssetBundleは、2種の状態を持ちます。

前者は意味ねーじゃんと思うと思いますが、実際にアクセスをするとき初めて作るので、まあこのような挙動になります。
ちなみにメタ情報を読んでおかないと、アセット生成は失敗します。
めんどくさいですね!!
Switch等で開発をする際、メモリ状況を鑑みて、多くの場合はアセットは必要な時に読み出し、必要なくなったらアンロードをするかと思います。
そうでなくてもSwitchやディスクアクセスを想定するコンシューマでは、I/Oスパイクを発生させないためにアセットをメモリ上に展開したままにすることが多いので、実質1キャラに対してメモリを2倍食うと考えてよいです。

参考ソース

AssetBundleManager.cs CacheTemplate.cs ResourceCache.cs

AssetBundleManagerの実装はあるので、参考にしてみてください。
まだ作ってはいませんが、ローカルで動かすアセバン版ではなく、リリース版ではビルトインとダウンロードの切り分けを管理する設定ファイル、アセバンのバージョンの管理をする設定データを用意します。

便利ツールズ

全部Package Managerからinstallできます。

Asset Bundle Browser

https://docs.unity3d.com/ja/current/Manual/AssetBundles-Browser.html

設定したAssetBundleの一覧、サイズなどが確認できます。
未登録のAssetの検索や、依存関係や、予期せぬアセットの追加などをチェックできるので、ざっと確認しておいた方がいいでしょう。
小さい案件や、あんまり複雑なアセット構造でないものであれば、ヘルプツールはこれでいいかな感はあると思います。

Asset Graph

https://docs.unity3d.com/ja/2019.4/Manual/com.unity.assetgraph.html

まだpreview(ベータ版)なので、扱いには注意してください。
手間なアセットビルドの流れをノードベースで作ることができます。

よくあるメンドクサイ手順として、プラットフォーム別にテクスチャ解像度を変える場合、解像度パターンを用意する場合などで、画像をリサイズしたりする手順を作れます。そういうケースで便利です。
あとはアセットを動的に作る場合など、スクリプトを書いて作ることができるようです。
そういうアセットビルド前の事前処理パターン数次第ですが、リサイズ程度ならツールはいくらでもある(うえ、OPTPiXなどのツールのほうが出力は高品質である)ので、わざわざ覚えてまで使う理由はないように感じました。
動的生成やパッチビルドについては、スクリプトを書く手間やビルド時間と相談って感じでしょうか。

Addressables

https://light11.hatenadiary.com/entry/2019/12/26/225232
https://light11.hatenadiary.com/entry/2020/02/06/203043

AssetBundleの管理システムを内包するパッケージです。
まあ、みんなやってるAssetBundle管理の仕組みをみんなに作らせるのは違うよねってなって、Unityが用意してくれた奴です。
Resources(実際にはAssetDataBase)とAssetBundleをコード上意識せず利用ができます。
コンシューマらしい「アドレス」という管理概念が出てきます。
重複チェック、エラーチェックとかもできます。

ただし使用時はコンバートが走り、Editor上の設定が消え、addressablesでしか管理やビルドができなくなります。設定もaddressablesのものになります。
↑2つと組み合わせるような、通常のAssetBundle利用を考えつつのエラー解決や調査目的の利用は実質できません。
使うときは注意が必要ですが、Managerを作る必要がないので、ある程度は楽になります。

依存関係チェック

https://techblog.kayac.com/unity-list-assets-relations
このあたりを参考にスクリプトを組んでみましょう

参考

https://qiita.com/k7a/items/df6dd8ea66cbc5a1e21d
https://qiita.com/k7a/items/d27640ac0276214fc850
https://docs.unity3d.com/ja/current/Manual/AssetBundles-Workflow.html