この記事のゴール

UIでオブジェクトの位置と角度を微調整し,保存したい状態になったら空間アンカーを保存する.
更に,保存した空間アンカーを読み込んで保存時の位置と角度を復元する.

後述するマーカー式位置合わせの方法が見つけられなかった私にChatGPTさんが

ChatGPTさん

UIで動的にオブジェクトの位置と角度を微調整して位置合わせすればいい,ジリ貧だけど確実

とご助言をくださったため,その方法で実装したときの記録.※アイコンはChatGPT4oさんに「ChatGPT風の抽象AIアイコンを、緑と白でお願いします」と言って生成してもらったものです.
空間アンカー関連は非同期処理や自動紐づけが多く,ところどころ「待つ」や「無効化する」処理を挟まないと思うような挙動をしなかったので高難易度.

UI作成

MetaQuest3でVRデモ 4. オブジェクトを初期位置にリスポーンするUI作成と同様の方法で,以下のレイアウトのUIを作成した.まだボタンや入力欄には処理を入れていない.あと座標系の3Dモデルは自作で,これもOVRCameraRig > TrackingSpace > CenterEyeAnchorの子になる位置に配置.


また,Sceneを有効化する必要がある.MetaQuest3でMRデモ 0. 予備知識と環境構築を参照.

Input Fieldを有効にする

InputFieldは最も上にある階層のオブジェクト(上図ではInputField_meterとInputField_degree)のInspector > TextMeshPro – Input Field内のTextに初期値を入力する.子のTextAreaやText(TMP)のTextは空白でいい.
また,余計な型変換とかにひっかかると面倒なのと,バーチャルキーボードがテンキーになるので,その下のInput Field Settings内のContent Typeを「Decimal Number」にする.
また,バーチャルキーボードを有効にするため,Camera RigのInspectorを開き,OVR Managerを展開し,Quest Features > General内のRequires System Keybordにチェックを入れる.

上記の処理をすれば,Input FieldをRayやPokeで押すとバーチャルテンキーが表示され数値が入力出来るようになる.

C#でスクリプト作成

UIで設定したボタンに追加するイベントをスクリプトで作成し,空のオブジェクトに追加し,[SerializeField]に対応したオブジェクトを入れる.
オブジェクトの位置情報やUIの座標値等の表記を更新するための処理をまとめた「updatetrans.cs」と,空間アンカーの保存・読み込みの処理をまとめた「saveload_spatialanchor.cs」の2つを作成.

オブジェクトの位置情報やUIの座標値等の表記を更新するための処理(updatetrans.cs)

このコード(全文は本節の最後の記載)では,以下の処理を実行している.

void Start()関数
obj(Cubeなど位置合わせするオブジェクト,Inspectorで指定)のTransfromと,InputField_meterとInputField_degreeの初期値を取得し,対応したUIのTextMeshPro(Inspectorで指定)の中身を更新する.

public async void reset()関数
「Reset」ボタンをクリックしたら呼び出される関数.Inspector内で呼び出すための設定が必要.
Transformをvoid Start()関数で定義したinitialPositionとinitialRotationの値に戻し,UIの中身にも反映させる.空間アンカーを削除する工程が含まれ「待ち」の処理が入る関係で,async void関数としないとエラーが出る.

public void ChangeRate()関数
InputField_meterとInputField_degreeに値を入力したら呼び出される関数.Inspector内で呼び出すための設定が必要.
入力値の文字列を取得してfloat値に変換し,updatetransクラス内の変数frate_trans, frate_rotの値を更新する.

public void ChangeTransform(string label)関数
各パラメータを増減させるボタンをクリックしたら呼び出される関数.Inspector内で呼び出すための設定が必要.
引数(string label)の内容によってどの値を増減させるか判定させている.X座標の値を減少させるボタンの場合は以下のとおり.
この関数では対応するUIのTextMeshProの中身を変えただけで,オブジェクトのTransformの更新は次のpublic async void UpdateTransform()関数で行われる.

public async void UpdateTransform()関数
public void ChangeTransform(string label)関数実行時とsaveload_spatialanchor.csのpublic async void LoadSpatialAnchor()関数を実行時に呼び出される関数.
空間アンカーが追加されていると保存時のTransform情報と同期するようで,一瞬新しいTransformに更新されるがすぐに前の状態に戻ってしまう.そのため,まず空間アンカーを削除して,TextMeshProの文字列を読み取ってfloat値に変換しTransformに反映している.

static int GetDecimalDigits(float value)関数
UIでの座標値や回転角の表示がぐちゃぐちゃにならないよう,frate_trans, frate_rotの値に基づいた有効桁数で丸めるためにこれらの桁数を取得するための関数.次のpublic void UpdateText()関数で使用する.

public void UpdateText()関数
saveload_spatialanchor.csのpublic async void LoadSpatialAnchor()関数を実行時に呼び出される関数.向こうではObjectのTransform更新だけ行うため,UIの中身をこちらで更新する必要がある.

updatetrans.cs 全文

空間アンカーの保存・読み込みの処理(saveload_spatialanchor.cs)

このコード(全文は本節の最後の記載)では,以下の処理を実行している.

public async void SaveSpatialAnchor()関数
「Save」ボタンをクリックしたら呼び出される関数.Inspector内で呼び出すための設定が必要.
spatialAnchor = obj.AddComponent();でobj(Cubeなど位置合わせするオブジェクト,Inspectorで指定)に空間アンカーを追加している.空間アンカーの作成にawait処理を使うことができなかったので,spatialAnchor.Created=Trueになるか50ミリ秒経過するまでwhileループを回して「待つ」処理を追加している.これがないと空間アンカーが作成されていない状態でこの後のコードが実行されてしまう恐れがある.
bool saveResult = await spatialAnchor.SaveAnchorAsync();で空間アンカーの保存ができ,spatialAnchor.Uuidをstring型に変換してPlayerPrefsに保存する.上手くいけば右側のUUID:欄が32桁の数値に更新される.

public async void LoadSpatialAnchor()関数
「Load」ボタンをクリックしたら呼び出される関数.Inspector内で呼び出すための設定が必要.
1つしか呼び出さないのだが,OVRSpatialAnchor.LoadUnboundAnchorsAsyncがunboundAnchorsがをList型でないと引数にできなかったので,unboundAnchorsはList型で定義している.
空間アンカーをロード後,objにそれを追加する.これ以降の処理の順番は必ずこのコードのとおりに実行する.順番が前後するとうまくいかなかった.
最後の2行はUIの表示を更新するための処理.

saveload_spatialanchor.cs 全文

スクリプトをオブジェクトに追加

Hierarchyで右クリックし,Create Emptyをクリックする.これを2回繰り返し.
それぞれ名前は「Contoll_trans」「SaveLoad_SpatialAnchor」とする.

Contoll_transにはupdatetrans.csをコンポーネントとして追加し,public関数は以下のように設定する.

SaveLoad_SpatialAnchorにはsaveload_spatialanchor.csをコンポーネントとして追加し,public関数は以下のように設定する.

アプリの実行

コントローラーのRayかPokeで各パラメータの増減ボタン「<」「>」を押せば,Rate of Changeの値に応じて対応した数値が増減し,オブジェクトのTransformにも反映される.「Reset」ボタンを押せば,Unityで設定したTransformの値に戻る.

保存したい状態になったら「Save」を押すと,UUID表示と同時に保存される.
UUIDが保存された状態で「Load」を押すと,最後に保存したUUIDが表示されTransformが読み込まれる.一度アプリを終了して再度別の場所で立ち上げても,UUIDは残っているため読み込み可能である.