勉強がてら、アセットバンドルメーカーを自作してみました。
Asset Bundle Browser より気軽にアセットバンドルを作成したい人向けです。また、ソースコードを自分で書き換えたり、アセットバンドル理解の手助けになるかもしれません。
自由にお使いいただけますが、動作不良などの問題については責任を負えませんので、あくまで自己責任でお願いします。
BundleAssets と組み合わせると依存関係を気にせず読み込めたり、バンドル名を暗号化して使うことができます。
それらの機能に興味がなければ標準の Asset Bundle Browser、Addressable Asset System が無難です。
- 必要なアセットバンドルだけ、メニューでチェックして作成が可能。更新が必要と思われるアセットバンドルのみ、最初からチェックがつきます
(日付チェックと、AssetDatabase / manifest の内容比較です。依存関係のあるファイルも自動的にチェックがつきます) - アセットバンドルを便利に使えるようにするクラス BundlePath を自動生成します。BundleAssets クラスと併用することで、以下の便利機能が使えます
- アセットバンドル名の暗号化が可能になります(中身は暗号化しません)
- アセットバンドルの依存関係が manifest を見なくても可能になります。BundleAssets を使うことで、依存関係を気にすることなくアセットバンドルを呼び出すことができます
(BundleA に BundleB が含まれる場合、BundleA を Load するだけで、自動的に BundleB もロードされます。ネストの深さはツールが落ちない限り無制限) - アセットバンドルとファイル名の定数が定義されるので、アセットバンドル名に直接文字列を使わずにすみます
結構ややこしい事もしているので、動作不良を起こしていたらスミマセン。
ソースコード
2つあります。どちらも Editor/ 下に入れておいてください。(推奨は Editor/CreateAssetBundle/)
使い方
ファイル構成
- Assets/StreamingAssets_RawData/ にアセットバンドルする前の通常ファイルを入れます
- Assets/StreamingAssets/ というフォルダを作成します(こちらにアセットバンドルが作成されます)
- StreamingAssets_RawData のファイルを選択し、GUI でアセットバンドルを設定してください
作り方
Tools - CreateAssetBundle を選択
Asset Bundle List
存在するアセットバンドル一覧です。初めからチェックが入っているものはビルドが必要(と思われる)項目です。
依存関係がある場合も自動的にチェックされます。画像だと scene1 に必要そうな dependa dependc fonts/font images unitychan の 5 ファイルもチェックされています。
全てチェック / クリア
全てビルドする、全くビルドしないのトグルスイッチです。
全ての依存関係を検出する
アセットバンドルの依存関係は重い処理なので起動時は最低限の検出に留めていますが、全てのアセットバンドルの依存関係を見たければ、このボタンを押してください。
アセットバンドル名の暗号化
アセットバンドル名は公開情報のため、その名前から内容が類推できてしまうという欠点があります。
そのアセットバンドル名が分かり辛くなっているだけでも、かなり多くの人が解析を諦めてくれる事でしょう。
本当のクラッカーには何をやっても無駄ですが…。
暗号化した場合は後述の BandlePath.cs に記述された GetName() メソッドを使って実名から変換します。
暗号化は CreateAssetBundle.cs の中にある getEncryptFilename() です。
現段階では「なんちゃって暗号名」なので、その中身を書き換えて、もっと分かり辛い暗号化を施してもいいと思います。
アセットバンドルを作成
チェックのついた項目のアセットバンドルを作成します。
依存関係があるのにチェックのないファイルがある場合、警告文が表示されます。
警告文が出た場合、依存関係のあるアセットバンドルは全てチェックすることをお勧めします。
(大抵は自動的にチェックがついており、この警告を見ることはないでしょう)
閉じる
ウィンドウを閉じます。
依存関係があるのに、BuildPipeline.BuildAssetBundles() で一度にアセットバンドルを作らないと…?
おそらく仕様だと思うのですが、アセットバンドルの依存関係が消えてしまいます。上のアセットバンドルリストの scene1 は 5 ファイルの依存関係がありますが、scene1 しかビルドしなかった場合その依存関係が消え、結果として scene1 に全てのアセットが含まれてしまうため、メモリやロード時間など、予期しない被害を引き起こします。
その辺を上手くやってくれそうなフラグ BuildAssetBundleOptions.CollectDependencies は現在 Obsolete 扱いです。AssetDatabase.GetAssetBundleDependencies() では依存関係を正しく取るため、この区別に意味があるかどうかは「?」です。
BundlePath.cs
BundleAssets.cs や BundleScenes.cs があればその近く、なければ Assets/ 直下に BundlePath.cs が自動生成されます。以下の項目についてコード化します。
- バンドル名、ファイル名の const 値
- 実名から暗号名を取得するメソッド GetName()
暗号化していない場合、GetName() は実名を返します - バンドルが依存しているバンドル名リストを取得するメソッド GetDependencies()
// bundles //$$REGION$$ public const string BUNDLE_DEPEND_DEPEND_B = "depend/depend_b"; public const string BUNDLE_DEPEND_DEPENDA = "depend/dependa"; public const string BUNDLE_UNITYCHAN = "unitychan"; //$$BUNDLES$$ // files //$$REGION$$ public const string DependB = "dependb"; public const string DependA = "dependa"; public const string unitychan = "unitychan"; //$$FILES$$ static Dictionary<string, string> realNames = new Dictionary<string, string> { //$$REGION$$ { "depend/depend_b", "l6ajbl/zjoxpzoa" }, { "depend/dependa", "l6ajbl/zjoxpzq" }, { "unitychan", "2etymkjws" }, //$$REALNAMES$$ }; // dependencies dictionary static Dictionary<string, string[]> dependencies = new Dictionary<string, string[]>() { //$$REGION$$ { "depend/dependa", new string[] { "depend/depend_b", } }, //$$DEPENDENCIES$$ };
BundleAssets と併用して使うと、依存のあるバンドルを自動的に読み込んだり、暗号化ファイル名に対応していたり、非常に便利です。
BundleAssets についてはこちら。
技術的なこと
アセットバンドルの依存関係
バンドルの依存関係ですが、AssetDatabase.GetAssetBundleDependencies() で取得するだけでは「なにが必要か」はわかっても「どういう順番で読み込めばいいか」がわかりません。
期待する読み込み順
AssetDatabase.GetAssetBundleDependencies() で取得
recursive = false : assetA, assetB
recursive = true : assetB-1, assetB, assetB-2, assetA ← 順番がめちゃくちゃ
この順番を取得するためには、全アセットバンドルの依存関係を調べ、順番を並び替えてやる必要があります。
これがツール起動の速度を著しく悪くしている原因ですが、必要なことなので諦めます…。
そして、その並べ替えたデータは BundlePath.cs に保管しておき、BundleAssets.cs で読み込む際に
依存関係のあるファイルがまだ読まれていなければ、順番に従って読み込む
という処理を行っています。
アセットバンドル名の暗号化
依存関係は GUID で管理しているからか、BuildPipeline.BuildAssetBundles() に指定するアセットバンドル名は任意に変更することが可能です。
とはいえ、AssetBundle.LoadFromFileAsync() では作成名で呼び出すので、迂闊に暗号名にしてしまうと、開発で混乱します。
例えば AssetBundle.LoadFromFileAsync("2etymkjws") なんて、ちょっとなんのファイルかわからないですよね…。
そういった問題が起こらないよう、BundlePath.cs に実名と暗号名の参照セットを持っておき、GetName() で変換後の名前を取得できるようにしています。
BundleAssets.LoadAsync() を使う場合、この処理も自動的に行われています。
更新するファイルのチェック
ファイルを更新する必要があるかどうかは、色々な要素が絡んでいます。
- アセットバンドルを消した
- アセットバンドル内のファイルを増減した、別のファイルにした
- アセットバンドルを新規追加した(アセットバンドルがない)
- アセットバンドルより、元になるファイルの日付の方が新しい
- アセットバンドルの子要素(依存関係)がある
これらを過不足なくチェックするのは意外と大変なので、実際に運用しているゲームでは「そうだ、全部更新すればいいんだ」という安全方針が多いんじゃないでしょうか。
今回は勉強がてらという事もあり、上記を全て意識したチェックを行い、更新するべきファイルにチェックがつくようにしています。
(正直ここが一番自信がありません💦)
問題点
- 1 記事の割にはややこしい事を色々やっており、正しく動作するか不安です
- 依存関係が深すぎたり、バンドルファイルが多いと、なかなか起動しないかなしみを味わうことになります
(AssetDatabase.GetAssetBundleDependencies() が重い…) Remove Unused Names されたファイルが現状残ってしまいます(2020/08/21機能修正)
お世話になった記事
テラシュールさんには常にお世話になっております。大変有難い!
これ以外にも色々ありましたが、最近の記事はなかなかお見掛けしませんでした。
周回遅れの方、手を取り合いましょう(笑)