テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】AssetBundle シミュレーターの作り方について

AssetBundleをメインで使用して開発するのは良いのですが、コンテンツを変更するたびにビルドが必要になるので若干面倒くさい所があります。一応、Unity 5からのビルドパイプラインでは、内包するコンテンツに変化がなければ再ビルドしない設計になっていますが、数が数だと非常に時間がかかります。

 できればResourcesのように、毎回ビルドせずともロード出来るのが望ましいのです。

 

なので、今回はAssetBundleの内包するアセットをビルドせずロードする機能を作成してみようと思います。

 

なお、今回の記事はキャッシュシステムを自作してる人の場合です。
Unity標準のキャッシュシステムを使用していたり、AsssetBundle Managerの運用に気持ち悪さを感じないのであれば、AssetBundle Managerを使うのをお薦めします。
(AssetBundle Simulatorもありますし)

tsubakit1.hateblo.jp

 

AssetBundle シミュレーター

 

機能の概要

機能はAssetBundleから取得するアセット群を、普通にプロジェクトから取得するといった感じです。

 

機能

  • AssetBundleを構築せずともアセットの読込が可能
  • 変更した内容は即反映
  • AssetBundle読込モードに切替可能(の機能を自作出来る)
  • 要するに、最終的にAssetBundleへ移行出来るResources

制限

  • Variantは非対応
  • 依存関係は常に解決した状態でロードされる
  • AssetBundleとシミュレーターで依存関係を混ぜる事は出来ない
  • あくまでシミュレーター

StreamedSceneAssetBundleの呼出モドキ

f:id:tsubaki_t1:20160830005557g:plain

まずはStreamedSceneAssetBundleについてです。

通常Sceneをロードする場合にはBuildSettingsにシーンを設定する必要がありますが、AssetBundleはBuildSettingsにSceneを登録しなくても呼べます。なので、以下のメソッドで呼び出します。

下のメソッドであれば、BuildSettingsに登録しなくても呼べます。

  • UnityEditor.EditorApplication.LoadLevelAdditiveInPlayMode
  • UnityEditor.EditorApplication.LoadLevelInPlayMode
  • UnityEditor.EditorApplication.LoadLevelAdditiveAsyncInPlayMode 
  • UnityEditor.EditorApplication.LoadLevelAsyncInPlayMode

f:id:tsubaki_t1:20160830012118j:plain

AssetBundleに格納したシーンを取得するには、

  • AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName

を使用します。

引数にAssetBundle名とシーン名を指定するとリストの最初にシーンのパスが格納されているので、後はEditorApplication.LoadLevel云々でロードすればOKです。

AssetBundle.LoadAssetモドキ

f:id:tsubaki_t1:20160830011524g:plain

アセットの呼出はある意味Sceneよりシンプルです。

まずはSceneと同様に、AssetBundle名とAsset名でファイルパスを取得します。

  • AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName

取得したパスに対して、以下のAPIでオブジェクトを取得します。

  • UnityEditor.AssetDatabase.LoadAssetAtPath<T> 

これをAssetBundle.LoadAssetsで取得した感じに、 GameObject.Instantiate.してやれば、インスタンスが生成出来ます。

f:id:tsubaki_t1:20160830012041j:plain

AssetBundle.LoadAllAssetsモドキ

LoadAllAssetsは少し異なります。まず、指定のAssetBundle名が設定されているアセットを全部取得するAPIで、AssetBundle内(に入る予定の)アセットを全て取得します。

  • UnityEditor.AssetDatabase.GetAssetPathsFromAssetBundle 

後は取得したアセット全てをリストに格納して、Arrayで返します(LoadAllAssetsがArrayの為)

f:id:tsubaki_t1:20160830012018j:plain

 

サンプル

試しに作ってみたサンプルです。

define symbolesにASSETBUNDLE_SIMULATORを設定すればシミュレーターモード、消せばStreamingAssetsからAssetBundleを取得します。

gist.github.com

 

実際に使うときは、こんな感じです。

f:id:tsubaki_t1:20160830012557j:plain

gist.github.com

