😎

Unity AssetBundle(+α AAS) メモ

2024/02/21に公開

メモしたの書きなぐります。

アセット回り

・アセットを別々のグループすることで、ランタイムに選択した追加コンテンツを段階的にダウンロードできる。
参照サイト

・Addressablesを利用することで、ゲームやアプリのダウンロードサイズとメモリ使用量を管理し、最初のダウンロードサイズを小さくして、実行時に他のアセットを後でロードすることができる。
参照サイト

・アセットをターゲットプラットフォームに合わせて最適化する。
マルチプラットフォームのプロジェクトでは、何百種類ものテクスチャがあり、それぞれのプラットフォームに合わせて異なる解像度でパッケージ化する必要がある。Unityでは、各ターゲットプラットフォーム用にビルドする際に、アセットのパッケージ化、サイズ変更、再圧縮を自動的に行える。
・プラットフォームにおけるユーザーの期待値も重要な要素である。例えば、モバイルプラットフォームでは、最初のダウンロードとインストールのプロセスが長いと、プレイヤーがアプリケーションをプレイする前に放棄してしまう可能性がある。このような理由から、モバイルアプリケーションでは、最初のビルドでは最小限のアセットしか含まれておらず、ユーザーが初めてアプリケーションを実行したときに、残りのアセットをリモートサーバーからダウンロードするのが一般的。
参照サイト

・Scripted Importerで、Unityが対応していない拡張子をアセットにする
また、自作した場合、インポータの一貫性を意識する必要がある。
参照サイト参照サイト

・Unityエディタ外(エクスプローラー上)で、アセットやスクリプトを移動などを行うと、参照がなくなる。
例えば、テクスチャであればマテリアルなどにテクスチャを付けていた場合、テクスチャが外れるし、スクリプトの場合は、Missingという表示になりスクリプトが利用できなくなる。
Unityエディター上で行えばこのようなことが起きない。
参照サイト

・AssetDatabaseで何かをロードする際のメソッド群。
AssetDatabase.LoadAssetAtPath、AssetDatabase.LoadMainAssetAtPath、AssetDatabase.LoadAllAssetRepresentationsAtPath、AssetDatabase.LoadAllAssetsAtPath
参照サイト

・AssetDatabase で、アセットの作成、名前変更、コピー、フォルダ作成、移動、コピー、削除を行う。
AssetDatabase.Contains、AssetDatabase.CreateAsset、AssetDatabase.CreateFolder、AssetDatabase.RenameAsset、AssetDatabase.CopyAsset、AssetDatabase.MoveAsset、AssetDatabase.MoveAssetToTrash、AssetDatabase.DeleteAsset。
また、これらの処理が重い場合は、バッチ処理のようにまとめてできるようなやり方があり、それによって早くできる。
参照サイト参照サイト

atching.html)

・Asset Importerで、アセットインポート時、Asset Importerで処理後に、モデル、テクスチャ、アニメーション、音楽、スプライト、キューブマップ、マテリアルなどの設定項目をスクリプト上から変更できる。
また、OnPostprocessAllAssetsを使用すれば、全てのアセットの名前がパスカルケースになっているかなど判定できたりする。
参照サイト参照サイト参照サイト

・Import Activity を使用して、Asset、スクリプト、フォルダなどの情報を確認。
インポートした日時・回数、インポートするのにかかった時間、GUID、パス、サイズ、再インポートした理由、依存数など一覧として確認できる。
また、ソート機能も搭載している。
参照サイト

・Preset Managerで、アセットインポート時やコンポート追加時に特定の設定を適用できる。
また、AssetPostprocessor でも可能。
一々設定する労力や、設定ミスにより負担がかかり不具合が出たりする可能性もありうるので活用した方が良い
参照サイト参照サイト参照サイト

・UnloadSceneAsync(UnloadScene)でシーンをアンロードすると、シーンのオブジェクトはアンロードされるが、アセットはアンロードされない。Resources.UnloadUnusedAssets()を使用すると、アセットもアンロードされる。
検証したところProfilerのMemororyのScene MemoroyとAssetsを見ると確かにAssetsの方はアンロードされず残っていた。
なお、UnloadSceneAsync(UnloadScene)でアセットがアンロードしないのは、LoadSceneMode.Additiveでシーンロードときである。
LoadSceneMode.Singleでロードした場合、それまでロードしていたのは、Scene MemoroyとAssetsからアンロードされる。

・スマホの情報一覧
参照サイト

・Select Dependenciesで依存しているものを表示できる

・ProfilerのScene Memoryは、Unityが管理しているネイティブメモリに分類される。このシーン内のものをScene Memoryからなくすことで、メモリの容量減らすことができる。
シーン内にオブジェクトを動的に生成して実験してみた。
①何もしない状態

②生成したオブジェクトを、Destoryで破棄。
Destoryすることで、Scene Memoryにあるものが削減されていることが分かる

参照サイト

AssetBundle

・Resources、StreamingAssets、AssetDatabase、AssetBundleのメリット・デメリット
参照サイト

・スクリプトやプロジェクト設定はアセットバンドルできない。アセットバンドル化できるアセットは、アセットバンドル名を指定できる
参考書籍のp480

・プラットフォームごとに同一のアセットバンドルファイルが必要。複数のプラットフォームに対応させるときは、アセットバンドルをプラットフォームごとに複数用意しなくてはならない
参考書籍のp480

・インスペクターで、アセットバンドルの名前を決めるが、その際同じ名前にすると同じアセットバンドルにアセットがまとめられる
参考書籍のp480

・シーンをアセットバンドルし、ロードすると、シーン内のオブジェクトなどはロードしなくても良く、 SceneManager.LoadSceneAsyncするだけでシーンを表示できる
また、アセットバンドル化したシーンは、ビルド時にアプリに含まれてしまうため、
Scene in Buildに追加不要。アセットが重複した状態となって、メモリに問題が生じる。
また、注意点としては、アセットバンドル化したいシーンと、アセットバンドル化したい通常のアセットを同じアセットバンドル名にはできない。ビルド時にエラーが出る。
参考書籍のp488

・アセットバンドルの中に複数のアセットを入れることが可能である。しかし、数枚の画像を更新しただけで一々アセットバンドル化する必要ないものまでもアセットバンドル化するのは、大きなサイズになればなるほど、ビルド時間もかかるし、ユーザーがダウンロードする際に負担も増えてしまう。
なので、できるだけアセットバンドルは、小さく分割することで、必要なときに最低限のダウンロードで済ませられる実装が必要である。
参考書籍のp493

・アセット名からロードするアセットバンドルを特定する実装方法
こちらを用いない実装は、アセットとアセットバンドルの対応関係を実装者が覚えておかないといけないので負担が大きい。なので、アセットを指定するだけでアセットバンドルを特定する工夫が必要である。
ポイントは、アセットがキーで、アセットバンドルが値となっているJSONを作成し、それを読み込ませることである。
参考書籍のp499

・アセットバンドル化後、アセット更新し、アセットバンドル化すると、.manifestのCRCとAssetFileHashのHashが変更される。また、アセットバンドルファイルも更新される。
参考書籍のp506

・アセットバンドルが増加すると以下の問題が起きる。

対象 大きくなったときの影響 解決方法
アセットバンドルのサイズ ダウンロードの時間 圧縮設定
キャッシュしているアセットバンドルのサイズ スマホのディスク容量 圧縮設定
メモリ上のサイズ アプリの動作への悪影響 アセットとバンドルの細かなUnload

BuildAssetBundleOptionsで圧縮設定を行える。

BuildAssetBundleOptions アセットバンドル キャッシュしているアセットバンドル
UncompressedAssetBundle 非圧縮 非圧縮
None LZMA LZ4
ChunkBasedCompression LZ4 LZ4

LZ4が、ロード時間、ファイルサイズ、メモリ使用量においておすすめ
参考書籍のp508、参照サイト参照サイト参考動画の21:00

・アセットバンドルをビルドする際に、アセットバンドルする対象が依存しているアセットをすべて抽出し、もしそのアセットにアセットバンドル名が付いていなければ、アセットバンドルに含めてしまう。
その際、アセットバンドル名がついていないアセットが、複数のアセットバンドルから依存されていた場合、そのアセットをアセットバンドルから両方ロードした場合、同じアセットがメモリにあることになるのでメモリの無駄遣いが起きる。
なので、重複するアセットにアセットバンドル名を付ければ、この問題は解決する。
参考書籍のp510

・AssetBundleGraphToolでアセットバンドルを簡単にビルドできる

・AssetBundleBrowserで手軽にアセットバンドルが依存しているものや、.manifestなどが見れる。

・ScriptableObject 、JSONなどもアセットバンドル化できる。Excelはできなかった。
参照サイト

