はじめに
今日また Unity の AssetBundle の地雷を踏んだので、同じ地雷を踏まないように記録として残します。
なお、この地雷は私自身が埋めたものであり、普通に使う分には起きないものなのでご安心ください。
経緯
とあるプロダクトの AssetBundle を更新したところ、以下のような不可思議な現象が発生するようになった。
- 最新版(v1.1.0 としておく)を新規インストールする分には問題ない
- 一つ前のバージョン(v1.0.0としておく)を(DeployGate などから)インストールする分にも問題ない
- v1.0.0 で AssetBundle をダウンロードしておいて、v1.1.0 で AssetBundle を更新すると 一部の AssetBundle のみ更新されない
- しかも、「v1.0.0 でダウンロードだけした場合」は問題無く、「v1.0.0 でダウンロードし、実際に使われている画面まで進んだ場合」が NG
とても不可解な現象で、CDN のキャッシュとかも疑ったけど問題無さそうだった。
前提
とあるプロダクトの AssetBundle 要件としては以下のような感じだった。
- タイトル画面に入るタイミングで必要な AssetBundle を全てダウンロードする仕組みになっている
- AssetBundle Manifest を用いずに、URL のみでキャッシュ管理を行っている
UnityWebRequestAssetBundle.GetAssetBundle()
の引数が(string url, uint crc)
なオーバーロードを使っている- ただし
crc
は固定値のゼロを渡している *1
- URL の中に各 AssetBundle のバージョン的なものを示す数値が含まれる
https://example.com/v1/foo.unity3d
https://example.com/v1/bar.unity3d
https://example.com/v2/foo.unity3d
- みたいな感じ
- ダウンロードのみを行うメソッドと、読み込みを行うメソッドが分かれている
原因
前提のなかで記載している「読み込みを行うメソッド」の中で使っている UnityWebRequestAssetBundle.GetAssetBundle()
のオーバーロードが (string url, uint crc)
ではなく (string url, Hash128 hash, uint crc)
になっており、更に hash
には new Hash128()
を渡すという状態になっていた。
分析
詳細な仕様は分からないが、キャッシュ周りの仕組みは以下のようになっていると考えられる。
なお、 crc (チェックサム)については話がヤヤコシクなるので割愛。
- ダウンロードされた AssetBundle を
GetAssetBundle()
の引数に応じて保存 GetAssetBundle()
の引数に渡された URL と AssetBundle Name との対応表的なものを別途保存- ココでは
hash
とかversion
は記録されていないんじゃないかと思われる
- ココでは
で、今回のケースの場合、ダウンロード時と読み込み時に引数が異なってしまっていたコトが原因で、以下のような感じになったものと推察される。
ここでは、 https://example.com/v1/foo.unity3d
, https://example.com/v2/foo.unity3d
という AssetBundle をダウンロードする場合を考える。
- v1.0.0 のダウンロード処理
- 対応表に
https://example.com/v1/foo.unity3d
に該当する AssetBundle Name が見付からないのでダウンロードが開始される hash
ナシのファイルfoo.unity3d
が保存される- 対応表に
"https://example.com/v1/foo.unity3d":"foo.unity3d"
的な情報が書き込まれる
- 対応表に
- v1.0.0 の読み込み処理
- 対応表に 1. で保存された
https://example.com/v1/foo.unity3d
に該当する AssetBundle Name はあるが、キャッシュの実体としてhash
アリのファイルが見付からないので、ダウンロードが開始される hash
アリのファイル00000000000000000000000000000000-foo.unity3d
が保存されるnew Hash128()
を渡してしまっているので、実際のハッシュ値は00000000000000000000000000000000
とかになると思われる
- 対応表に 1. で保存された
- v1.1.0 のダウンロード処理
- 対応表に
https://example.com/v2/foo.unity3d
に該当する AssetBundle Name が見付からないのでダウンロードが開始される- URL のバージョン番号のところが異なるのでキーがないはず
hash
ナシのファイルfoo.unity3d
が更新される- 対応表に
"https://example.com/v2/foo.unity3d":"foo.unity3d"
的な情報が書き込まれる
- 対応表に
- v1.1.0 の読み込み処理
- 対応表に 3. で保存された
https://example.com/v2/foo.unity3d
に該当する AssetBundle Name があり、更に 2. で保存されたhash
アリのファイル00000000000000000000000000000000-foo.unity3d
が存在するため、キャッシュから読み込みが行われる - 結果として古いファイルが読まれてしまう!
- 対応表に 3. で保存された
所感
いや、はい。同じオーバーロード使わなかったオレが悪いんです。
引数省略せずに、素直に AssetBundleManifest.GetAssetBundleHash()
とかで取得出来る Hash を使うと良いんじゃないでしょうか。
この辺のキャッシュの詳細な資料ってどこかに転がっていませんかねぇ…?