関連

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

【Unity】SceneをAssetBundleに格納して実行時にロードする方法について

f:id:tsubaki_t1:20160821210525j:plain

今回はAssetBundleにSceneを含め、実行時に展開する手法についてメモします。面倒くさいとおもったら、AssetBundle Managerを使うのが良いと思います。

tsubakit1.hateblo.jp

AssetBundleにSceneを含める

通常、Unityでシーンを読込する為にはBuild Settingsにシーンを設定し、SceneManager.LoadScene等のAPIにて取得する方法が一般的ですが、実はScene自体をAssetBundleに格納し、AssetBundleから取得するといった方法があります。

この手法は(AssetBundle Managerを使用しないならば)動作検証にAssetBundleのビルドを必要としますが、以下のようなメリットがありそうです。

  • Lightmapやbatcing等の静的な情報を持つコンテンツを追加できる
  • Scriptを用いず、AssetBundle差替でアセットを更新できる
  • (初回にダウンロードする)アプリサイズを小さくできる
  • (物によっては)アプリの起動時間を短縮できる

f:id:tsubaki_t1:20160821213104j:plain

但し、AssetBundleの依存関係やShaderのバリアント、ScriptのStrip等、幾つか注意すべき項目があります。

 

SceneをAssetBundleに含める方法

SceneをAssetBundleに含める方法は、他のAssetBundleと同じような手法で行けます。つまり、SceneファイルにAssetBundleNameを指定するだけです。

後はAssetBundleをビルドすれば、該当のプラットフォームで使用できるAssetBundleが生成されます。

f:id:tsubaki_t1:20160821213549j:plain

ここで注目すべき事は二つです。

  • AssetBundleの依存関係の無い、Sceneが参照する全てのアセットが含まれる
  • SceneとScene以外を混ぜる事は出来ない

AssetBundleには、Sceneが参照する(かつ別のAssetBundle nameが明示的に指定されていない)アセットは全て含まれます。

例えばLightmapに関する情報や、Sceneが参照しているMaterialやMesh、Textureといった暗黙的に参照しているアセット群が、AssetBundleに含まれます。
これは場合によってはダウンロードするAssetBundleのサイズが激増したり、メモリを強烈に圧迫したりする為注意が必要となります(StreamedSceneAssetBundleに限った事ではありませんが)

f:id:tsubaki_t1:20160822103226j:plain

Sceneを格納するAssetBundleh(StreamedSceneAssetBundle)は、シーン以外のアセットを明示的に含める事が出来ません
もしScene以外のアセットが含まれている場合、ビルド時にCannot mark assets and scenes in one AssetBundle. AssetBundle name is "AssetBundle名".というエラーが表示されます。特にSceneを含むフォルダにAssetBundle名を指定する場合、このエラーがよく発生します。

f:id:tsubaki_t1:20160821214500j:plain

なお、AssetBundle.LoadはLoadFromFiles等キャッシュされたアセットに対して行ってる限り、殆どコストはありません。限度はありますが、大量に開きっぱなしにしても一応問題ないっぽいです。限度はありますが。

AssetBundleからSceneをロードする方法

AssetBundleからSceneを読む場合、二つのステップで行います。

  1. Sceneを含むAssetBundle(StreamedSceneAssetBundle)をロード
  2. SceneManager(もしくはLoadLevel)でシーンをロード

動作のイメージ的には、AssetBundleをロードした際にBuildSettingsにStreamedSceneAssetBundleに格納したScene一覧が追加され、
SceneManagerでSceneをロードした際にAssetBundleに格納されているアセット群がメモリに展開、シーンがロードされる感じです。

 

StreamedSceneAssetBundleに格納されているScene群はGetAllScenePathsで取得出来ますが、LoadSceneで必要なのはScene名のみなので、Path.GetFileNameWithoutExtensionでシーン名のみを抽出します。

f:id:tsubaki_t1:20160821220801j:plain

 StreamedSceneAssetBundleの判定

StreamedSceneAssetBundleはアセットを含めたAssetBundleとは明確に呼出方法が異なります。その為、StreamedSceneAssetBundleかどうかを判断するisStreamedSceneAssetBundleが何時の間にか追加されていました。

