Imanect Engineering

Meta QuestアプリでRemote AddressablesをJSONから切り替える構成

Meta Quest 3向けUnityアプリでRemote AddressablesをHTTP経由で取得し、設定JSONの変更だけで実行時にPrefabを切り替える構成と、Catalog更新、AssetBundleキャッシュの問題への対処を整理します。

杉浦夢斗

Meta Quest 3にビルドしたUnityアプリから、PC上のAddressablesアセットをHTTP経由で取得し、実行時にPrefabを切り替えられるかを検証した。

最終的に確認したかったのは、Questアプリを毎回再ビルドせずに、PC側またはWeb管理画面側でシナリオ設定やアセットを更新し、VR空間の表示内容を切り替えられる構成である。

この記事では、固定AddressのPrefab表示から始めて、設定JSONによるPrefab切り替え、Catalog更新、AssetBundleキャッシュの問題までを整理する。

目的

今回の検証では、次の構成を目指した。

Web管理画面またはPC側設定
-> 設定JSONファイルを更新
-> 必要に応じてAddressables Bundleを更新
-> Questアプリが起動時に設定JSONファイルを取得
-> JSON内のAddress名をもとにPrefabをロード
-> VR空間に表示

最小検証では、PC上に置いた検証用PrefabをQuestアプリからロードした。次に、JSONの値を変更するだけで、表示するPrefabを切り替えられるかを確認した。

全体像

検証環境は次の通り。

Unity
6000.3.13f1
実行端末
Meta Quest 3
PC側
Unity Editor実行環境
Addressablesビルド環境
Python簡易HTTPサーバー
通信方式
QuestからPCへのHTTP通信

PC側では、AddressablesのRemote Build成果物と設定JSONを同じHTTPサーバー配下に置いた。

[配信ルート]/
Android/
[Catalogファイル名].hash
[Catalogファイル名].bin
[Bundleファイル名].bundle
Config/
[設定JSONファイル].json

Pythonの簡易HTTPサーバーは [配信ルート] 直下で起動した。

Terminal window
cd [配信ルートのパス]
python -m http.server [ポート番号]

この場合、Quest側からは次のURLにアクセスする。

http://[IPアドレス]:[ポート番号]/Config/[設定JSONファイル名].json
http://[IPアドレス]:[ポート番号]/Android/[Catalogファイル名].hash
http://[IPアドレス]:[ポート番号]/Android/[Catalogファイル名].bin
http://[IPアドレス]:[ポート番号]/Android/[Bundleファイル名].bundle

[配信ルート]/Android 直下でHTTPサーバーを起動する場合は、Load Pathが変わる。今回の整理では [配信ルート] 直下で起動し、Load Pathを次の形にしている。

http://[IPアドレス]:[ポート番号]/Android

Addressablesの設定

AddressablesにはRemote用のGroupを作成した。

Group名
[Remote Group名]
登録したAddress例
[Address名_標準]
[Address名_A]
[Address名_B]
[Address名_C]

[Remote Group名] の重要設定は次の通り。

Build Path
[配信ルート]/Android
Load Path
http://[IPアドレス]:[ポート番号]/Android
Bundle Naming Mode
Append Hash

Addressable Asset Settings側では、Remote Catalogを有効にした。

Build Remote Catalog
ON
Remote Catalog Build Path
[配信ルート]/Android
Remote Catalog Load Path
http://[IPアドレス]:[ポート番号]/Android

特に重要だったのは、Catalog側だけでなくRemote Group側のLoad PathもHTTP URLにすることだった。

正しい状態
[Remote Group名] Load Path = http://[IPアドレス]:[ポート番号]/Android
不正な状態
[Remote Group名] Load Path = <undefined>

Group側のLoad Pathが未定義だと、Catalog設定が正しくても、Quest側はBundleを正しいHTTP URLから取得できない。

HTTP通信の許可

QuestブラウザからPCサーバーにアクセスできても、Unityアプリ内でHTTP通信が許可されていなければRemote Addressablesは失敗する。

実機ログには次のエラーが出ていた。

Non-secure network connections disabled in Player Settings
Insecure connection not allowed
UnityWebRequest result : ConnectionError : Insecure connection not allowed

UnityのPlayer SettingsでHTTP通信を許可した。

Edit
Project Settings
Player
Android
Other Settings
Configuration
Allow downloads over HTTP

ローカルLANでの検証では、次の設定にした。

Allow downloads over HTTP
Always allowed

設定JSONでPrefabを切り替える

Quest側が最初に取得する設定ファイルとして設定JSONファイルを用意した。

{
"scenario_id": "[シナリオID]",
"spawn_asset": "[Address名]",
"spawn_position": {
"x": 0,
"y": 1.2,
"z": 2
}
}