・アセットバンドル名をインスペクターから決めるとき、/を付けると、アセットバンドル化した際フォルダを作成してくれる。
また、インスペクターで名前を決める際、階層化してくれる。
参照サイト

・アセットバンドルの分類
アセットバンドルに全てのアセットを入れるのは、単純であるがいくつか欠点がある。
例えば、ロードする必要のないアセットがある場合、そのアセットがメモリにあるのはメモリの無駄であるし、ロード時間も増加するし、サーバーからダウンロードしてくる時間もかかってしまうし、アセットを新しく更新した場合、必要のないアセットビルドもしなくてはいけなくてビルド時間がかかってしまう。
なので、なるべくアセットバンドルは、グループ化して分けたほうが良い。

①キャラクターや、UI、背景などといった種類別でアセットバンドル化する方法
各アセットバンドルが分かれているため、ある一つの要素に変更を加えた場合、変更されていない余計なアセットをダウンロードしなくても良い。
ただ、アセットがどこで使われるのか把握しておく必要がある。
②類似したアセットをアセットバンドル化する方法
例えば、オーディオの圧縮設定がプラットフォームごとに同じである場合、全オーディオデータを一つのアセットバンドルに入れることで、アセットバンドルをビルドする時間もなくなるため効率が良い
③同時に読み込み・使用されるアセットをアセットバンドル化する方法
例えば、ステージごとにまったく異なるキャラクターや背景やbgmなどの場合で、ステージごとにアセットバンドル化すれば、依存性を考慮しなくてもいいので実装が楽である。
ただ、アセットバンドル同士で、ひとつのアセットに依存関係がある場合、その一つのアセットのために、アセットバンドルをロードしなくてもいけないので、読み込み時間が増えてしまう問題が出てくる。なので、この手法を取る場合は、アセットバンドル同士で依存関係がないようにしないといけない。
この手法では、シーンをバンドルする方法がよく用いられる。
いずれの方法を取るにしても以下を意識するのも重要である。
1.必要でないアセット(アセットバンドル)のダウンロードを防ぐため、頻繁に更新されるオブジェクトとあまり変更されないオブジェクトを別々のアセットバンドルに入れる。
2.依存性の考慮の負担を減らすため同時に読み込まれる可能性の高いオブジェクト同士をまとめる。例えば、特定のモデルとそのテクスチャーとアニメーション、など。
3.メモリの無駄遣いを防ぐため同時に読み込まれることはないであろう 2 つのオブジェクト (例えば、標準アセットと HD アセット) がある場合、それらは別々のアセットバンドルに分けるようにする。
4.A、Bというアセットバンドルに、同じマテリアルがあった場合、普通にこれをビルドすると、無駄にアセットバンドル化していることになり、ビルド時間も増えるし、ファイルサイズ、メモリリソース、読み込み量も増えてしまう。
なので、このマテリアルを別のアセットバンドルにして、それを読み込めば上記の問題は解決する。
ただ、依存関係に注意しないといけないので、不具合が起きてしまう。なので、依存先のアセットバンドルもロードすることを忘れずに。
参照サイト参照サイト

・アセットバンドル内のアセットを読み込む方法
一つのアセットを読み込むのは、LoadAsset ( LoadAssetAsync )
全てののアセットを読み込むのは、LoadAllAssets ( LoadAllAssetsAsync )
サブアセットを読み込むの、LoadAssetWithSubAssets ( LoadAssetWithSubAssetsAsync )
なお、LoadAllAssetsはAssetBundle 内のオブジェクトの大部分またはすべてをロードする必要がある場合にのみ使用するべき。LoadAllAssetsはLoadAssetsを個別に複数回呼び出すよりもわずかに高速。したがって、ロードするアセットの数が多いものの、一度にロードする必要があるアセットバンドルの 66% 未満の場合は、アセットバンドルを複数の小さなバンドルに分割し、 LoadAllAssets を使用することを検討したほうが良い。
また、同期型の読み込みは(Async ついていないのを)は、読み込みが完了するまでメインスレッドを一時停止する。
※サブアセットの読み込みは、アニメーションが埋め込まれた FBX モデルや、内部に複数のスプライトが埋め込まれたスプライト アトラスなどを読み込むときに使用したりする。
参照サイト参照サイト参照サイト参照サイト

// LoadAsset
GameObject gameObject = loadedAssetBundle.LoadAsset<GameObject>(assetName);

// LoadAllAssets 
Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();

// LoadAssetAsync
AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);
yield return request;
var loadedAsset = request.asset;

// LoadAllAssetsAsync
AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request;
var loadedAssets = request.allAssets;

・非同期のアセットの読み込み処理(Resources.LoadAsync や AssetBundle.LoadAssetAsync)の優先度を、全体か個別のアセットかで制御でき、アセットの読み込みの速さを調節できる。
メインスレッドとグラフィックスレッドの優先度は両方とも ThreadPriority.Normalである。これより優先度が高いスレッドは全て、メインスレッドやレンダースレッドを一時停止させ、フレームレートのカクつきを発生させる。これより優先度の低いスレッドの場合はこれは起こらない。メインスレッドと優先度が同等なスレッドの場合は、 CPU が両スレッドに同等の時間の分配を試みる。これは通常、アセットバンドル解凍などの高負荷処理がバックグラウンドで複数のスレッドによって行われている場合に、フレームレートのカクつきを発生させる。
制御方法は、二つある。
1.全体の場合
Application.backgroundLoadingPriority で制御。
2.個別の場合
AsyncOperation.priorityで制御。
参照サイト参照サイト参照サイト

・AssetBundleManifest.GetAllDependenciesと、AssetBundleManifest.GetDirectDependenciesは、文字列配列を割り当てることに注意する。したがって、パフォーマンスが重要な部分では使用しないほうが良い。
参照サイト

・アセットバンドルの不適切なアンロードは、メモリ内におけるオブジェクトの重複やテクスチャの欠落など、望ましくない結果を引き起こす可能性がある。
アセットバンドルの管理について理解すべき最大のポイントは、AssetBundle.Unload(bool) – またはAssetBundle.UnloadAsync(bool) – をいつ呼び出すか(この二つのメソッドは非同期か同期の違い)、そして関数呼び出しに true または false を渡すべきかということである。
基本的に、trueを設定した方が良い。
理由としては、アセットバンドルのメタ情報と、そのアセットバンドル内のアセットがまとめてアンロードされ、再度同じアセットを読み込んだとしてもオブジェクトの重複が発生しないからである。
次に、いつアンロードするかについて述べる。
一つ目は、ステージとステージ間のロード画面などアンロードしても、問題ない場面で行うのが良い。
trueでアンロード行うと、画面に見えているオブジェクトが消えるのでそれでも問題ない場面で行う。
二つ目は、個々のオブジェクトの参照カウントを維持し、アセットバンドルに含まれるオブジェクトがひとつも使用中でない場合にのみ、アセットバンドルをアンロードする。この方法だと、メモリの重複を起こすことなく、アプリケーションのオブジェクトを個々にアンロードおよび再読み込みができる。

もし、falseでアンロードを行うとしたら以下のように行うと良い。
公式ドキュメントを抜粋。

不要なオブジェクトへの参照を、シーン内とコード内の両方で削除し、その後 Resources.UnloadUnusedAssets を呼び出す。

⇒後述のAssetBundle.Unloadを検証で、falseだと、trueに比べてアンロードされないのがあるのが分かったのでtrueの方が良いかも?。おそらく自分のやり方が間違っているかもしれないが。。

非加算的にシーンを読み込む。これにより、現在のシーン内の全てのオブジェクトが破棄されて Resources.UnloadUnusedAssets が自動的に実行されます。

⇒後述のAssetBundle.Unloadを検証を参照。確かに、正しくアンロードされている。

・AssetBundleのインスタンス.Unloadを検証
アセットバンドルからPrefabを生成した。そのPrefabは、テクスチャやアニメーションを参照している。
ProfilerのAssetsのGameObject、Mesh、Texture2D、Material、AnimationClipを注目してみてほしい。
また、Not SavedのAssetBundleのメタ情報も見てほしい。(このメタ情報を破棄しないとメモリを圧迫してしまう可能性がある。参照サイト)
なお、Resources.UnloadAsset(解放したいアセット)で個別にアセット解放できるが、これは今回割愛。
①アンロードしない初期の状態
GameObjectが200、Meshが2、Texture2Dが1、Materialが4、AnimationClipが8
AssetBundleが4
なおNot SavedのMeshやTexture2Dは、今回のPrefabとは関係ない

②AssetBundleのインスタンス.Unload(true) でアンロード
GameObjectが0、Meshが0、Texture2Dが0、Materialが2(今回のPrefabとは関係ない)、AnimationClipが0
AssetBundleが1
正しくアンロードされていることが分かる
また、画面で見えていたキャラクターが消えたが、ヒエラルキーには残っている。