また同時期に、「明示的にAssetBundleに格納したアセット」をロードするAssetBundle.LoadAllAssetsはStreamedSceneAssetBundleに対して呼び出せなくなりました。明示的にエラーにしているのは、StreamedSceneAssetBundleの場合、アセットとして抽出出来るアセットは無いので無意味、しかしパフォーマンス的に結構無駄になるとか、そんな感じかもしれません。

f:id:tsubaki_t1:20160822093213j:plain

StreamedSceneAssetBundleにLoadAllAssetsをコールすると、InvalidOperationException: This method cannot be used on a streamed scene AssetBundle.(このメソッドは、ストリーム配信シーンAssetBundleに使用することはできません。)というエラーが表示されます。

依存関係の構築

AssetBundleは分割し依存関係を設定する事で、AssetBundle1つあたりのサイズを抑えたり、読込時間を短縮する事が出来ます。

但しSceneを読み込む前に依存するAssetBundleをロードしていない場合はmissingとなるので注意が必要です。(後で依存するAssetBundleを読み込む事でmissingを解決する事も可能だが、複雑になる)

 

ロードするStreamedSceneAssetBundleが依存しているAssetBundleを知るには、AssetBundleManifestを使用します。

f:id:tsubaki_t1:20160823232328j:plain

AssetBundleの分割は、個人的には複数のシーンで使いまわしているアセットに対して行うのが良いんじゃないかなと思います。

なお、同名のAssetBundleは同時に複数回ロードする事は出来ません。そのためAssetBunleをDictionaryに保存してロードの判定するか、AssetBundle Managerのような上位レイヤーを使用するのが良さそうです。

 

続く

長くなるので分割します。