spawn_asset を変更すると、Quest側でロードするAddressablesアセットが変わる。

[Address名_標準]
[Address名_A]
[Address名_B]
[Address名_C]

Quest側の起動処理は次の流れにした。

1. [設定JSONファイル名] をHTTPで取得
2. JSONをパース
3. spawn_assetを取得
4. Addressables Catalogの更新を確認
5. Catalog更新があればUpdateCatalogsを実行
6. AddressのLocation情報をログ出力
7. Download sizeを確認
8. DownloadDependenciesAsyncを実行
9. LoadAssetAsync<GameObject>でPrefabをロード
10. 指定位置にInstantiate
11. 失敗時は [Fallback Prefab名] を表示

ログには、少なくとも次を出すようにした。

Location count
Location PrimaryKey
Location InternalId
Location ProviderId
Location ResourceType
Dependency count
Dependency PrimaryKey
Dependency InternalId
Dependency ProviderId
Dependency ResourceType
Download size
DownloadDependencies result
LoadAssetAsync result

これにより、Addressが見つからないのか、Catalogが古いのか、Bundleが取得できていないのか、Prefabロードだけが失敗しているのかを切り分けられる。

最初に成功したこと

最初に、固定AddressでPrefabをロードした。

[Address名_標準]

この段階で確認できたことは次の通り。

QuestからPCへのHTTP通信ができる
Addressables Catalogが読める
Remote Bundleを取得できる
Bundle内のPrefabをGameObjectとしてロードできる
Quest上にPrefabを表示できる

成功までの流れは次の通り。

1. PC側でAddressablesを Android 向けにビルド
2. [配信ルート]/Android にBundleとCatalogを出力
3. PCでpython -m http.server [ポート番号] を起動
4. Questブラウザから http://[IPアドレス]:[ポート番号]/Android にアクセス確認
5. Unity Player SettingsでHTTP通信を許可
6. Questにアプリを再ビルドして再インストール
7. Questアプリ内で [Address名_標準] をロード
8. PrefabがVR空間に表示された

ここで注意すべきなのは、Unity Editor上で表示されることは実機成功を意味しない点である。EditorではPlay Mode Scriptの設定によって、Remote BundleではなくAsset Databaseから直接読めてしまう場合がある。

実機検証では、対象プラットフォーム向けにAddressables Buildしているか、Questアプリを再ビルドしているか、実機ログでエラーを見ているかを確認する必要がある。

はまった点

adbにPATHが通っていない

Quest実機ログを確認しようとして次を実行した。

Terminal window
adb logcat -s Unity

しかし、adb がコマンドとして認識されなかった。

原因は adb.exe にPATHが通っていなかったこと。Unityに同梱されているADBを直接使うことで解決した。

Terminal window
cd "[Unityエディタのインストールパス]\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\platform-tools"
.\adb.exe devices
.\adb.exe logcat -s Unity

Quest側に古いCatalogまたはキャッシュが残る

Remote Groupの設定を直しても、既にQuestへインストール済みのアプリは古いCatalogや古いAddressables状態を持っていた。

そのため、正しいRemote Load Pathを持つ状態で一度Questアプリを再ビルドし、再インストールする必要があった。

実施したことは次の通り。

1. Quest側のアプリをアンインストール
2. Addressablesを正しい設定でNew Build
3. Questアプリを再ビルド
4. Questへ再インストール
5. [Address名_A] をロード

この結果、対象Prefabが表示された。

成功時のログは次の状態だった。

Location PrimaryKey
[Address名_A]
Location InternalId
[PrefabファイルのUnity内パス]
Location ProviderId
BundledAssetProvider
Dependency InternalId
http://[IPアドレス]:[ポート番号]/Android/[Bundleファイル名].bundle
Download size
[ダウンロードサイズ]
DownloadDependencies
succeeded
LoadAssetAsync
succeeded
Spawn
[Address名_A]

サーバー側にも対象BundleへのGETが出た。

"GET /Android/[Bundleファイル名].bundle HTTP/1.1" 200

アプリ再ビルドなしの新規Prefab追加で失敗する

次に、Questアプリは再ビルドせず、[Address名_C] を追加する検証を行った。

手順は次の通り。

1. Questアプリはそのまま
2. [Prefab名_C].prefabを作成
3. [Remote Group名] に追加
4. Addressを [Address名_C] に設定
5. AddressablesでUpdate a Previous Buildを実行
6. "spawn_asset" を [Address名_C] に変更
7. Questアプリを再起動

このとき、[Address名_C] はCatalog上では見えていた。

Location PrimaryKey
[Address名_C]
Location InternalId
[PrefabファイルのUnity内パス]
Location ProviderId
BundledAssetProvider
Dependency InternalId
http://[IPアドレス]:[ポート番号]/Android/[Bundleファイル名].bundle
Download size
[ダウンロードサイズ]
DownloadDependencies
succeeded