③AssetBundleのインスタンス.Unload(false) でアンロード
GameObjectが200、Meshが2、Texture2Dが1、Materialが4、AnimationClipが8
AssetBundleが1
AssetBundleのメタ情報はアンロードされていることが分かるが、それ以外はアンロードされていない。
また、画面で見えていたキャラクターは消えておらず、ヒエラルキーにも残っている。
なお、アセットバンドルをアンロード後、再度同じアセットをロードするとアセットが重複してまいメモリ使用量が増えるので注意。参照サイト

④AssetBundleのインスタンス.Unload(false) でアンロードしResources.UnloadUnusedAssets()を実行
GameObjectが37、Meshが1、Texture2Dが1、Materialが3、AnimationClipが7
AssetBundleが1
AssetBundleのメタ情報はアンロードされていることが分かるが、それ以外は③ほどアンロードされていない。
また、画面で見えていたキャラクターは消えておらず、ヒエラルキーにも残っている。

⑤AssetBundleのインスタンス.Unload(false) でアンロード後、LoadSceneMode.Singleでシーン読み込み
GameObjectが0、Meshが0、Texture2Dが0、Materialが2(今回のPrefabとは関係ない)、AnimationClipが0
AssetBundleが1
AssetBundleのインスタンス.Unload(true)の結果と同じく正しくアンロードされていることが分かる

⑥AssetBundle.UnloadAllAssetBundles(true)でアンロード
上のAssetBundleのインスタンス.Unloadはアセットバンドルを一個一個にUnloadしないといけないが、AssetBundle.UnloadAllAssetBundlesを使用するとアセットバンドルをまとめてアンロードしてくれる。
GameObjectが0、Meshが0、Texture2Dが0、Materialが2(今回のPrefabとは関係ない)、AnimationClipが0
AssetBundleが0
AssetBundleが0になっている。上の例ではアセットバンドルマニフェストをアンロードしてなかったのでこのような結果になったと思われる。
Assetsはもちろん正しくアンロードされている。
また、画面で見えていたキャラクターは消えておらず、ヒエラルキーにも残っている。

⑦AssetBundle.UnloadAllAssetBundles(false)でアンロード
GameObjectが200、Meshが2、Texture2Dが1、Materialが4、AnimationClipが8
AssetBundleが0
AssetBundleのインスタンス.Unload(false)した場合とほぼ同じであるが、AssetBundleが0になっている。理由は、⑥と同じ。
また、画面で見えていたキャラクターは消えておらず、ヒエラルキーにも残っている。

⑧AssetBundle.UnloadAllAssetBundles(false)でアンロードしResources.UnloadUnusedAssets()を実行
GameObjectが37、Meshが1、Texture2Dが1、Materialが3、AnimationClipが7
AssetBundleが0
結果は⑤とほぼ同じであるが、AssetBundleが0になっている。理由は、⑥と同じ。

・キャッシュシステムを利用するアプリケーションは、Caching.readyでキャッシュシステムが準備できているかどうかを確認してからロードしなくてはならない。これはアプリケーション起動時に一度行っておけば十分である。
ただ、このCaching.readyは、たくさんのアセットバンドルを使用すると、時間がかかってしまう。
参考書籍のp493と508、参照サイト

・UnityWebRequestAssetBundle.GetAssetBundle を使用して、アセットバンドルをサーバからダウンロードする際、それをキャッシュする場所は、ディスクかメモリに分けられる。
メモリにキャッシュすると、ディスクにキャッシュするよりもRAMの使用量が多くなるし、毎回ダウンロードする場合通信量も多くかかってしまう。
アセットバンドルのコンテンツに頻繁かつ迅速にアクセスしたい場合を除いて、ディスクキャッシュを使用すべき。
ちなみに、ディスクキャッシュにする場合、GetAssetBundle に、versionかhashかCachedAssetBundle を指定するとできる。urlだけだとメモリキャッシュになる。
参照サイト参照サイト

・キャッシュの保存場所は、Caching.currentCacheForWriting.pathで取得できる。
なお、iOSは通常iCloudにバックアップされてしまうパスになっているが、Unityが自動的にバックアップ対象外にするフラグを立ててくれる。
参照サイト

・versionかhashかCachedAssetBundleどちらでキャッシュすればいいのか
キャッシュの個別削除、キャッシュの衝突を考えるとCachedAssetBundleの方が良いらしい。
参照サイト

・キャッシュを削除する方法
古いバージョンのキャッシュは削除されません。
また、キャッシュを個別削除するためにハック的に用いられていた「誤ったCRCを指定することで対象のキャッシュが削除される」という挙動も変わっていない。
キャッシュが削除されるパターンを三つほどあげる。
①期限切れによる削除
最後にキャッシュがロードされた時間からexpirationDelayで指定した時間以上経過すると削除される。
期限切れになった瞬間に削除されるのではなく、恐らく何らかのタイミングでチェックが走っているが、どういうタイミングやトリガーでチェックされているのかは分からない。
デフォルトで、12,960,000(150日間)で、それ以上引き延ばすことができない。
Caching.MarkAsUsedでこの日数の制限を引き延ばすことができる。
0を指定すると次のチェックのタイミングで削除される。
設定は保存されないため、起動時に毎回設定してやる必要がある。
検証したところ、プロジェクト内でキャッシュしたのが、全て消えてた。

var cache = Caching.currentCacheForWriting;
cache.expirationDelay = 12960000;

②容量制限による削除
キャッシュの容量がmaximumAvailableStorageSpaceを超えた場合、タイムスタンプが古い順にキャッシュが削除される。
設定は保存されないため、起動時に毎回設定してやる必要がある。
参照サイト

var cache = Caching.currentCacheForWriting;
cache.maximumAvailableStorageSpace = 4 * 1024 * 1024 * 1024; // 4.0GB

③手動での削除
ClearCachedVersionで指定したバージョンのキャッシュ
ClearOtherCachedVersionsで指定したバージョン以外のキャッシュ
ClearAllCachedVersionsで指定したAssetBundleの全てのキャッシュ
ClearCacheで全てのキャッシュ
で削除できる。
引数名はassetBundleNameとなっているが、versionやHashのAPIを使った場合はAssetBundleのファイル名、CachedAssetBundleのAPIを使った場合はCachedAssetBundle.nameが対応するキーとなっている。
ただし、キャッシュの削除は同期処理のため、AssetBundle名とHashのリストを用意して、どこかのタイミングで一括で処理してやるのがおすすめ。
参照サイト

・キャッシュのバージョンハッシュのリストが取得
Caching.GetCachedVersionsで可能
アセットバンドル名と、listを指定する
参照サイト

List<Hash128> listOfCachedVersions = new List<Hash128>();
Caching.GetCachedVersions("players", listOfCachedVersions);
foreach (Hash128 hash in listOfCachedVersions)
{
Debug.Log(hash.ToString());
}

・指定したバージョンがキャッシュされているか確認
Caching.IsVersionCachedで可能
アセットバンドル名と、ハッシュを指定する
キャッシュされていればtrue、していなければfalse
参照サイト

private string hash = "148c68456140b0f4b2a83d6bd6a4ccec";
bool a = Caching.IsVersionCached("players", Hash128.Parse(hash));

・キャッシュを複数種類に分ける
今までキャッシュは一つのディレクトリに全てまとめられていましたが、Unity2017.1から複数に分けることができるようになった。
これにより、例えば高画質モードと低画質モードで異なるAssetBundleを利用するといった時に、キャッシュの切り替えを簡単に行う事ができる。
参照サイト

・CRC をチェックすることで、アセットバンドルのデータがビルド後に破損したり改ざんされたりしていないことを確認できる。
CRCチェックするためには、UnityWebRequestAssetBundle.GetAssetBundleの引数にアセットビルド時に計算されるCRCを指定し、その値と保持している値が違うならエラーとして、ダウンロードを停止する。
キャッシュからロードしていた場合、そのキャッシュは削除される。
CRCの指定方法は、ビルド時にAssetBundle名とCRCの対応表を書き出しておき、それを読み込んで指定するのが一般的である。
ちなみに、0を指定すると、CRCチェックが行われない。
参照サイト参照サイト

・Caching.compressionEnabled =trueにすると、アセットバンドルをディスクキャッシュする際、LZ4 圧縮を行う。
なお、デフォルトは、Caching.compressionEnabled =trueになっている。
一方で、Caching.compressionEnabled =false の場合アセットバンドルをディスクキャッシュする際圧縮を適用しない。
参照サイト

・UnityWebRequestAssetBundle.GetAssetBundleでURLを指定して、アセットバンドルをダウンロードしてくる際は、HTTPSを使用した方が良い。悪意のある中間者攻撃に対して脆弱であるため

参照サイト