後は「AssetBundle シミュレーターの作り方」「SceneをAssetBundleに格納する際に注意すべきこと」「モバイル向けAssetBundleのダウンロード手法」「MODのようなシステムの作り方」について書く予定(予定話。

関連

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

 
 

【Unity】「'Assembly-CSharp.dll' shouldn't be queried by IsAssemblyCompatible, missing IsInternalOrCompiledAssembly check ?」の対処法

f:id:tsubaki_t1:20160818233048j:plain

ビルドの度に'Assets/Assembly-CSharp.dll' shouldn't be queried by IsAssemblyCompatible, missing IsInternalOrCompiledAssembly check ?のようなエラーが出た場合の対処法についてです。

コンポーネントを編集する度に出てくるエラー

このエラー、コンポーネントを編集する度に出てきます。一応Clearで全て消す事が出来ますが、ビルドする度に出てきます。

 

Assembly-CSharpをAssets以下に含めると出る

このエラーですが、どうもAssembly-CSharp.dll等のdllをAssetsフォルダ以下に含めると出てくるようになるみたいです。

含めた直後はerror CS2015: Source file `***/Managed/Assembly-CSharp.dll' is a binary file and not a text fileのようなエラーが出てビルドが通りません。

で、含めたdll群を削除すると、shouldn't be queried by IsAssemblyCompatible, missing IsInternalOrCompiledAssembly check のエラーが出始めます。

 

これを起こす簡単な方法は、Assets以下にアプリをビルドしてしまう事です。条件はわかりませんが、一度Build and runしたらAssets以下のフォルダが指定されておりAssetsフォルダにアプリがビルドされて、こうなりました。

f:id:tsubaki_t1:20160818234010j:plain

対策はエディタの再起動

これが起こると、コンパイルが走る毎にエラーが出てくるので、エディタを再起動します。

 

【Unity】アプリに含めるアセットを抑えて、アプリサイズを小さくする

f:id:tsubaki_t1:20160818201357j:plain


今回は、Unityプロジェクトからアプリケーションをビルドした際、Unityプロジェクトに含まれるアセットについてです。

Assets以下のアセットは全て含まれる訳ではない

Unityはアセット(TextureやModelやAudioやScript等)を使用する場合、Assetsフォルダ以下に配置する必要があります。
偶に、Assets以下に含まれるアセットは全てビルドしたゲームに含まれると考えている人がいますが、それは間違いです。

 

アプリ内に含まれるアセット群については、アプリケーションのビルド後にEditor.Logで確認出来ます。Console > 三 > Open Editor Logで実際に開いて確認してみると、Assets以下に含めたアセットと比較して減っている事が分かります。

f:id:tsubaki_t1:20160818201550j:plain

 

とりあえず探すときは、一番下からComplete sizeを検索すると、すぐ見つかります。

f:id:tsubaki_t1:20160818201930j:plain

アプリに含まれるアセットの数(特に大きいアセット)が減れば減るほど、アプリサイズは当然小さくなります。逆に、未使用なアセットが沢山含まれていれば大きくなります。

ここに含まれるには、どういった条件があるのかを確認してみました。

 

Resourcesフォルダ以下のアセット

まずResourcesフォルダ以下に含まれるアセットは基本的に全てアプリに含まれます。これはEditorフォルダ以下にResourcesフォルダを設定した場合もです。

 

ここで注目すべきは、Resourcesに含めたアセットが参照しているアセットも含まれる…という事です。例えば、PrefabがShaderやMaterial、AudioやAnimation等のデータを参照している場合、それらも全て含まれます。

例えば下のPrefabの場合、キャラクターの持つSprite及びAnimationController、AnimationControllerが持つAnimationClip達と、AnimationClipが持つSprite等が含まれています。

f:id:tsubaki_t1:20160818203952j:plain

Resourcesは使わないサブアセットも全て含める

また、Textureの持つSpriteやFBXの持つMeshやAnimationClip Avater等の情報も丸ごと含まれます。これは物によっては非常に勿体のない事になります

例えば下の例の場合、HumanoidCrouchのFBXを丸ごとResourcesに含めると、FBXのサイズは393kbになりますが、HumanoidCrouchIdleのみを参照するPrefabをResourcesに配置した場合、33kbまで小さくなります。また、ロード時間も(僅かに)短縮されます。

f:id:tsubaki_t1:20160818205034j:plain

f:id:tsubaki_t1:20160818205619j:plain

上は極端な例で、全てのAnimationとMeshとAvaterを使用してるなら差は殆どありません。しかし、Resourcesフォルダ以下に配置したファイルが多ければ多いほど起動時間が伸びるので、その辺りも考えて配置する必要があります。

Spriteは少しルールが異なる

 サブアセットの参照についての例外は、Spriteです。
AnimationとFBXの場合、上に書いたとおりAnimationClipが参照しているアセットのみをアプリに含めます。しかし、Spriteの場合は少しルールが異なります。

 

まず特定のSpriteを参照する事でアプリに含めた場合です。

Spriteを効率的に表現するには、単一のTextureに幾つかのSpriteを含めTextureの数を減らす事が重要になります。そのため、多くのケースではAtlas化(複数のSpriteを一つのTextureに纏める)を行います。

つまり、Textureが沢山のSpriteを持つ訳です。

Spriteの情報(Textureへの参照やUV情報)は未使用ならばゲームに含まれませんが、Textureはどれか一つでも参照があれば含まれてしまいます。

このため、参照を用いてアセットへ参照をかけても、あまりアプリサイズに影響しません。(Spriteのサイズ分は減ります)

f:id:tsubaki_t1:20160818221628j:plain


またSprite Tagを使用している場合、Spriteを直接参照している場合と比較してEditor.logに出力されるファイルサイズ表記が変化します。

Sprite tagを使用していない場合はTextureとSpriteのサイズが乗りますが、SpriteTagを使用している場合はSpriteのアセット分のみが乗ります。

f:id:tsubaki_t1:20160818220620j:plain

なおSpriteのTagはResourcesでは使えません。その為、Atlas化してドローコールを減らしたい場合、外部アセットでテクスチャをAtlas化してMultiple Spriteを設定する必要があります。

tsubakit1.hateblo.jp

読めない(インポート出来ない)アセットは含まれない

Resourcesは基本的に全ての「アセット」を含めますが、Unityがアセットと認識出来ないファイルは含まれません。

例えばxlsやxlsx、dbといった拡張子のファイル郡です。

一応bytesやassetと拡張子を変更する事でアプリ内に含める事は可能です。その場合、TextAssetにて読み込みbytesを引っ張り出します。
自分でデコードする処理を考える必要はありますが、上手くすればUnity非対応のフォーマットやpng等のファイルを実行時にロード(メモリリッチな方法)出来ます。

tsubakit1.hateblo.jp

BuildSettingsに含まれているSceneが参照するアセット

Sceneサイドでは、BuildSettingsに含まれるSceneが参照しているアセットがゲームに含まれます。

Sceneから参照されている条件は、Sceneに配置したGameObjectのコンポーネントから参照されている事が条件になります。

f:id:tsubaki_t1:20160818210934j:plain

アプリに含まれるのはあくまでBuild Settingsに含まれている(ScenLoadで呼び出せる)シーンのみで、それ以外のシーンに関しては、アプリに含まれません。

f:id:tsubaki_t1:20160818211245j:plain

PreloadAssetsやAlwaysIncludeShaderで指定

ResourcesやScene以外でのケースではPreloadAssets(モバイル)やAlwaysIncludeShaderといった物があります。

PreloadAssetsはアプリの起動時に事前にアセットをロードしておく機能です。このアセットをPreloadAssetsに設定しておくことで、ゲーム起動時にアセットを事前読込したり、Sceneに依存しない共有インスタンスとして使用出来ます。

tsubakit1.hateblo.jpAlwaysIncludeShaderは、指定のシェーダーをアプリに常に含める事が出来ます。
(それを使うかどうかはまた話は別なのが面倒な所ですが)

 

StreamingAssetsとPluginsはそのまま含まれる

StreamingAssetsやPlugins/iOSやPlugins/Androidに含めたファイルは、そのまま含まれます

Plugins/iOSやPlugins/AndroidはプラットフォームがiOS/Androidだった場合、直下のアセットをそのまま含んでくれるので、プラットフォーム依存したファイルを云々したい場合には使えるかもしれません。この動作NativePlugins(objective-cやjar)等からアクセスする用といった気配がします。

StreamingAssetsはAndroidだとFileクラス等でアクセス出来ないので、アクセスするにはWWWでロードするか自分でapkを解凍するかしないといけません。

tsubakit1.hateblo.jp

スクリプトは大体含まれる

スクリプト全部含まれます

但し、Editorフォルダ以下のスクリプトは含まれません。
エンジン側のコード(及び多分個別にDLL化したコード)は、スクリプトが参照している場合は含まれます
スクリプトは全部含まれるので、場合によっては余計なコードがストリップを抑制する事もある。AssetBundleや全体をDLL化したせいでストリップされ過ぎる事もある)

 

また、.DLLの場合はPlatformの指定にチェックが入っている場合に含まれます。

f:id:tsubaki_t1:20160818225315j:plain

アプリサイズを減らす為には

上のルールを元に、使用していないアセットをアプリに含まれるのを防ぐのがアプリサイズを減らす上で重要な要素になりそうです。

例えばReference Viewerを使用して、該当のアセットが何故含まれているかを確認したり、AssetCleanerでそもそも使用してないコードやアセット群を削除してしまう等です。

https://camo.githubusercontent.com/bb8e19800731cf52ea1b4effa83869e354bc6a13/68747470733a2f2f646c2e64726f70626f7875736572636f6e74656e742e636f6d2f752f3135333235343436352f73637265656e73686f742f254533253832254239254533253832254146254533253833254141254533253833254243254533253833254233254533253832254237254533253833254137254533253833253833254533253833253838253230323031342d30322d313625323031382e35352e30332e706e67

github.com

tsubakit1.hateblo.jp

アセットレベルでのストリップはルールをちゃんと把握していればビルドサイズ的なメリットは余り無いですが、ビルド時間的には超意味があるので、その辺りも含めて。

関連

tsubakit1.hateblo.jp2

tsubakit1.hateblo.jp