【Unity】Unity 5.3からAssetBundleはどう変わるのか…まとめ
この記事は Unity 2 Advent Calendar 2015 の16日目の記事です。
14日めはookumaneko_XDさんでUnityで学ぶシーングラフ入門の入門モドキです。
今回はUnity 5でAssetBundle周りがどう変わったのか、Unity 5.3からどう変わったのかについて書いていこうと思います。
- Unity 5.3のAssetBundleの作成
- Unity 5.3のAssetBundleの読込
- キャッシュについて
- 依存関係の解決について
- 過去バージョンとの互換性
- AssetBundleにのみ使われているクラスがある場合の注意
- シェーダー読込失敗のケース(Unity 5.3f4)
- まとめ
Unity 5.3のAssetBundleの作成
まずはUnity 5のAssetBundleのおさらいについてです。
Unity 4と異なりUnity 5ではAssetBundleの生成・読込が大分シンプルになりました。
例えばAssetBundleをStreamingAssetsへ作成し、読み込んでみます。
アセットにAssetBundle Nameを設定します。
この時、アセット毎でも良いのですがフォルダに名前を指定してやるとフォルダ以下のアセットを全て格納するので楽出来ます。
とりあえずSpritesフォルダにspriteタグを付け、中にPlayerオブジェクトでも配置しておきます。
後はAssetBundleを作成します。以下のコードをAssets/Editorフォルダ以下にでも配置すればOKです。
メニューバーの/AssetBundle/Exportが*1追加されるので、それを選択するとAssetBundleがStreamingAssetsに作成されます。
この時、AssetBundleの構造に変化が無い物はビルドされないです。
Assetbundleの作成は標準機能「だけ」ではまだ出来ずコードが必要です。
とはいえ、ノードベースでAssetBundleを作成するAssetGraph(下図)や、簡単なAssetBundle作成機能と運用管理機能の付いたAssetBundleManagerなどがあるので、そこまで頑張らなくても良いです。
なおAssetBundle名に「/(スラッシュ)」を使用すると癖のある動きをするので注意が必要です。
なおスクリプトだけでビルドを完結させる事も一応出来ます。
下のコードは、選択中のオブジェクトをAssetBundleとして出力するサンプルです。
AssetBundleをダイアログを指定して出力する。依存関係は知らない · GitHub
Unity 5.3のAssetBundleの読込
次は読込です。
下のコードを実行します。
これでAssetBundle内にあるPlayerオブジェクトがインスタンス化されます。
Androidで動かす場合は、WWWからアクセスする必要があります。*2
AssetBundle.LoadFromFileが圧縮したAssetBundleに対応
以前からAssetBundleを使ってきた人ならここで「おや」と思うかもしれません。そう、Unity 5.3から「AssetBundle.LoadFromFile」でも圧縮したAssetBundleを読む事が出来るようになりました。
これにより圧縮したAssetBundleをわざわざAssetBundle.CreateFromMemoryを使用せず使えるようになります。Monoメモリの肥大化を防ぐ効果が期待出来そうです。
またUnity 5.3よりチャンクに対応したAssetBundleを生成可能になりました。
チャンクベースのAssetBundle
チャンクに対応したAssetBundleは、圧縮した状態からAssetBundle.LoadFromFileを実行し、余計なIOアクセス・余計なメモリ消費無しにアセットが取得できます。
これはどういうことかと言うと、
以前使用していたLZMA AssetBundleは、単一のファイルとしてアセット群を格納していたため、取得する為には、一旦全体のファイルを取得し圧縮を解除する必要がありました。これはIO的にもメモリ的にも無駄であり、LoadFromCacheOrDownloadを利用する動機でもあります。
これに対してLZ4では「チャンクベース」なるアルゴリズムで動作するらしく、各アセット毎に圧縮を行う事で特定のアセットのみを解凍・取得する事が出来るとの事です。
Unity - Manual: Asset Bundle Internal Structure
チャンク対応しないAssetBundleと比較してファイルサイズが倍程度になりますが、非圧縮AssetBundleよりも小さく、かつLZMAやストリームなAssetBundleより効率的にアセットが取得できます。
チャンクベースのAssetBundleを使用するには、BuildoptionにBuildAssetBundleOptions.ChunkBasedCompressionを指定します。
キャッシュについて
Unity 5.3より、コロプラさんのセッションにより絶賛不人気なWWW.LoadFromCacheOrDownloadのキャッシュですが、非チャンクなAssetBundleをダウンロードした場合もダウンロード時にLZ4へ再圧縮することで、キャッシュサイズを大幅に減らす事が可能になりました。
これによりキャッシュ時のアプリサイズが大幅に減る事になりそうです。
再圧縮を行わずキャッシュしたい場合は、Caching.compressionEnabled = falseを指定します。読込時間はこちらの方が高速ですが、アプリサイズが膨れます。
LoadFromCacheOrDownloadが嫌な場合、チャンク対応なAssetBundleをローカルへ自分で配置するのが良さそうです。
AssetBundleを読み込むAPIの挙動の違い
AssetBundleを読み込むAPIは幾つかありますが、全てが同様に動く訳ではありません。
- WWWから取得
LZMAならばLZMAとLZ4へ再圧縮したメモリを消費し、LZ4ならばLZ4のメモリを消費します。双方共に余計な負荷が発生します。
(5.2以前はLZMAとLZMAを解凍したメモリを消費) - LoadFromCacheOrDownloadから取得
キャッシュ後は余計な負荷も無駄なメモリ消費も発生しません。 - LoadFromMemory
一度メモリに展開する関係上、Monoのメモリを膨らませます。
WWWと同程度の負荷っぽいです。AssetBundleを暗号化したい場合は、こっちを使用します。 - LoadFromFileAsync
LZMAはLZMAを解凍する関係上、全てメモリに展開して処理するので時間もかかるし無駄も多いです。
LZ4はLoadFromCacheOrDownloadと同じく、余計な処理とメモリ消費が発生しません。 - WebRequest
WWWと同じ?
Unity - Manual: Asset Bundle Compression
依存関係の解決について
Unity 5になり、AssetBundleの依存関係の解決が非常にシンプルになりました。
例えばメッシュやテクスチャ情報を持つ「RollBall」アセットバンドルと、Prefab群を持つ「Prefabs」アセットバンドルを用意します。
これをAssetBundleとして出力します。
後は、Rollballプレハブをインスタンス化する前にRollBallアセットバンドルをロードしておくと、依存関係を自動的に解決してくれます。
むしろ、GameObjectを作成したに追加でロードした場合も、依存関係を解決してくれます。
依存関係の自動解決によるリソースの追加読み(下準備)
例えば、下図のようなシーンを用意します。
このシーンは大きく分けると、ステージを構成するStageオブジェクト群と、リソース周りを他のAssetBundleへ全て格納しているRollBallに分かれます。
シーンをAssetBundleへ出力します。
やり方は、シーンにAssetBundleName付けてビルドするだけです。
AssetBundleに格納したシーンを読み、リソースを後から追加する
で、このAssetBundle化したシーンを読んでみます。
リソース周りは全て他のAssetBundleに格納しているので、このようなステージのみの表示となります。
追加でリソースを読みます。
若干面倒なのが、リソースを読んだ直後は参照missingが解決するだけで、初期化はされない事です。初期化するにはGameObjectごとactive/非activeをスイッチしてやる必要があります。
今回は面倒なのでGimicオブジェクト以下に置いてやってしまいましたが、実際にやるにはもう少し効率的なやり方がありそうです。
とりあえずプレイヤーとステージのみを先にロードしておき、NPCキャラクター等の重要度が低いキャラクターを後でロードするようなケースに使えるかなって気がします。
またリソースやシーンをAssetBundleに格納しStreamingAssetsへ格納することで、*3単純なアプリサイズの削減(PCやiOSのみ)、パッチを当てやすくする等の効果が期待出来るかもしれません。
過去バージョンとの互換性
今まで勘違いしてましたが、どうやらUnity 5のAssetbundleはUnity 4で作成したAssetbundleをロードする事が出来たみたいです。
追記:※ただしPC向けのみ
勿論スクリプト込のPrefabといった物は怪しいですが、リソースのみを取り出す形であれば何とかなりそうです。
AssetBundleにのみ使われているクラスがある場合の注意
AssetBundleでのみ使用しているクラスが存在する場合、かつIL2CPPを使用している場合、1点注意すべき事があります。それは、ゲームをビルドする際Unityエンジンから未使用コードが削除されてしまう点です。
例えば旧いパーティクルシステムのParticleRendererをAssetBundleのみで提供する場合、またアニメーションをビルドしたゲームには含めていない場合、このような問題が発生します。
このため、例えばAnimationをAssetBundle内にのみ格納している場合、参照先のクラスが無い為Could not produce class with ID 番号のようなエラーが出力されます。
対策は二つ、エンジンコードをストリップしない方法と、エンジンコードからストリップさせない方法です。
エンジンコードをストリップしない
エンジンコードからストリップしない方法は、PlayerSettingsのOther SettingsにあるStrip Engine Codeからチェックを外すだけです。
この項目はIL2CPPが有効になっている時のみ表示されます。
特定のエンジンコードをストリップさせない
特定のエンジンコードのみをストリップさせたくない場合のケースです。この場合、二通りの回避方法があります。
まず「コードを参照するコードを記述する」ですが、これは単純にスクリプトにコードを書くだけです。例えば、パーティクルを消したくない場合、以下のようなダミーコードを用意します。
次に「ビルドするゲームからストリップしたくないコンポーネントを参照する」ですが、要するにResourcesのような確実にゲームに含めてしまうフォルダに、コンポーネントを参照しているオブジェクトやアセットを放り込むだけです。
例えばAnimationControllerがストリップされてしまっている場合、Resourcesフォルダ以下に空のAnimatorControllerを作成して配置します。
最後に「link.xmlを使用する」についてです。link.xmlを使用することで、特定のコンポーネントがストリップされる事を回避します。
link.xmlでコードのストリップを回避する
まずAssetBundleが使用している(削除されたくない)コンポーネントを把握します。
AssetBundleは生成する際、Manifestファイルを生成します。このManifestファイルには、AssetBundleの参照関係の他に、AssetBundleが使用しているクラスのIDが含まれます。
このクラスIDですが、Unity - マニュアル: YAML クラス ID リファレンスと一致します。例えばクラスID 28はTexture2D(UnityEngine.Texture2D)です。
後はここで取得したクラスIDを元にlink.xmlを記述します。
link.xml*4をAssetsフォルダ直下に作成し下のようなフォーマットでストリップから外すコードを追加します。
Texture2DとかGameObjectのような本来確実に含まれる物は別に書かなくても良いです。
なおAnimationControllerのようなエディタクラスは、link.xmlで定義しても意味が無いっぽいので、馬鹿らしいですがリソースを配置する方法でストリップを回避する必要があるみたいです。
シェーダー読込失敗のケース(Unity 5.3f4)
Unity 5.3f4現在、エディタでAssetBundleからモデルを取得した際シェーダーの読み込みが失敗するケースがあります。
これはMetalやOpenGLCoreを使用しないようにすれば回避できるみたいです。(バグレポ済)
まとめ
Unity 5.2以前と比較し、省メモリで、省スペースで、割と高速で、ある程度独自で、AssetBundleを運用できるようになった気がします。
日本に帰ってきてからの記事なので少し時間かかった…
次回はKan_Kikuchiさんで、素材系のおすすめAsset40選です。