今回はAssetBundleにSceneを含め、実行時に展開する手法についてメモします。面倒くさいとおもったら、AssetBundle Managerを使うのが良いと思います。
AssetBundleにSceneを含める
通常、Unityでシーンを読込する為にはBuild Settingsにシーンを設定し、SceneManager.LoadScene等のAPIにて取得する方法が一般的ですが、実はScene自体をAssetBundleに格納し、AssetBundleから取得するといった方法があります。
この手法は(AssetBundle Managerを使用しないならば)動作検証にAssetBundleのビルドを必要としますが、以下のようなメリットがありそうです。
- Lightmapやbatcing等の静的な情報を持つコンテンツを追加できる
- Scriptを用いず、AssetBundle差替でアセットを更新できる
- (初回にダウンロードする)アプリサイズを小さくできる
- (物によっては)アプリの起動時間を短縮できる
但し、AssetBundleの依存関係やShaderのバリアント、ScriptのStrip等、幾つか注意すべき項目があります。
SceneをAssetBundleに含める方法
SceneをAssetBundleに含める方法は、他のAssetBundleと同じような手法で行けます。つまり、SceneファイルにAssetBundleNameを指定するだけです。
後はAssetBundleをビルドすれば、該当のプラットフォームで使用できるAssetBundleが生成されます。
ここで注目すべき事は二つです。
- AssetBundleの依存関係の無い、Sceneが参照する全てのアセットが含まれる
- SceneとScene以外を混ぜる事は出来ない
AssetBundleには、Sceneが参照する(かつ別のAssetBundle nameが明示的に指定されていない)アセットは全て含まれます。
例えばLightmapに関する情報や、Sceneが参照しているMaterialやMesh、Textureといった暗黙的に参照しているアセット群が、AssetBundleに含まれます。
これは場合によってはダウンロードするAssetBundleのサイズが激増したり、メモリを強烈に圧迫したりする為注意が必要となります(StreamedSceneAssetBundleに限った事ではありませんが)
Sceneを格納するAssetBundleh(StreamedSceneAssetBundle)は、シーン以外のアセットを明示的に含める事が出来ません。
もしScene以外のアセットが含まれている場合、ビルド時にCannot mark assets and scenes in one AssetBundle. AssetBundle name is "AssetBundle名".というエラーが表示されます。特にSceneを含むフォルダにAssetBundle名を指定する場合、このエラーがよく発生します。
なお、AssetBundle.LoadはLoadFromFiles等キャッシュされたアセットに対して行ってる限り、殆どコストはありません。限度はありますが、大量に開きっぱなしにしても一応問題ないっぽいです。限度はありますが。
AssetBundleからSceneをロードする方法
AssetBundleからSceneを読む場合、二つのステップで行います。
- Sceneを含むAssetBundle(StreamedSceneAssetBundle)をロード
- SceneManager(もしくはLoadLevel)でシーンをロード
動作のイメージ的には、AssetBundleをロードした際にBuildSettingsにStreamedSceneAssetBundleに格納したScene一覧が追加され、
SceneManagerでSceneをロードした際にAssetBundleに格納されているアセット群がメモリに展開、シーンがロードされる感じです。
StreamedSceneAssetBundleに格納されているScene群はGetAllScenePathsで取得出来ますが、LoadSceneで必要なのはScene名のみなので、Path.GetFileNameWithoutExtensionでシーン名のみを抽出します。
StreamedSceneAssetBundleの判定
StreamedSceneAssetBundleはアセットを含めたAssetBundleとは明確に呼出方法が異なります。その為、StreamedSceneAssetBundleかどうかを判断するisStreamedSceneAssetBundleが何時の間にか追加されていました。
また同時期に、「明示的にAssetBundleに格納したアセット」をロードするAssetBundle.LoadAllAssetsはStreamedSceneAssetBundleに対して呼び出せなくなりました。明示的にエラーにしているのは、StreamedSceneAssetBundleの場合、アセットとして抽出出来るアセットは無いので無意味、しかしパフォーマンス的に結構無駄になるとか、そんな感じかもしれません。
StreamedSceneAssetBundleにLoadAllAssetsをコールすると、InvalidOperationException: This method cannot be used on a streamed scene AssetBundle.(このメソッドは、ストリーム配信シーンAssetBundleに使用することはできません。)というエラーが表示されます。
依存関係の構築
AssetBundleは分割し依存関係を設定する事で、AssetBundle1つあたりのサイズを抑えたり、読込時間を短縮する事が出来ます。
但しSceneを読み込む前に依存するAssetBundleをロードしていない場合はmissingとなるので注意が必要です。(後で依存するAssetBundleを読み込む事でmissingを解決する事も可能だが、複雑になる)
ロードするStreamedSceneAssetBundleが依存しているAssetBundleを知るには、AssetBundleManifestを使用します。
AssetBundleの分割は、個人的には複数のシーンで使いまわしているアセットに対して行うのが良いんじゃないかなと思います。
なお、同名のAssetBundleは同時に複数回ロードする事は出来ません。そのためAssetBunleをDictionaryに保存してロードの判定するか、AssetBundle Managerのような上位レイヤーを使用するのが良さそうです。
続く
長くなるので分割します。
後は「AssetBundle シミュレーターの作り方」「SceneをAssetBundleに格納する際に注意すべきこと」「モバイル向けAssetBundleのダウンロード手法」「MODのようなシステムの作り方」について書く予定(予定話。