・アセットバンドルは、二分岐検索をするので、1アセットバンドル内のアセットの数が増えれば増えるほど、単体アセットの検索コストは増える。
参考動画の2:40

・ダウンロードサイズを表示する方法
App Storeの審査ガイドラインによって、追加リソースをDLする時には事前にダウンロードサイズを開示する義務があると定められている
参考動画参照サイト

・AppStoreが150MB、GooglePlayが100MBがアプリの上限で、これを超えるとWifiでしかダウンロードできなくなる。

・AssetBundleに名前を、Prefabのみの設定と、全アセットだとPrefabのみの方がロードが1.5倍から2倍速くなる。
参照サイト

・シーンが入っているアセットバンドルととキャラクターが入っているアセットバンドルが別々で、これらを使用したい場合
参照サイト

・Monoメモリは、プログラマーがC#で書いたものが管理されているメモリ領域。GCによって不要なものは破棄される。
UnityNativeメモリは、C#管理外の、Unityのネイティブ実装部分が使うメモリ領域。アセットなどはここに管理され、明示的にリソースをアンドするか、Resources.UnloadUnusedAssetsなどを呼び出す必要がある。
参照サイト

・アセットバンドルから取り出したアセットを2回呼び出すと、2回目は1回目で呼び出したものを参照する。
なので、ディスクIOが発生しないし、アセットがメモリを重複しない。