しかし、最後にPrefabロードで失敗した。

Unable to load asset of type UnityEngine.GameObject from location [PrefabファイルのUnity内パス].

別の新規Prefabを作成して再検証しても同じだった。そのため、Prefab個体の破損ではなく、デバイス側のAssetBundleキャッシュ、Catalog更新、Bundle ID競合の可能性が高いと判断した。

安定化のために効いた設定

最終的に有効だった対策は次の3つ。

1. Unique Bundle IDsをONにする
2. UpdateCatalogsでautoCleanBundleCacheを有効化する
3. 検証用にClearDependencyCacheAsyncを追加する

Unique Bundle IDs

Addressables Asset Settingsで次を有効にした。

Addressables Asset Settings
Build
Unique Bundle IDs
ON

目的は、Catalog更新後に古いBundleと新しいBundleの内部ID競合を避けること。

ただし、これは常に無条件で有効にすべき設定ではない。Unityのドキュメントでも、実行中にCatalogを更新して既にロード済みのBundleと競合する場合には有効だが、ビルド時間や更新サイズが増える可能性があると説明されている。

今回の検証では、アプリ起動後または再起動時にRemote更新を扱うため、有効化した。

UpdateCatalogsでBundle Cacheを整理する

Catalog更新時に、不要になったBundleキャッシュを整理するようにした。

変更前のイメージ。

Addressables.UpdateCatalogs(catalogs, false);

変更後のイメージ。

Addressables.UpdateCatalogs(
true,
catalogs,
false
);

第1引数の true により、Catalog更新後に参照されなくなったBundle Cacheを削除する。

ClearDependencyCacheAsync

検証用として、対象Addressの依存Bundleキャッシュを明示的に削除してからダウンロードする処理を入れた。

Catalog更新
-> Location確認
-> Download size確認
-> ClearDependencyCacheAsync
-> DownloadDependenciesAsync
-> LoadAssetAsync

これは、古いBundleキャッシュを使って失敗しているのかを切り分けるための処理である。本番では毎回無条件に呼ぶのではなく、Catalog更新時や復旧処理時など、必要な場面に限定する設計が望ましい。

New BuildとUpdate a Previous Buildの使い分け

初期構築やAddressables構成を作り直す場合は、New Buildを使う。

Addressables Groups
Build
New Build

Questアプリを再ビルドせず、Remote側だけを更新する検証では、Update a Previous Buildを使う。

Addressables Groups
Build
Update a Previous Build

判断基準は次の通り。

アプリも再ビルドする
New Build
アプリは再ビルドしない
Update a Previous Build

最終結果

最終的に、アプリを毎回アンインストールしなくても、Addressables更新とJSON変更によるPrefab切り替えが成功した。

確認できた流れは次の通り。

Questアプリを一度正しい設定でビルドする
-> PC側でAddressablesを更新する
-> [設定JSONファイル名] を書き換える
-> Questアプリを再起動する
-> 新しいPrefabが表示される

まとめ

Remote Addressablesを使えば、Questアプリを毎回再ビルドせずに、PC側のアセット更新とJSON変更だけで表示Prefabを切り替える構成は実現できる。

ただし、安定運用には次が重要になる。

Remote GroupのLoad Pathを正しく設定する
Build Remote Catalogを有効にする
Unique Bundle IDsの必要性を判断する
UpdateCatalogs時にBundle Cacheを整理する
必要に応じてClearDependencyCacheAsyncを使う
Location、Dependency、Download sizeログを出す
サーバー側GETログを確認する

特に、アンインストールすると成功するが再起動だけでは失敗する場合は、アプリロジックだけでなく、AddressablesのCatalog、Bundle、Cacheの整合性を疑うべきである。

参考

用語リスト

  • Addressables: Unityのアセット管理機能。Addressという文字列キーでPrefabや音声などをロードできる。
  • AssetBundle: Unityのアセットを実行時に読み込める形式でまとめたファイル。
  • Catalog: AddressablesがAddressと実際のBundleやアセット位置を対応づけるための管理ファイル。
  • Remote Group: AddressablesのGroupのうち、アプリ本体ではなく外部サーバーから取得するアセットを置くGroup。
  • Load Path: 実行時にQuestアプリがBundleやCatalogを取りに行くURL。
  • Prefab: Unity上で再利用できるGameObjectのテンプレート。
  • Instantiate: Prefabから実際のGameObjectをシーン上に生成する処理。
  • Fallback Prefab: ロード失敗時に代わりに表示するPrefab。
  • Bundle Cache: 一度取得したAssetBundleを端末側に保存して再利用する仕組み。

I'm Yumeto Sugiura. XR engineer.