Unity Addressable Asset System を使いこなす
はじめに
Addressables Asset System が登場してかなり立ちますが、Webにはいまだに応用編の情報が少ない状況です。基礎編はわりと充実しており、なんとなく使えるレベルにはすぐにたどり着けます。しかしながらいざ製品版や運営タイトルに投入しようとすると、途端にどのように設計すればいいかに悩むわけです。
アセットの分割および配信設計はゲーム運営やDLC配布に欠かせない仕組みであり、一度設計を失敗すると二度と取り戻せない重篤な事態となる危険があります。一度リリースしたソフト・アセットを無かったことにはできないからです。
本稿を執筆したモチベーションは、世の中に具体的な情報が少なく手探りで進めて苦しい経験をしたからです。実運用を考慮したプロダクトレディな事例共有が少ないため、どうしても自PJに参考となる事例を見つけられませんでした。大規模モバイル案件はコンシューマー案件には参考にはならないし、逆もまた然りです。
本稿では、Addressablesの応用編となる詳しい情報およびプラクティス事例を少し公開します。主にWindows向けスタンドアロンビルドを前提とします。
本稿によりUnity界隈の力になれれば幸甚です。
(みんなももっと事例を公開しよう!)
履歴
- 01/30 誤字修正。 言い回し変更。その12追記
用語
説明のため以下の略語を定義します。
- AAS - Addressable Asset System
- AA - Addressable Asset (アドレッサブル管理にしたアセットのこと)
- AG - Addressable Group の略
- アドレス - Addressable Address。 AAに紐づく一意の文字列のこと。(主キー)
- アセバン - Asset Bundle の略
- AAビルド - Addressable Assetを ビルドすること。ゲームはビルドしない。
Addressables とは
誤解を恐れず一言でいうと、Unity用のアセットバンドル管理ライブラリです。
Addressablesが存在しない神代の昔、人々はオレオレでAsset Bundle を管理していました。
Nested Prefab の実装に喜んだり、AssetBundleをどう分けるべきか、といったことにやきもきしていた頃です。連綿と続く長寿運営タイトルでは今もオレオレのAssetBundle管理機構を使っているのではないでしょうか?
- AssetBundle設計のとある形 <- 2017年...
そんなどこのご家庭にもあるアセバン管理機構ですが、Addressables登場により共通言語として使えるようになりました。やったー!
Addressables は Asset Bundle を管理するいい感じのツールセット
Addressables は膨大な数の Assset Bundle をそれなりに管理できるようにして、リソース配信・メモリ使用量・アセットの更新ワークフローなどといった様々な困りごとを解決してくれるツールです。単純なエディタ拡張ではなくランタイムも同梱されたライブラリとなっています。
ライブラリユーザー側からみて、アドレスから一意に紐づくアセットを依存アセットも含めて解決してくれるという点が大変イケています。AssetBundleが完全に隠蔽されて、アドレスというstringだけを扱えばよくなったのです。C#エンジニア的にはDeveloper Experienceが改善されました。
UniTask様のおかげ様で非同期ロードもちょちょいのちょいで実装できるようになりました。
なんだかんだ名前でロードできるのがいいんです.
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(key: "path/to/menuWindow.prefab");
比較的後発ライブラリであるだけあって、そのクラス設計はうまくinterfaceに抽象化されております。AssetBundle に限らずユーザー側で独自のカスタムアセットとアセットプロバイダをサポートできるようになっています。CustomAssetBundleProvider および CustomAssetProvider を実装するカスタマイゼーションポイントが設けられています。
とはいうものの、独自形式のバイナリをUnityEngineに食べさせることは困難なので、AssetBundleProviderを専ら使います。
基本の使い方と概念
有能な記事・動画があるのでそちらに譲ります。
本編
アセット管理と配信をどうやりたいかを決める
ゲームによって必要となるアセット管理機構は異なります。というのも、個人開発なのか大規模開発なのか、売り切りなのか運営するのか、ビジネスモデル・開発事情に大きく影響を受けるからです。
安心してください。Addressablesはほとんどのユースケースに適合しています。アセット管理するならAddressablesでOKです。個人的な見解ではありますが、Addressablesは Unity公式の数少ない 信頼できるパッケージです。多少癖はありますが、実用に耐えられるGame Ready な商用パッケージでしょう。
本稿ではアセット管理戦略を以下のように定めます。
以降の説明はこの戦略を前提とします。モバイル向けでは別の戦略の方がよかったりするので参考にとどめておいてください。
1アセット1AA化戦略
最も粒度の細かいAA化戦略です。
Addressable Groupの Bundle Mode を Pack Separately
に設定するのが基本となります。
利点は
- アセットの複製が発生しないこと(トータルサイズ減)
- ファイル単位で差分更新が可能なこと(配信量減)
- 管理がわかりやすいこと
- 重複がないので、ピークメモリサイズを抑えやすいこと
- 依存関係が多いのでロード済みAAがHitしやすい
欠点は
- Disk上のファイル数が多くなること (HDD上での遅さ)
- ファイルダウンロード数が多くなること(HTTP1.1の遅さ)
- 同時に開くファイル数が多くなること(ファイルデスクリプタ数の制限)
- ファイルI/Oが重くなる
CDNサービスの利用料金は保存量と配信流量に比例します。
そのため、トータルサイズは小さいほど、更新の流量も小さいほどお得です。
ユーザーから見てもDL時間とアプリサイズが小さくて済むのでお得です。
Unityにおいてピークメモリサイズはとても重要で、ファイル重複がない分自然と抑えられます。C#では確保したマネージドメモリを基本的にOSに返還しません。多数のファイルを同時に開いてロードすると困ることがありますので、並列ロードは用量を守りましょう。
AAS は依存AAがロード済みであるならばそれへの参照カウントを増やしてロードを省略してくれます。AAが細かいほど、ロードの省略率が上がるはずです。
欠点について、昨今はSSDが当然なのでDisk上のファイル数は問題になりません。
ファイルダウンロード速度はHTTP2であれば小さい大量のファイルにも速度がでます。
ファイルデスクリプタ問題はWindows, Macでは実質無限なので問題になりません。モバイルで問題になりますが、こればっかりはこまめにアセットを閉じる他ありません。
ファイルI/Oはどうしても重いです。ファイルを開く・閉じるという行為自体が重いので、ファイル数が多いほど負荷が増えるでしょう。ramfsのようにメモリ上に仮想FileSystemを構築できればいくらか早くなるでしょうが、UnityFsと相乗りさせるのはまぁ大変でしょう。
総括すると、1アセット1AA戦略はシンプルに1ファイルをAAにコンバートするという単純戦略です。
重複を最小化することを目的とし、運営費用を少しでも抑える作戦です。ディスクと通信が実質無制限なStandalone版に適しています。Standalone版は全てのファイルの事前DLが可能なため、欠点を踏み倒せます。
重複が減る分、CDN総流量が少なくなるため、無料プレーゲーム・ライブサービスに適しています。
売り切りStandalone版はCDN代を価格に入れられるので、重複があってもよかろうです。
ロード速度優先でBundleにまとめた方がいいと思い見ます。DLC,更新頻度も少ないでしょうから、小難しい管理は必要ないかもしれません。
モバイルはファイルディスクリプタを抑えないといけないのと、毎週のようにアプデが必要になることから相当アセバンのバージョン管理に気を遣うはずです。そちらは先行事例が共有されてますので割愛します。
Addressables のコツ
さぁいこう。罠はいっぱいあるぞぉ。
その1: AAの開放は ハンドルをReleaseしたらええ
ややこしいことに AAS にはリリース方法がいくつか存在します。
handle.Release();
Addressables.Release<TObject>(AsyncOperationHandle<TObject> handle)
Addressables.Release<TObject>(TObject object)
Addressables.ReleaseInstance(AsyncOperationHandle hanlde)
Addressables.ReleaseInstance(GameObject gameObject)
まじややこしいのですが、1番だけでいいです1択です。
2番でもいいですが、実装を見ると1番を呼んでるだけなので一緒です。
他のAPIは内部にもったDictionary<object, Handle>
から逆引きしているだけです。
逆引きするぐらいならハンドルを素直に引き回すべきです。ロードした者が責任をもってアンロードするのが対称性のある実装です。
// これだけ使えばええ。
AsyncOperationHandle<T> handle;
if(handle.IsValid())
{
handle.Release();
handle = default; //デフォ値が無効値である
}
なおReleaseしたハンドルを再度Releaseするとヌルポします 😢
ハンドルはstructですから内部値を無効化したいときは default を再代入すればいいです。
その2: インスタンスを使った解放は罠だから使うな
Unity には Fake Null問題があるため、GameObjectが破棄されている場合があります。
ReleaseおよびReleaseInstanceに渡す時点で fake nullになっている可能性があります。
Release<TObject>(TOObject obj)
はヌルポしないようにnullチェックしています。通常 TObject
は GameObject
や UnityEngine.Object
派生型です。このとき if(obj == null)
で比較されるため fake nullだと early return されてしまって解放されません。
AddressablesImpl.csより
public void Release<TObject>(TObject obj)
{
if (obj == null) // UnityEngine.Object.operator==が使われるぞ!
{
//fake nullオブジェクトがガード節でearly return される
LogWarning("Addressables.Release() - trying to release null object.");
return;
}
// 解放処理へ続く
...
}
修正するなら if (obj is null)
とすればnullとのみ比較できます。
Addressables.InstantiateAsync
で生成したGameObjectは 参照カウントを増やします。Addressables.ReleaseInstance
で解放して参照カウントを減らします。
ぶっちゃけオーバーヘッドが大きいし、ハンドルを引き回すならインスタンスが参照カウントを増やす必要がないので GameObject.Instantiate
および GameObject.InstantiateAsync
で事足ります。
Addressables.ReleaseInstance
に Addressables.InstantiateAsync
由来でないインスタンスをに与えるとハンドルを解放できません。つまりそのインスタンスがどこに由来するか、new で作ったのか、シリアライズフィールドからやってきたのか、コード全体を見て正確に把握する必要がでてきます。お仕事が増えているので、やはりインスタンスによる解放はよくないです。
その3: 基本的なコードはこうなると思う
Addressable の素晴らしいところは アセットの依存関係を隠蔽して自動で解決してくれる点です。AAビルド時点で強参照による依存関係が見つかれば、AASが依存先アセットを記録してくれます。
ロード時点で依存アセットが未DLもしくは未ロード状態であるならば、それぞれダウンロード・先行ロードしてくれます。
そのおかげで、開発者はアセットの依存関係を気にせずに アドレスという抽象的な情報のみを用いて所望のアセットをほぼ確実にロードすることができるのです。開発者体験はかなり向上します。
private void Start()
{
StartAsync(this.destroyCancellationToken).Forget();
}
private async UniTask StartAsync(CancellationToken cancellationToken)
{
// アドレスを主キーにしてアセットをロードできる
AsyncOperationHandle<GameObject> handle =
Addressables.LoadAssetAsync<GameObject>(key: "path/to/menuWindow.prefab");
// ロードは素直に awaitで待てる
// ロードに成功したらassetが返る。失敗したら例外(KeyNotFoundExceptionなど)。
GameObject prefabAsset = await handle;
// この時点ではあくまでアセット
// アセットをインスタンス化してオブジェクトを作る
GameObject instance = Instantiate(prefabAsset);
// インスタンスを使ってなんかする
await DoSomethingAndWaitAsync(instance, cancellationToken);
// 使い終わったのでハンドルを開放する
handle.Release();
}
参照カウント方式であるため、handleを解放しても意図せず使用中の他アセットをリリースしてしまうという事件が起こりません。
参照カウント方式で問題となる循環参照ですが、Addressablesでもロードにこけるケースがあるようなので、アセット同士の循環参照を起こさないように気を付けましょう。
※ Non-Recursive Dependency Calculation は trueだと循環参照のあるアセットでロードにこけることがあるようです。
その4: using ステートメントを駆使すべし
Addressables は Releaseを呼んで明示的に解放してあげる必要があります。強制的に解放する手段もありますが、参照カウントの仕組みが台無しになるので使うべきではありません。それは最後の手段です。
ここは using ステートメントによる 確実なリソースハンドルの解放が向いています。
特にリソース周りは非同期にならざるを得ず、非同期はusing ステートメントと相性がいいので大変都合がよいです。
struct HandleDisposable<T> : IDisposable
{
private AsyncOperationHandle<T> Handle;
// ctor
public HandleDisposable( AsyncOperationHandle<T> handle) => Handle = handle;
// IDisposable interface
public void Dispose()
{
if(Handle.IsValid())
{
Handle.Release();
Handle = default; // 何度Dispose呼び出しされてもいいように無効値いれとく
}
}
}
private async UniTask StartAsync(CancellationToken cancellationToken)
{
AsyncOperationHandle<GameObject> handle =
Addressables.LoadAssetAsync<GameObject>(key: "path/to/menuWindow.prefab");
using (new HandleDisposable<GameObject>(handle))
{
GameObject prefabAsset = await handle;
GameObject instance = Instantiate(prefabAsset);
await DoSomethingAndWaitAsync(instance, cancellationToken);
} // 例外でも正常系でもここで確実にdisposeされる
}
アセット周りは例外の塊なので、例外対応は必須です。
例えば、アセットAをロードしている間にアセットBをロードしてNotFoundException、TimeoutExceptionなどの例外が出ることがあります。ユーザーが待ちきれずに中断ボタンを押すとOperationCanceledExceptionが飛ぶかもしれません。例外でjumpした結果アセットAがロード完了のまま解放されずメモリリークすることがよくありますね。アセットなのでリーク量が大きいのが苦しいところです。
開発中はアセットが揃っていないことも多く、例外がよくでます。その場合、メモリリークによって徐々にエディターのパフォーマンスが落ちて開発効率が落ちます。リークした残ったアセットを参照して何故か更新できない、なぜか生動きするといった事態を招きます。
参照カウントとはコンピューターサイエンスに於けるの有限リソースです。セマフォやクリティカルセクションのように注意して取り扱う必要があります。これに対処すべく確実なリソースの破棄に using ステートメントを強くお勧めします。
実用上はラップしてね
いちいち new HandleDisposable
とするのは大変めんどくさいので、thin wrapperを用意して使いやすくしてください。
その5: AAはスクリプトで管理すべし
AASに同梱されている管理メニュー、Addressable Group ビューは使い物にならないので忘れましょう。これは開発者がざっと見で使うだけのツールです。このタブを経由して操作してはいけません。使いにくいだけならまだしも結局使えません。
AASの想定するワークグローは次の通りです。
- アセットをAA化するとき、各アセットをAAに登録します
- 一意のアドレスを紐づけます
- グループ分けを適切に行います
- 必要ならラベル付けを行います
- AssetReferenceを用いることででシリアライズフィールド経由で弱参照できます
このワークフローは破綻しています。理由は3点存在します。
- アセット数多すぎて管理できない問題
- AAへの登録漏れ・除外漏れが絶対発生する問題
- バージョン管理でとちる問題
アセット数多すぎて管理できない問題
アセット数が多すぎると人手で管理できなくなります。
1アセット1AA化戦略に限らず、どのような戦略をとっても管理できません。
一例として、CommonUIグループにButtonとTextパーツを置きました。UI要素をprefab化して使いまわすことはすごく合理的です。この図を見ると管理できそうに思いますよね?
本番ゲーム制作ではアセット数が膨大です。ゲームを作ると千とか万とかアセットが膨らんできます。膨大な数のアセット群をリストビューで担当エンジニアやアーティストが目で見て一つずつクリックして管理することは到底できません。グループに分けて分割管理すればいいと思うかもしれませんが、焼け石に水です。例ではUIグループだけですが、UI要素だけでも膨大です。結局はグループの中身が正しいかを確認する必要があるため、どこかで目視確認が必要となってしまいます。
ここに、ムービー、テクスチャ、メッシュ、BGM・SE、各言語の音声...が投入されます。さらにそれら素材系アセットを組み合わせて作ったprefabが組み合わせ爆発で増えてきます。作る前から終わりました。
URPフォルダを突っ込んだ図。製品版ゲームはもっと多いよ!
大量に扱うコツ動画 (少し古いけど使える)
AA管理に絶対しくじる問題
誤りには2種類あります。
- AA化するべきアセットがAA化されていない
- AA化してはいけないアセットがAA化されている
各アセットを人でAA化しているようでは絶対に失敗します。
アーティスト陣はアセット制作者であるもののアセット管理については専門家ではありません。AA化すべきかの判断ができません。特定のフォルダ丸ごとAA化しているならば簡単なのですが、特定のアセットのみAAから除外したいことも多々あります。
恐ろしいのは入ってはいけないアセットがAA化されることです。Sandboxフォルダのデータを参照してたりとか。開発用のデータとか、没データとか。版権物のデータとか。AASは依存関係を解決して自動で複製する機能が備わっています。そのため、入っちゃいけないデータがこっそりアセバンに同梱されちゃったりします。APIトークンやパスワードをgitignore設定はしたけど、なぜかSerializeFieldに強参照が張られててAAビルドに巻き込まれてなぜかアセバンに入っている事件は起きて欲しくないものです。
入ってはいけないアセットは基本的にゲームからロードされません。そのため気づけません。
AAビルドログを舐めるようにみて、チェックしなくてはなりません。
この点はスクリプト管理にしても、チェックは必要です。
1アセット1AA化戦略の場合はファイルとして存在するのでcatalog.jsonを見たりエクスプローラで見れば、怪しいファイルを見つけることが可能です。
バージョン管理でとちる問題
1アセット1AA化戦略ではバージョン管理が比較的楽です。そのアセットを更新したらAAも更新したらいいからです。そして、AAが更新されたらbundle CRCやAAのhashが変更されるため、ロード時点で自動解決されて更新対象アセットとなります。AAの自動更新機能にのっかれていい感じです。
とはいえgit, svn, perforceと仲良くするのは結構大変です。
バージョンタグ打ったり、フォルダ名で管理したり、branch運用したり、結構大変です。
グループ設定を人手で分けるのは至難の業です。
配信バージョンごとのグループを作るという作戦も悪くはないですが、それだとAddressables Group数が増え続けるのでいつか破綻する気がして私は採用しませんでした。
Addressables.Editor 空間を使おう
AA管理はエディタースクリプトから実行できます。
Addressables には Addressables.Editor APIがちゃんと用意されており、すべての操作をスクリプトから実行できます。そのため、特定のフォルダや特定のパス・拡張子のファイルをAA化することができたり、allowリスト-denyリスト形式で明示的にアセットをフィルタリングすることもできます。
スクリプトでAA化すると命名規則にそったアドレスの発行も可能になります。typoによるアセットロードできない問題とかゴミファイルの棚卸とか、アセット管理はやることが多すぎるのでスクリプト管理にするべきです。
詳しい記事はちらほらありますので詳細はそちらに譲ります。
とにかくEditorスクリプトを使わないで手作業でAA化できると思うな、ということです。
お勧めの手法は アドレスの命名規則を完全に決めてしまい、アセット置き場なども決めたうえで、決定論的にAA化を行うことです。AddressablesAssetSettings
自体をスクリプト生成するべきです。
そうすることで、各種プラットフォームごとの設定を スクリプトから生成できるようになります。
上記問題はスクリプトで確実に解決できます。
※ content_state.binはバージョン管理した上で行ってください。
こういうの使ったらいいと思う
その6: CIで回すべし
AAビルドだけを実行することができますので、CIに組み込みましょう。
AAビルドはインクリメンタルを有効にしていれば、早めに終わるのでこまめに回しましょう。
commitされたアセットがAA化できなかったり、依存関係に問題があったりすることを早めに検知しましょう。
なお、同一マシンでAAビルドとアプリビルドを並行実行する方法は調べてません。(誰か教えて)
Libraryフォルダを複製・シンボリックリンクを張るhack方法だとなんかとちりそうで嫌だし。
その7: TextMeshProに気を付ける
FontAssetが複製されるとめちゃくちゃAAサイズがでかくなります。
TMPコンポーネントから参照させているとFontAssetが複製される危険があります。
必ず"すべて"のFontAssetをAA化しましょう。
とくに忘れがちなのは フォールバックフォントとシステムフォントです。
"LiberationSans SDF" お前だよ。
気を付けましょう。
多言語対応するときに各言語のFontAssetを用意すると思いますので、言語ごとのグループを作るのでいいと思います。全プレーヤーに全言語DLさせるのか、選択言語だけを遅延DLさせるのかはアプリ次第です。ボイスアセットもあるので、選択言語のみのDLが多い印象です。
※ 完全に余談ですが、LiberationSans は SIL OpenFont License ver 1.1です。使うならちゃんと批准しましょう。
※ 完全に余談ですが、TextMeshProに同梱されているEmojiOneは、出所がわからずライセンス情報が確認できませんでした。昔は有料版もあってfree版の商用利用が怪しい気持ちがあったのですが、今はwebサイトごとアクセスできません。同梱されているのは多分free版???
その8: Unityパッケージは1グループでいいと思う
Packages配下のアセットも AA化できます。
1アセット1AA化戦略をとる場合でも、パッケージアセットは1bundleにまとめてしまっていいでしょう。
パッケージを更新するときは全部更新して配信すると思いますから、1bundleで不都合がないはずです。
アプリケーションスコープのライフタイムを持つアセットは常駐させることでしょうし、消費するファイルディスクリプタの数を減らせるはずです。。
Bundle Mode を Pack Together
にするとそのグループは1bundle、1ファイルにまとまります。
PackagesのアセットもAA化しないと複製される 😢
例えば、とあるマテリアル1,2,3にURPのLitシェーダーを設定したとします。
それらマテリアルをAA化するのですが、それぞれグループ1, グループ2, グループ3に割り振ります。
このとき、アセバン1,アセバン2, アセバン3のすべてにLitシェーダーがそれぞれ同梱されます。
結果 アセバンサイズが膨らみます。
アセットを持つような共通パッケージかつ常駐するようなものは1グループにあらかじめまとめてしまっ
て、一気にロードしてもらうのがいいと思います。
URP, CoreRP, AI Navigationなどなど。
※ Editor用のアセット(アイコンとか)はAA化しないようにしましょう。意味ないので。
※ 純粋なC#パッケージはAA化できないのでやる必要ないです。
その9: builtin-resourceは AA化できないので使わない
アプリビルド時に builtin-resource.res に格納されるようなアセット群が存在します。
Cubeメッシュなど標準で用意されているアセット達です。
これらはAA化できませんので必ず複製されます。デバッグprefabなどで使うことがあると思いますが、あまり使わないようにしましょう。それほどサイズも大きくないし、開発ビルドでしか使わないと思うので、問題にはならないと思いますが。
嘘かも。要確認なので、信用しないで。
unitybuiltinassets というbundleが生成されます。
そいつらの中身は確認できていません。
その10: Addressables Analyze はこまめにみるべし
アセットのDupliceやグループ分けが正しいかを、こまめにチェックしましょう。
AA化の粒度やグループ分けが悪いと無駄にアセットが複製されます。
依存関係の順番によっては意図しないバージョンのアセットへ解決されることがあります。(先祖返り問題)
とくに Scene Duplicationは AASの悪名高いクソ仕様 です。
シーン内にAAが埋め込まれて更新できなくなるようなことは避けましょう。
その11: ファイルパス問題に気を付ける
Windows OSのファイルシステムのパス最大長は260文字であるため、長いパスのファイルを開けないという問題があります。MAX_PATH
で有名です。
上記リンクの説明のとおり、レジストリをいじるかグループポリシーを適用することで制限を取っ払えますが、全ての開発PCでそれをやれるかといえばできないでしょう。
しかも #define MAX_PATH(260)
でビルドされたツール群が世の中に五万とあるわけで、アプリ側がちゃんと動くかは保証がないわけです。実質、Windowsは260文字までで頑張る他ありません。
さて、AddressablesにおいてはAAのビルドキャッシュは少々深い箇所に配置されます。
BuildPath
と LoadPath
で設定可能です。
Bundle Naming Mode を Append Hash
にするとパス名がめちゃくちゃ長くなります。中間フォルダにもhash名を付与しだすのであっというまに260文字を超えます。
これだけで155文字、長くね?
{UnityProject}\Library\com.unity.addressables\aa\Windows\StandaloneWindows64\7ac544a236e1cbae374f730031378d37_unitybuiltinassets_c735796e73b3538238b6710bc521de8d.bundle
{UnityProject}\Library\com.unity.addressables\aa\Windows\StandaloneWindows64\defaultlocalgroup_assets_all_18b016a1ad0a0bea31770f79c573cf7f.bundle
私はプラットフォーム名をStandaloneWindows64
から Win64
に変更するなど、涙ぐましい節約を行ったことがあります。
BundleNamingModeは OnlyHash
にするのが無難化と思いますが、開発中は結構大変です。NoHash
に設定したいところですが、アセットの配置場所が深いと名前が長くなります。
AAビルドエラーになるので気を付けましょう。エラーメッセージは大変わかりにくいです。FileNotFoundExceptionが出た記憶。
その12: バージョンフォルダは合った方がいいかも
Addressablesというよりも CDN の使い方のコツです。
CDNサービスでは同名ファイルの更新がめちゃくちゃ大変です。
その性質上、ファイル更新がすべてのエッジサーバーに行き届くまで時間がかかります。CDNはファイル更新を行うと、エッジサーバー間で徐々に伝搬されていきます。エッジサーバー上のファイルが更新されるまでは古いデータがしれっと降ってきます。いつ更新されるかは厳密には分かりません。
(3日-7日とか聞いたことある)
開発中はこれがめちゃくちゃ問題になります。本番環境やステージング環境で貫通チェックしたいときに修正反映が面倒くさいのです。
対策方法は同名ファイル"パス"でアップロードしないことです。
つまり、cdnサービスにあげるときに配置場所を
resources/v1.0.0/assets/win64/ui/path/to/menu.prefab
resources/v1.0.1/assets/win64/ui/path/to/menu.prefab
とすることです。CDN上で上書きしないことです。
ファイルのhashをパスに含めていいかもしれません。
シンプルに日付を入れてもいいと思います。
さて、ここで難しいのがcatalog.jsonの配置場所です。ゲームアプリはcatalog.jsonのURLを知らねばなりません。通常は固定urlをC#にハードコードするか設定ファイルにハードコードしちゃうと思います。可能ならサーバー制御でアセットのDL先を変更できるように、サーバーを立てて、catalog.jsonのDL先を返すwebサービスを構築すると良さそうです。
※最近のCDNサービスはキャッシュ削除を明示的に行ってパージできるようになってるのでそれを信じるなら同名でもいいかも。どれだけすぐに反映されるのかは存じません。
このあたりの話は更新ポリシーによります。
古いバージョンのアプリを動かすべきか、とちったときのロールバックはどう行うか、などなど。
Discussion