var assetBundleA = AssetBundle.LoadFromFile(path);
var textureA = assetBundleA.LoadAsset<Texture2D>("textureA");
var textureA2 = assetBundleA.LoadAsset<Texture2D>("textureA");
[参照サイト](https://qiita.com/k7a/items/d27640ac0276214fc850#assetbundle%E3%82%92%E3%83%AD%E3%83%BC%E3%83%89%E3%81%97%E3%81%9F%E6%99%82%E3%81%AE%E5%AE%9F%E9%9A%9B%E3%81%AE%E5%86%85%E9%83%A8%E5%8B%95%E4%BD%9C%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)

・AssetBundle.Unload(true or false)を呼び出した後は、アセットバンドルからアセットを取り出せなくなる。
参照サイト

・ロード済みのアセットバンドルをロードしようとすると、エラーになる
これを解消するするためには、未ロードならWebからDLしてきてロードし内部のDictionaryに格納、ロード済みならDictionaryに格納してあるAssetBundleオブジェクトを返す、というような実装が必要になる。

var assetBundle = AssetBundle.LoadFromFile(path);
// 2回目にロードするとエラー
var assetBundle2 = AssetBundle.LoadFromFile(path);

参照サイト

・AssetBundle同士に依存関係が存在する場合
この場合、LoadAssetする前に依存しているAssetBundle(のメタ情報)を全てロードしておく必要がある。ロードする順序を特にない。
これを実現するためには、下記のように行うと良い。
※なお、こちらのやり方は、アセットを指定しただけで、対象のアセットバンドルを読み込む実装も考慮されている。例えば、1などがまさにそう。(なぜこのような実装するかというと、アセットを読み込みたいとき、それに対応しているアセットバンドルを覚えておかないといけないので、アセット名を指定するだけ対応しているアセットバンドルをロードさせたいため行っている。)
1.アセットバンドルをビルド後、.manifestファイルから、アセットとアセットバンドルの対応JSONを自前で作成する。
2.マニフェストバンドルをサーバーからダウンロードし、ロ―ド。
3.作成したJSONをサーバーからダウンロードし、FromJsonでパース。
4.アセット名を指定して、依存元の対象アセットバンドル名を取得。
5.依存元のアセットバンドルのマニフェストファイルから、依存先のアセットバンドルをダウンロードまたはキャッシュし、ロードする。
6.依存元のアセットをロードする。
参考書籍のp500

・他のロード済みAssetBundleから依存されているAssetBundleが不用意にUnloadされないようにする仕組みも必要となる。
これを実現するには、Load時にカウントを+1、Unload時に-1し、カウントが0になった段階で実際にUnloadを呼び出すような実装が必要

// AがBとCに依存している
var assetBundleA = AssetBundle.LoadFromFile(pathA);
var assetBundleB = AssetBundle.LoadFromFile(pathB);
var assetBundleC = AssetBundle.LoadFromFile(pathC);

// Aからprefabをロード
var prefab = assetBundleA.LoadAsset<GameObject>("prefab");

// Cからテクスチャをロード
var textureC = assetBundleC.LoadAsset<Texture2D>("textureC");
// 不要だと思ってUnloadする
assetBundleC.Unload(false);

// 再びロードしようとするとエラーになる
var prefab = assetBundleA.LoadAsset<GameObject>("prefab");

参照サイト

・CDNの反映遅延への対処
下記サイトに対処法が記載されているが、アセットバンドルを更新しようとしている場合はサーバへリクエストを行えないようにしてアップデート中ですみたいなUI出せば良さそうな気がする。
参照サイト

・アセットバンドルにパッチ適用する方法
参照サイト

・カスタムダウンローダー作成する際意識すべきこと
参照サイト

・AssetBundle Variants
同じアセットに対して「日本語版・英語版」や「HD版・SD版」など複数の種類を用意したい場合に使う機能である。
これをAssetBundle Variantsを使用せずに実現しようとすると、反映漏れが起きたりする。
ただ、この、AssetBundle Variantsはパフォーマンスや色々な面で使用するのは避けた方が良い。
また、Addressable Asset Systemは、AssetBundle Variantsが非対応。

Addressable Assets System

・公式&スクリプトマニュアル
参照サイト参照サイト参照サイト

・AssetBundleの分割単位
以下のGroup構成で検証し、Analyzeも確認。(TestのGroupで実験。ほかのGropuはデフォルト)

Bundle Modeが、Pack Togtherの場合
Gropu単位で.bundleファイルが作成され、また、Gropuの中でもsceneとassetsで分けられていることが分かる。


Bundle Modeが、Pack Separatelyの場合
Gropu単位で.bundleファイルが作成されるが、Gropuの中でアドレスごとに分けられていることが分かる。


Bundle Modeが、Pack Together By Labelの場合
Gropu単位で、かつsceneとassetsで、かつラベルごとに分けられる。
ラベルを指定していないものは、Gropu単位で、かつsceneとassetsでまとめられる。


参照サイト

・Analyzeでアセットの重複をチェックし修正、またはアセットバンドル内のアセットの構成をチェック。
アセットバンドル同士のアセット重複、アセットバンドルとResourcesフォルダによりアプリに含まれるアセット重複、アセットバンドルと組み込みシーン(Build Settingsに登録されているシーン)のアセット同士重複などをチェックし、修正してくれる
参照サイト

参照サイト
・AssetReferenceを使用すると、ソース上に読み込みたいアセットを指定せずに、インスペクター上で設定を行える。
また、AssetReferenceの型を制限できる
AssetReferenceAtlasedSprite
AssetReferenceGameObject
AssetReferenceSprite
AssetReferenceT<TObject>
AssetReferenceTexture
AssetReferenceTexture2D
AssetReferenceTexture3D
AssetLabelReference
参照サイト

・Fast Mode、Virtual Mode、Packed Mode
Fast Modeは、AssetBundleを使用ぜずにアセットをAssetDatabaseでロードする。Event Viewerで得られ理情報が少ない。例えば、アセットがどのアセットバンドルに入っているかなどがわからない。ただ、読み込み速度が速い。
Virtual Modeは、AssetBundleを使用ぜずにアセットをAssetDatabaseでロードする。ただ、Event Viewerで得られる情報はFast Modeよりも多い。例えば、アセットがどのアセットバンドルに入っているかなどが分かる。ただ、Fast Modeより読み込み速度が重い。
また、ロードの速度制限機能等、AB利用時の動作に近い感じで使える。
Packed Modeは、アセットバンドルをロードし、アセットをロードする。エディターでと言うよりは、実機での動作を確認することを期待するもの。
AssetBundle.LoadFromFileAsyncやUnityWebRequestAssetBundle.GetAssetBundle(URI uri)を使ってAssetBundleをロードし、アセットを読み込んでいる。
ちなみに、
Fast Mode、Virtual Mode、Packed Modeが名前変更されていた。
Fast Mode⇒Use Asset Database (fastest)
Virtual Mode⇒Simulate Groups (advanced)
Packed Mode⇒Use Existing Build (requires build groups)
参照サイト参照サイト

・外部のサーバからアセットバンドルをダウンロードし、それとアセットをロードする方法
参照サイトを参照。

・ラベルを付けて一括ロードや、事前ロードを行う。
事前ロードを行うことで、パフォーマンスが改善できる場合がある。
たとえば、プレーヤーがゲームを初めて起動したときに重要なコンテンツをダウンロードして、ゲーム プレイの途中でコンテンツを待つ必要がないようにすることができる。


参照サイト参照サイト参照サイト参照サイト参考動画の18:30

・コンテンツカタログ
コンテンツカタログとは、アセットの情報などを記載したもの。
Build Remote Catalogにチェックを入れ、Use Existing Buildでビルドすると生成される。
またグループごとにコンテンツカタログとbundleファイル生成される。
そしてアセットに変更を加えると、新たにコンテンツカタログとbundleファイル生成される。

このコンテンツカタログと、bundleファイルをサーバにアップロードし、ダウンロードすると、
ローカルにキャッシュがされていない場合は、ローカルにキャッシュし、している場合はローカルにキャッシュを行わない。
また、設定したグループごと、そして、差分のあったものでキャッシュのフォルダが分けられる。

グループごとに分けられる(フォルダ名は適当?)

キャッシュの差分ごとにフォルダが分けられる(.hashファイルの中身がフォルダ名になる)

参照サイト

・依存関係を気にしなくても良い
PrefabとPrefabが依存しているテクスチャとマテリアルを用意し、依存関係を気にしなくてもよいか実験してみた

ビルド後したら以下が生成された

ビルドされたのサーバーにアップロードし、ダウンロードしてみた
以下がソース
PrefabのCubeだけをロードしている

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class Test : MonoBehaviour
{
   private AsyncOperationHandle<GameObject> m_CubeHandle;
   void Start()
   {
   // Cubeを生成
       m_CubeHandle = Addressables.InstantiateAsync("Cube", new Vector3(0, 0, 0), Quaternion.identity);
   }

}

正しくPrefabが生成され、依存しているマテリアルやテクスチャをロードしなくても良いことが分かる。

ちなみに、キャッシュした際のディレクトリ
.bundleファイルごとにフォルダが作成されている


参照サイト

・AssetBundle の依存関係①
依存元は、依存先をアンロードしなくても依存元をアンロードすれば、依存先はアンロードされる。
上のコンテンツカタログと同じサンプルを用いる。
以下がコードである。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class Test : MonoBehaviour
{
   private AsyncOperationHandle<GameObject> m_CubeHandle;
   private bool flg = true;
   void Start()
   {
   // Cubeを生成
       m_CubeHandle = Addressables.InstantiateAsync("Cube");
   }

   void Update()
   {
       if (Input.GetMouseButtonDown(0)) {

           if (flg) { 
       //依存元をアンロード
           Addressables.ReleaseInstance(m_CubeHandle);
           flg = false;
           }
       }
   }

}

アンロードする前。依存元、依存先のテクスチャ(haru)とマテリアル(New Material)がAssetsにあることが分かる。

アンロード後。依存元、依存先のテクスチャ(haru)とマテリアル(New Material)が、GameObject(Cube)がAssetsからアンロードされていることが分かる。

また、話は逸れるがNot SavedとScene Memoryについてもアンロードされているか調べてみた。(これは使用するアンロードメソッドによって、がNot SavedとScene Memoryがアンロードされるされないはあるかも?)
アンロードする前のNot Saved。AssetBundelのメタ情報があることが分かる。

アンロード後のNot Saved。AssetBundelのメタ情報がアンロードされていることが分かる。

アンロードする前のScene Memory。生成したCubeがあるのが分かる。

アンロード後のScene Memory。生成したCubeがアンロードされているのが分かる。(テクスチャはアンロードされていない?)
また、シーンにあったCubeが消えていた

参照サイト参考動画の4:10

・AssetBundle の依存関係②
AssetBundle の依存関係①では、「依存元は、依存先をアンロードしなくても依存元をアンロードすれば、依存先はアンロードされる」ということを述べたが、これは一つ注意すべきことが必要がある。
例えば、以下のGropu構成があったとする。
Quadは、New Material1とmakotoに依存している。一方で、Cube1はどこにも依存していない。

このときに、 Addressables.InstantiateAsync("Cube1")でロードした場合、Cube1はどこにも依存していないのにInuのアセットバンドルもメモリに読み込まれてしまう。

なので、このような無駄なロードすることでメモリ使用量がネックになる場合は、依存関係をなくすため同じアセットバンドルにまとめるなどして依存関係をなくすということも検討をすべき。
参照サイト

・基本的なロード
アドレス名を指定して、LoadAssetAsyncを呼び出す。
Addressables.LoadAssetだと古い形式らしい。
LoadAssetAsyncは非同期で、同期のロードは、こちらを参照。

var handle = Addressables.LoadAssetAsync<GameObject>("Example");
handle.Completed += op => {
   if (op.Status == AsyncOperationStatus.Succeeded) {
       Instantiate(op.Result);
   }
};

awaitも可能

void Start()
{
 AsyncSample1();
}

async void AsyncSample1()
{
 var handle = Addressables.LoadAssetAsync<GameObject>("Cube");
 await Task.Delay(3000);
 await handle.Task;
 if (handle.Status == AsyncOperationStatus.Succeeded)
 {
   Instantiate(handle.Result);
 }
}

コルーチンも可能

void Start()
{
 StartCoroutine(CoroutineTest());
}

IEnumerator CoroutineTest()
{
 var handel = Addressables.LoadAssetAsync<GameObject>("Cube");
 yield return new WaitForSeconds(3);
 if (handel.Status == AsyncOperationStatus.Succeeded)
 {
   Instantiate(handel.Result);
 }

}

Addressables.Release(ロードした際の戻り値)を用いるとアンロードできる。
実際に検証してみた。

private AsyncOperationHandle<GameObject> t_CubeHandle;
void Start()
{
t_CubeHandle = Addressables.LoadAssetAsync<GameObject>("Cube");
t_CubeHandle.Completed += op =>
{
   if (op.Status == AsyncOperationStatus.Succeeded)
   {
   Instantiate(op.Result);
   }
};
}

void Update()
{
if (Input.GetMouseButtonDown(0))
 {
 Addressables.Release(t_CubeHandle);
 }
}

アンロードする前のNot Saved。

アンロード後のNot Saved。AssetBundleがアンロードされている。

アンロードする前のAssets。

アンロード後のAssets。Texture2D(haru)とMaterial(New Material)とGameObject(Cube)がアンロードされている。

アンロードする前のScene Memory。

アンロード後のScene Memory。特に変化がない。

参照サイト

Addressables.ReleaseはAssetsのアセットとNot SavedのAssetBundleのメタ情報をアンロードすることが分かる。
また、生成されたCubeはシーン上でシェーダーエラーのピンク色で表示されていた。
なので、Destoryすれば生成したオブジェクトは消えるし、Scene Memoryで変化がなかったところがアンロードされる。
参照サイト

・Instantiate生成したオブジェクトにNull代入すると、アンロードされる。

private AsyncOperationHandle<GameObject> t_CubeHandle;
private GameObject gameObject;
void Start()
{
t_CubeHandle = Addressables.LoadAssetAsync<GameObject>("Cube");
t_CubeHandle.Completed += op =>
{
   if (op.Status == AsyncOperationStatus.Succeeded)
   {
   gameObject = Instantiate(op.Result);
   }
};
}

void Update()
{
if (Input.GetKeyDown(KeyCode.A))
   {
     Addressables.Release(t_CubeHandle);
   }
   
if (Input.GetKeyDown(KeyCode.B))
   {
     Destroy(gameObject);
   gameObject = null;//Destoryより前にnullを代入するとDestoryできない
   }
}

nullを代入しない

nullを代入。NotSavedとAssetsでいくつかアンロードされていることが分かる。

・Destoryした後でも、 Addressables.Releaseでもアセットをアンロードできる。

・GameObjectをロードしてInstantiateする

var handle = Addressables.LoadAssetAsync<GameObject>("Example");
await handle.Task;
Instantiate(handle.Result);
//まとめて記載すると、、、
↓
var handle = Addressables.InstantiateAsync("Example");

Addressables.InstantiateAsyncのロード方法で、アンロードする際は、 Addressables.ReleaseInstanceを使用する。アンロードの詳しい内容は、上で記述している、AssetBundle の依存関係①を参照。
参照サイト

・サブアセット全てと特定のサブアセットをロード

//サブアセット全て
Addressables.LoadAssetAsync<IList<Mesh>>("FbxExample");
//特定のサブアセット
Addressables.LoadAssetAsync<Mesh>("FbxExample[skin]");

参照サイト

・シーンをロードする
デフォルトだとSingleで、Build Settingsにシーンを登録する必要ない。
また、Addressables.LoadSceneだと古い形式らしい。

// Singleでシーンをロード
Addressables.LoadSceneAsync("TestScene");
// Additiveでシーンをロード
Addressables.LoadSceneAsync("TestScene", LoadSceneMode.Additive);

アンロードは、Addressables.UnloadSceneAsync(シーンをロードした際の戻り値)で行う。
また、Adressables.UnloadSceneだと古い形式らしい。
一応正しくアンロードされているか検証してみた。

AsyncOperationHandle<SceneInstance> scene;
void Start(){
scene  = Addressables.LoadSceneAsync("TestScene", LoadSceneMode.Additive);
}

void Update()
{
 if (Input.GetMouseButtonDown(0))
 {
   Addressables.UnloadSceneAsync(scene);
 }
}

アンロードする前のNot Saved。

アンロード後のNot Saved。AssetBundleがアンロードされている。

アンロードする前のAssets。

アンロード後のAssets。TestSceneのTexture2D(makoto)とMaterial(New Material 1)がアンロードされている。

アンロードする前のScene Memory。

アンロード後のScene Memory。TestSceneのGameObject(Sphere)がアンロードされている。

また、Singleでシーンをロードすると、ロードする前のシーンのものは自動的にアンロードされる
参照サイト参照サイト

・アセットがアンロードされるタイミング
アセットがアンロードされるタイミングは、アセットバンドルの参照カウントがゼロになってから。
アセットバンドルは参照カウントをもっており、アセットをロードすると+1、アンロードされると-1され、0になるとそのアセットバンドル内のアセット全てはアンロードされる。
なので、あるアセットをアンロードしても、アセットバンドルの参照カウントが0にならないとそのアセットはアンロードされない。
今回はそれを検証してみる。以下ソースコード。
CubeとQuadが同じGroupで、それぞれ違うマテリアルとテクスチャを参照している。

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;


public class Test : MonoBehaviour
{
   bool cubeflg = true;
   bool quadflg = true;
   AsyncOperationHandle<GameObject> handlecube;
   AsyncOperationHandle<GameObject> handlequad;
   void Start()
   {

       handlecube = Addressables.InstantiateAsync("Cube");
       handlequad = Addressables.InstantiateAsync("Quad");

   }

   void Update()
   {
       if (Input.GetKey(KeyCode.A))
       {
           if (cubeflg) { 
           Addressables.ReleaseInstance(handlecube);
           Debug.Log("Cubeをアンロードした");
           cubeflg = false;
           }
       }

       if (Input.GetKey(KeyCode.S))
       {
           if (quadflg) { 
           Addressables.ReleaseInstance(handlequad);
           Debug.Log("Quadをアンロードした");
           quadflg = false;
           }
       }

       if (Input.GetKey(KeyCode.W))
       {
           Resources.UnloadUnusedAssets();
           Debug.Log("アセットをアンロードした");

       }

   }

}

Aキーを押したとき。Cubeをアンロードしたがまだ参照先のテクスチャがメモリにある。

Sキーを押したとき。参照先のテクスチャがアンロードされているのが分かる。

ただ、Resources.UnloadUnusedAssets()を呼べば、アセットバンドルの参照カウントが0になっていなくてもアセットはアンロードされる。
Aキーを押下後、Sキーを押下。参照先のテクスチャがアンロードされているのが分かる。
ただ、このメソッドはそこそこ負荷が高いので注意。

参照サイト

・EventViewerで、ロード・アンロードした際の参照カウントを視覚化し、参照数も見る
参照カウントが0になるとアセットがメモリから解放される。チューニングの際は参照カウント0になることを目標とする。
参照サイト参照サイト参照動画の17:15

・Addressables.LoadSceneAsyncかAddressables.InstantiateAsyncの違い
Addressables.LoadSceneAsyncとAddressables.InstantiateAsyncの速度比較
処理負荷とGCAllocがAddressables.LoadSceneAsyncの方が断然低いことが分かる。

Addressables.InstantiateAsyncでアンロードする際は、Addressables.ReleaseInstance()を使用するがAssetsとNotSavedのものはもちろん、SceneMemoryのものもアンロードしてくれるため使い勝手が良い。(生成したインスタンスは消える)
Addressables.LoadSceneAsyncの場合は、Addressables.Release()でアンロードするがAssetsとNotSavedのものはアンロードするが、SceneMemoryのものはアンロードしてくれない。(生成したインスタンスは消えない。ピンク色になる)
この場合SceneMemoryのものはアンロードするには、Destoryしnullを代入する必要がある。
また、参照サイトによるとAddressables.InstantiateAsync()は以下の特徴を持つ。

またAddressables.InstantiateAsync()で生成した場合、生成されたインスタンスに関連するリソースの寿命は所属するシーンに依存します。
つまり、Addressables.InstantiateAsync()で生成されたGameObjectが所属するシーンが破棄されると、
それに連動してAddressables.ReleaseInstance()と同等のリリース処理が自動的に行われます。

参照サイト

・アセットバンドルのサイズをAddressables.GetDownloadSizeAsyncで取得。
参照サイト参照サイト

・Addressables.LoadResourceLocationsAsync()とAddressables.GetDownloadSizeAsync()は、
アンロードするまでデータを保持し続ける。
なので、不要になった時点でAddressables.Release()する必要がある。
実際にやってみたが確かにアンロードしないとデータを保持し続けていた。
参照サイト

・Hosting Serviceでアセットバンドルをローカルサーバからダウンロードし利用
参照サイト

・コンテンツカタログのファイル名をタイムスタンプではなく、任意のファイル名にする。
参照サイト

・独自のダウンロード・ロード処理を実装
独自ではないUnity標準の機能を使用すると、暗号化といった独自処理を実装できない。
今回はそういった独自処理を紹介
参照サイト参照サイト

・Editor拡張で、グループを作成・削除、グループのアセットを作成・移動・削除、ラベルの追加・削除、アセットにラベルを適用・削除する
参照サイト

・アドレスの名前を自動化するUnity Addressable ImporterとEZAddresser
参照サイト参照サイト

・Unique Bundle IDを使用して、メモリにあるアセットを更新する
ただ、デメリットのほうが大きい
参照サイト

・Update Restrictionをいじって、更新ダウンロードアセットと組み込みアセットのワークフローを構築
更新ダウンロードアセットとは、アプリと一緒にビルドされないアセットで、いわゆる更新されていればサーバからダウンロードしてくるものである。一般的なアセットバンドルのイメージで良い。
組み込みアセットとは、アプリをビルドした際に一緒に組み込まれるアセットである。
これらを運用するにあたっていくつか気をつけないといけないことがある。
参照サイトによると、

一点目として、リモートコンテンツカタログを同名で更新することが挙げられます。
使用するリモートコンテンツカタログの名前はプレイヤーに組み込まれているため、同名で中身だけ更新する必要があります。
二点目として、組み込みリソースの意図していない更新による不具合を防ぐための仕組みが必要です。
ダウンロードコンテンツだけ更新したつもりが実は組み込みリソースも更新されていて、
うまく更新が行われずに慌ててアプリもアップデートする・・といった事態を防がなくてはいけません。

確かに、リモートコンテンツカタログは同じものを使用したほうが管理しやすいし、容量も喰わない。また、アプリのアップデートするという手間も生じてしまうため気を付けないといけない。
これらを実現するための方法を今回述べる。
そのためにはUpdate Restriction(Can Change Post ReleaseとCannot Change Post Release)とUpdate a Previous Buildが肝である
今回は、DownloadBundleをCan Change Post Release、KumikomiをCannot Change Post Releaseを設定し説明していく。(また、New Buildで事前ビルドしておいた)

①更新ダウンロードアセット(DownloadBundle)
アセットを更新したとする。
Update Restrictionの設定が、Can Change Post Releaseで、Update a Previous Buildでビルドすると新たに.bundleファイルが生成される。
この際、コンテンツカタログは新たに新規作成されないが、ハッシュ値は更新される。
なのでこれをサーバーにアップロードし、ダウンロードすると新たにキャッシュされる。
カーソル当たっている箇所が更新・作成されたのが分かる。

New Buildでビルドすると、新たに.bundleファイルが生成されるし、コンテンツカタログも新たい生成される。(Player Version Override設定していない場合)
②組み込みアセット(Kumikomi)
Update Restrictionの設定が、Cannot Change Post Releaseで、Update a Previous Buildでビルドすると新たに.bundleファイルが生成されない。
この際、コンテンツカタログは新たに新規作成されないが、ハッシュ値は更新される。
ただ、組み込みアセットなので、ハッシュ値が更新されたからといって、ローカルにキャッシュなどしない。

こちらも①同様に、New Buildでビルドすると、新たに.bundleファイルが生成されるし、コンテンツカタログも新たい生成される。(Player Version Override設定していない場合)
また、Check for Content Update Restrictionsで、Cannot Change Post Releaseを設定したアセットが変更されていた場合、画面にそのアセットを表示してくれる。
参照サイト

・組み込みアセットの取り扱い(チュートリアルとか)


参照動画の33:57

・ダウンロード時のエラーをハンドリング

ハンドリングできるエラー一覧 和訳
Request aborted リクエスト中止
Unable to write data データ書き込み不可
Out of memory メモリ不足
No Internet Connection インターネット接続不可
Encountered invalid redirect (missing Location header?) 無効なリダイレクトに遭遇(Locationヘッダーの欠落?)
Cannot modify request at this time 現時点ではリクエストを変更できません
Unsupported Protocol サポートされていないプロトコル
Destination host has an erroneous SSL certificate 宛先ホストに不正なSSL証明書があります
Unable to load SSL Cipher for verification 検証のためSSL暗号をロードできません
SSL CA certificate error SSL認証局証明書エラー
Unrecognized content-encoding 認識できないコンテンツエンコード
Request already transmitted リクエストはすでに送信されました
Invalid HTTP Method 無効なHTTPメソッド
Header name contains invalid characters ヘッダー名に不正な文字が含まれています
Header value contains invalid characters ヘッダー値に不正な文字が含まれています
Cannot override system-specified headers システムで指定されたヘッダーを上書きできません
Backend Initialization Error バックエンド初期化エラー
Cannot resolve proxy プロキシを解決できません
Cannot resolve destination host 宛先ホストを解決できません
Cannot connect to destination host 宛先ホストに接続できません
Access denied アクセス拒否
Generic/unknown HTTP error 一般的な/不明な HTTP エラー
Unable to read data データ読み込み不可
Request timeout リクエストタイムアウト
Error during HTTP POST transmission HTTP POST送信中のエラー
Unable to complete SSL connection SSL接続が完了しません
Redirect limit exceeded リダイレクトの制限を超えました
Received no data in response 応答としてデータを受信していない
Destination host does not support SSL 宛先ホストがSSLをサポートしていません
Failed to transmit data データ送信に失敗しました
Failed to receive data データ受信に失敗
Login failed ログインに失敗しました
SSL shutdown failed SSLシャットダウンに失敗しました
Redirect limit is invalid リダイレクト制限が無効です
Not implemented 実装されていません
Data Processing Error, see Download Handler error データ処理エラー、ダウンロードハンドラーエラーを参照
Unknown Error 不明なエラー
参照サイト参照サイト

・Simply Addressables Namesを使用すると、アドレス名を短縮できる

アセットを右クリックして、Simply Addressables Namesを選択

・UnityのものでないCRIなどの対応

参照動画の29:06

・nested Prefabを使用した際の困ったこと

参照動画の44:50

・スプライトとスプライトアトラスを表示する方法
詳しい方法は参照サイト参照。
参照サイト
参照サイトで、スプライトアトラスとスプライトアトラスの中身を別々にロードするのか、まとめてロードするのかパフォーマンスについて記載されているがその検証をしてみた。
ただ、別々のほうはインスペクターでアドレスを文字列で指定するため使い勝手が悪い。
まとめてのほうは、AssetReferenceが使用できる。

確かに、別々にロードするほうがパフォーマンスが良い

・スプライトアトラスをアプリには含めず、アセットバンドルで使用する方法
参照サイト

・Compress Local Catalogで、コンテンツカタログを圧縮し、サイズをを小さくできる。
ただ読み込みに時間がかかる可能性がある。
参照サイト

・Optimize Catalog Sizeでコンテンツカタログのサイズを小さくする。ただロードする時間が増える可能性がある。
実際に、検証してみた。

Optimize Catalog Sizeオフ
6.99 KB (7,166 バイト)
Optimize Catalog Sizeオン。確かに減ってはいるが微々たるもの。もっとファイルサイズが大きくなれば変わるかも?
6.95 KB (7,123 バイト)
参照サイト

・Disable Catalog Update on Startupで、Addressables が初期化されるときに、更新されたリモートカタログの自動チェックを無効にする。
初期化するタイミングは、最初に Addressable をロードしたとき、または Addressable API に対して別の呼び出しを行ったときに初期化される。
なので、無効にした場合手動でリモートカタログをチェックする必要がある。
参照サイト参照サイト

・Custom certificate handlerで、独自SSLでhttps通信を可能にする
参照サイト参照サイト

・Max Concurrent Web Requestsで、サーバーへリクエストするリクエスト数を制限する。
フォーラムを見ると、デフォルトの500だとエラーが起きる可能性があるし、ダウンロードがスムーズにできない問題があった。
参照サイト参照サイト

・Catalog Download Timeoutで、サーバにあるコンテンツカタログをhttp(https)通信する際のタイムアウト時間を設定。
デフォルトは、0になっているのでタイムアウトがない。
参照サイト参照サイト

・Ignore Invalid/Unsupported Files in Buildで、無効なファイルや Unity がサポートしていないファイルを無視する
参照サイト

・Contiguous Bundlesで、アセットのロード時間を改善する
ただ、Addressables 1.12.1 以前だとオフにしたほうが良い
参照サイト参照サイト

・Non-Recursive Dependency Calculationで、アセットに依存関係がある場合に、ビルド時間が短縮され、ランタイム メモリのオーバーヘッドが削減される。
(https://docs.unity3d.com/Packages/com.unity.addressables@1.19/manual/AddressableAssetSettings.html#catalog)

・Asset Bundle Compressionで、グループ内の圧縮形式を変更する。
LZ4が、ロード時間、ファイルサイズ、メモリ使用量においておすすめ
参照サイト参照サイト参考動画の21:00、参照サイト

・Include In Buildで、グループ内のアセットをアセットバンドルにするのかしないのか決める。

Include In Buildオン

Include In Buildオフ

参照サイト

・Force Unique Providerで、独自プロバイダーの実装でグループ間でそのインスタンスを共有するかしないかを設定
フォーラムによると、

ユース ケースは、カスタム プロバイダーがあり、そのプロバイダーが正しく機能するためにインスタンス化する必要がある、非常に特殊なケースです。

参照サイト参照サイト

・Use Asset Bundle Cacheで、グループ内のアセットバンドルをキャッシュするかしないかの設定
グループのキャッシュを無効にすると、そのグループ用に作成されたリモート バンドルは、ダウンロードされると、アンロードするか、アプリケーションが終了するまで、メモリに保存される。次にアプリケーションがバンドルをロードするときに、Addressables はバンドルを再度ダウンロードする。
参照サイト参照サイト

・Asset Bundle CRCで、グループ内の.bundleファイルをCRCチェックを行うか行わないか決める。
ただ、CRCを無効にすると速度が速くなる場合もある。
参照サイト参照サイト

・Request Timeoutで、サーバにある.bundleファイルをhttp(https)通信する際のタイムアウト時間を設定
デフォルトは、0になっているのでタイムアウトがない。
参照サイト

・Http Redirect Limitで、サーバにある.bundleファイルをhttp(https)通信する際のリダイレクトの数。無制限の場合は -1 に設定。
参照サイト

・Retry Countで、サーバにある.bundleファイルをhttp(https)通信する際の失敗した時のリトライ回数を設定
参照サイト

・Include Addresses in Catalogで、グループ内のアセットをアドレス指定の読み込みにするかしないのか。しない場合、オフにすることでコンテンツカタログのサイズが小さくなる
オフでアドレス指定でロードしたらエラー出た。
参照サイト

・Include GUIDs in Catalogで、グループ内のアセットをGUIDで読み込ませるのかしないのか設定。AssetReferences または GUID 文字列を使用して読み込ませない場合、オフにすることでコンテンツカタログのサイズが小さくなる。
オフでAssetReferencesでロードしたらエラー出た。
参照サイト

・Include Labels in Catalogで、グループ内のアセットをラベルで読み込ませるのかしないのか設定。ラベルで読み込まない場合、オフにすることでコンテンツカタログのサイズが小さくなる。
オフでラベルでロードしたらエラー出た。
参照サイト

・Internal Asset Naming Modeで、グループ内のアセットの格納先のパス指定を設定。
開発時にはFull Pathで、リリース時にはDynamicがおすすめ。
Full Pathだとアセットの格納先が分かりやすい。
Dynamicだと、AssetBundle とカタログのデータが削減され、実行時のメモリ オーバーヘッドが減少する。
Full Path

Filename

GUID

Dynamic

参照サイト参照サイト

・Cache Clear Behaviorで、グループ内のアセットの古いキャッシュを消すタイミングを制御する。
Clear When Space is Needed in Cacheでストレージの空きがなくなったタイミングで消去。
Clear When New Version Loadedで新しいキャッシュをキャッシュされると古いのを削除。
Forum見る限り、基本的にはClear When New Version Loadedがいいらしい。
ただ、キャッシュの処理は同期処理なので、パフォーマンスがネックの場合はClear When Space is Needed in Cacheの使用も検討したほうがいいかも。
参照サイト参照サイト参照サイト

・Bundle Naming Modeで、グループ内の.bundleのファイル名を変える。
セキュリティ的には、Use Hash of AssetBundle、Use Hash of Filenameがよさそう。

Filename

Append Hash to Filename

Use Hash of AssetBundle

Use Hash of Filename

参照サイト参照サイト

・Asset Load Modeで、グループ内のアセットを個別にロードするのか、Groupのアセットをすべてロードするのか決める。
Requested Asset and Dependenciesは個別ロード
All Packed Assets and DependenciesはGroupでロード
公式マニュアル見る限り、基本的にはRequested Asset and Dependenciesがいいらしい。
必要なものだけメモリにロードするので、メモリの無駄遣いがなくなるため。
ただ、Prefabのような依存関係がたくさんアセットは、All Packed Assets and Dependenciesを使用した方が早くなる可能性がある。
なので、そこらへんはどっちでやるのが早いのかは検証する必要がある
また、プラットフォームによっても違いがあるみたいなので、それも検証する必要がある。
(All Packed Assets and Dependenciesモードの方が、アセットを個別に読み込むよりも、一般的にパフォーマンスが高くなる。)
参照サイト参照サイト

・Asset ProviderとAsset Bundle Providerで、Providerを変更する
参照サイト参照サイト

・アドレスを同じにして、ラベルを違うものにして状況に応じて読み込みを変えることができる。

アセット 1: アドレス: "plate_armor_rusty"、ラベル: "hd"
アセット 2: アドレス: "plate_armor_rusty"、ラベル: "sd"
hdとsdのアセットを状況に応じて変えることができる。
LoadAssetsAsync のMergeModeを使用すればいけるみたいだが、未検証
参照サイト

・Gropuの分け方の考え方のヒント
公式マニュアルそのまま抜粋
また、当記事の「・アセットバンドルの分類」でも述べているのも参考にするのもいいかも

・アセットをカテゴリに分類することで、編成を理解しやすくなり、場違いなアイテムを見つけやすくなります。
・ バンドルが非常に大きくなった場合、または非常に多数のバンドルがある場合、パフォーマンスのボトルネックが発生する可能性があります。
・特定のレベルのすべてのアセットなど、同時にロードするアセットをグループ化します。この戦略は、多くの場合、長期的に最も効果的であり、プロジェクトでのピーク メモリ使用量を減らすのに役立ちます。プロジェクト管理とランタイム メモリ パフォーマンスの両方の観点から、同時使用に応じて編成するのが最も効率的です。
・豊富な仮想メモリを提供するプラットフォームは、仮想メモリが限られているプラットフォームよりも大きなバンドル サイズを適切に処理できます。
・一部のプラットフォームは、コンテンツのダウンロードをサポートしていないため、アセットのリモート配布を完全に除外しています。
・一部のプラットフォームは AssetBundle キャッシングをサポートしていないため、可能な場合はアセットをローカル バンドルに配置する方が効率的です。
・頻繁に更新する予定のアセットと、めったに更新しない予定のアセットを分けて保管してください。
・多くのアセットを同じグループに保持すると、多くの人が同じプロジェクトで作業するときにバージョン管理の競合が発生する可能性が高くなります。

参照サイト参照サイト

・Bundle ModeのPack Together、Pack Separately、Pack Together By Labelどれを選択すべきかのヒント
.bundleファイルが多い場合(この場合Pack Separately)

・それぞれのバンドルにはメモリのオーバーヘッドがあります。これはいくつかの要因に関連しており、そのページで概説されていますが、簡単に言えば、このオーバーヘッドが大きくなり得るということです。もし、100個、あるいは1000個のバンドルが一度にメモリに読み込まれるとしたら、これはかなりの量のメモリが消費されることを意味します。
・バンドルのダウンロードには並行処理の制限があります。一度に1000個のバンドルが必要な場合、それらをすべて同時にダウンロードすることはできません。いくつかのバンドルはダウンロードされ、それらが終了すると、さらに多くのバンドルが起動します。実際には、これはかなり小さな問題で、バンドルがいくつに分割されているかよりも、ダウンロードの合計サイズによって制限されることが多いほど小さな問題です。
・バンドル情報はカタログを肥大化させる可能性があります。カタログをダウンロードしたり読み込んだりするために、私たちはバンドルに関する文字列ベースの情報を保存しています。1000バンドル分のデータがあると、カタログのサイズが大きくなってしまいます。
・重複する資産の可能性が高くなる。2つのマテリアルがAddressableとしてマークされ、それぞれが同じテクスチャに依存しているとします。それらが同じバンドルにある場合、テクスチャは一度引き込まれ、両方から参照されます。それらが別々のバンドルにあり、テクスチャがそれ自体Addressableでない場合、それは複製されます。その場合、テクスチャをAddressableとしてマークするか、重複を受け入れるか、マテリアルを同じバンドルに入れるかのいずれかが必要です。
→Check for Content Update Restrictionsで重複をチェックできる機能あるし問題ない気もする。
・多数の小さなバンドルを使用すると、アセットとアセットバンドルをより簡単にアンロードできるため、ピーク時のメモリ使用量を最小限に抑えることができます。

.bundleファイルが少ない場合(この場合Pack Together)

・UnityWebRequest(ダウンロードに使用)は、失敗したダウンロードを再開しません。そのため、大きなバンドルをダウンロード中にユーザーが接続を失った場合、接続を回復するとダウンロードがやり直されます。
・アイテムはバンドルから個別にロードすることができますが、個別にアンロードすることはできません。例えば、バンドルに 10 個の素材があり、10 個すべてをロードし、そのうちの 9 個を解放するように Addressables に指示した場合、10 個すべてがメモリ内に存在する可能性があります。詳しくは、「メモリ管理」を参照してください。
→上で述べた「アセットがアンロードされるタイミング」を参照
・少数の大きなバンドルを使用することで、アセットバンドルの総メモリ使用量を最小限に抑えることができます。

参照サイト参照サイト参照サイト

・作成するゲームの規模が多くなるにしたがって、アセットとバンドルの次の側面に注意。
以下公式ドキュメント抜粋。

・バンドルサイズ:歴史的に、Unity は 4GB 以上のファイルをサポートしていません。これは最近のエディタバージョンで修正されましたが、まだ問題がある場合があります。すべてのプラットフォームで最高の互換性を得るには、特定のバンドルのコンテンツをこの制限内に保つことをお勧めします。 
・規模に応じたバンドルレイアウト。コンテンツ ビルドで生成される AssetBundle の数とバンドルのサイズの間のメモリとパフォーマンスのトレードオフは、プロジェクトの規模が大きくなるにつれて変化する可能性があります。
・UI パフォーマンスに影響を与えるサブアセット。ここに明確な制限はありませんが、多くのアセットがあり、それらのアセットに多くのサブアセットがある場合は、サブアセット表示をオフにするのが最善である場合があります。このオプションは、Groups ウィンドウでのデータの表示方法にのみ影響し、実行時にロードできるもの、できないものには影響しません。このオプションは、GroupsウィンドウのTools > Show Sprite and  
Subobject Addressesで利用可能です。これを無効にすると、UIがより反応しやすくなります。

参照サイト

・AssetBundle の内部データを格納するためにメモリ割り当て
AssetBundle をロードすると、アセットに使用されるメモリに加えて、バンドルの内部データを格納するためにメモリを割り当てる。
読み込まれた AssetBundle の内部データの主なタイプは次のとおり。
①Loading cache
最近アクセスした AssetBundle ファイルのページを保存している。
AssetBundle.memoryBudgetKBでキャッシュサイズを変更できる。
特定の条件下では、パフォーマンスが向上するケースがある。
②TypeTrees
オブジェクトのシリアル化されたレイアウトを定義。
同じ種類のアセットを同じバンドルにまとめるとメモリ使用量が減る。
③Table of contents
バンドル内のアセットを一覧
Table of contentsのサイズは、アセットの総数に基づいている。特定の時間にロードされる AssetBundle の数を最小限に抑えることで、目次データの保持専用のメモリ量を最小限に抑えることができる。
④Preload table
各アセットの依存関係を一覧
アセットバンドル同士の依存関係が複雑だとそれだけ、Preload tableが大きくなるので
メモリ使用量を削減したい場合は、参照する関係を減らす必要がある。
参照サイト

・link.xml
プレイヤービルドを行うと、link.xml生成される。
これは、コードストリッピングを無効したいのを記載するファイル
参照サイト参照サイト

・キャッシュを削除する方法
①キャッシュ全体は、Caching.ClearCache
検証したところが確かにすべて消えていた。
②特定のキャッシュは、Addressables.CleanBundleCache
未検証。若干めんどくさそう
Cache Clear Behaviorで古いキャッシュを削除できる設定があるのでそれでもいいと思う。
参照サイト

・スプライトアトラス周り
参照サイト

・Build layout reportで、ビルドしたアセットバンドルのサイズ、ビルドサイズ、グループ内のアセット情報や依存関係、などを確認できる。

参照サイト

Discussion