UnityでHMDとかコントローラとかの座標を取得する

 タイトルがなんか自動翻訳されたサイトみたい。

 VR機器( VIVE PRO EYE )を手に入れたので、 Unity( 2020.3.11f1 ) でいろいろやってみようと以下の記事群を参考に触っていた。

https://qiita.com/RyoyaHase/items/5a173b7acbdf9aa2cfff

 この第7回では、 HMD やコントローラの座標は、

HMDPosition = InputTracking.GetLocalPosition(XRNode.Head);

のように InputTracking を用いて取得していた。問題なく実行されたのだが、私はエディタに警告として表示されていた

'InputTracking.GetLocalPosition(XRNode)' は旧式です

がとても気になった。これが古い記法であることは分かったが、 Unity も VR も C# も初めての私は何をどうすれば……という感じであったため、

Please use InputDevice.TryGetFeatureValue with the CommonUsages.devicePosition usage instead

を基にリファレンスに潜った。

2020.3 のリファレンスにおける InputTracking には GetLocalPosition 自体が記載されておらず、2018.4 で確認した。

この記事は「座標とか取得したときに色々困ったから記事にしとくか」という動機で書いているのだが、時間がたってから書いているためどこで悩んだのか全く思い出せないでいる。なので、実際にどうか書けばいいのか、その自分なりの答えを残しておく(なんでその時に書いておかなかったのだろう)。

 警告文によれば、代わりに InputDevice.TryGetFeatureValue を使えとのことであった。この関数は、引数にとった usage に応じた機器の情報を取得するものである。返り値は取得できたら True の bool である。

 私はここで HMD とコントローラそれぞれの座標と向きを取得したいのだが、その特定のパラメータを取得するにはどうすればよいのか。説明によれば XRNode で色々指定するようだ。

 XRNode では識別できるデバイスが変数として用意されている。またそこの説明に、なぜ GetLocalPosition が使えない(非推奨)なのかも書いてあった。簡単に言えば、デバイスは集合体として扱われるため、単体のデバイスを引数とする GetLocalPosition は向かない、みたいな感じらしい( C# の理念等は知らないため、この翻訳であっているのかはわからない)。また、ここでは InputDevice.GetNodeStates を代わりに使うよう指示されている。

 InputTracking.GetNodeStates は現在接続されている XRNodes の要素をリストにして返すらしい。まあ確かに、特定のデバイスを引数にとる場合、実際にそれが接続されている保証はなく動作が不安定になるだろう。接続されているものがリストで提示されていれば、その時点で確定するわけだから、安定した動作をすることができる。というわけで、早速接続されているデバイスを取得してみよう。

List<XRNodeState> DevStat = new List<XRNodeState>();
InputTracking.GetNodeStates(DevStat);

ここで XRNodeState は、そのままの通りで XRNode の状態に関するクラスである。これが扱う状態は、

  • 加速度(acceleration)
  • 角加速度(angularAcceleration)
  • 角運動量(angularVelocity)
  • ノードタイプ(nodeType)
  • 座標(position)
  • 角度(クォータニオン)(rotation)
  • トラッキングフラッグ(tracked)
  • 識別子(uniqueID)
  • 速さ(velocity)

である。私が欲しいのは座標と向きであるから、ここでいう position と rotation である。また、このクラスには、加速度、角加速度、角運動量、座標、角度、速さを取得する TryGet~ の関数が定義されている。

 ここからが本題である。特定のデバイスの情報を取得するには、先ほど定義した接続中デバイスリストから、それに合う(それに含まれていると期待される)デバイスを選択し、そこから情報を取得することになる。リストであるから、 for 文的なので回して、内部で nodeType を比較すればよさそうだ。

 この先は、以下のサイトを参考にしている。

https://jmpelletier.com/ja/unity%E3%81%A7oculus%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%A9%E3%81%AE%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B/

ここでは、先述の手法( for 文的なので……)を実践したものが使われている。良かった。

C# 初心者である私は「リスト」と表現しているが、これは正しくは「コレクション」だそうだ。そしてコレクションは列挙できないから、 for が使えず、そのために用意された foreach を使うことになる。

foreach(XRNodeState s in DevStat) {
            if(s.nodeType == XRNode.Head) {
                s.TryGetPosition(out HMDPosition);
                s.TryGetRotation(out HMDRotation);
            }else if(s.nodeType == XRNode.LeftHand) {
                s.TryGetPosition(out LeftHandPosition);
                s.TryGetRotation(out LeftHandRotation);
            }else if(s.nodeType == XRNode.RightHand) {
                s.TryGetPosition(out RightHandPosition);
                s.TryGetRotation(out RightHandRotation);
            }
       }

foreach は引数に (型名) (内部変数) in (コレクション) をとる。ここでは、デバイスのリストを比較したいから、 (XRNodeState s in DevStat) という形になっている。また、接続されたうち HMD とコントローラの情報が欲しいから、一度に nodeType: Head (HMD) と nodeType: LeftHand (左コントローラ) と nodeType: RightHand (右コントローラ) を比較している(リンク先では break を入れているが、この場合どこに挟めばいいかわからず入れていない)。座標と角度がほしいから、 TryGetPosition と TryGetRotation を使っている。引数は Vector と Quaternion だ。これによって、指定の変数にデバイスの情報を入れることができる。

 これが正しいかはわからないが、 Qiita での教科書にしていた記事で期待される通りの動作を行うことができた。