XR Interaction Toolkitのコントローラー操作を攻略する

はじめに

この記事は STYLYアドベントカレンダー Advent Calendar 202115日目の記事です。

STYLYには大変お世話になっておりますが、今回はSTYLYに関係ないOculus Quest開発の話になります🙇‍♀️

テーマはUnity + XR Interaction Toolkitでのコントローラー操作について。今でこそ情報がよく纏まった記事もいくつか出ていますが、開発していた当初(主に1年〜半年前)はあまり情報がなく、特に「ものを掴む・移動する は簡単に実装できたけど、コントローラーに独自の機能を割り当てるにはどうすればいいんだ??」となっていたので一度まとめることにしました。

XR Interaction Toolkitのメリット・デメリット

※知っている方は読み飛ばしてください

2年前(2019)まではOculus Quest向けの開発といえばOculus公式の提供しているUnityアセット「Oculus Integration」の一強でした。

assetstore.unity.com

しかし2020年頃からUnity純正のツールキットとして「XR Interaction Toolkit」が登場します。私はこれをXRKaigi 2020の講演で知り、研究で使う実験環境を開発するために早速導入しました。

使ってみると、やはりメリットとしては

  • 「ものを掴む」「VR空間内を移動する」といったVRでの基本的な操作を非常に簡単に実装できる
  • 1つのプロジェクトでQuest、Viveなど複数のVRヘッドセットに対応できる

という点が挙げられると思います。

Oculus Integrationを使って初めてVR開発をしたときは、容量が重くビルド時間の長さに悩まされたり、本来なら利用できるはずのPrefabが利用できずやむなくスクリプトVR内を歩き回れるようにしたり、ただUIをクリックしたいだけなのにコントローラーのボタン取得に苦戦したりと様々な苦労をした覚えがあります。XR Interaction Toolkitでは最初から「テレポーテーション」「UIクリック」「ものを掴む」の役割を持つコンポーネントがあり、それを使いやすくしたサンプルプロジェクトもあり、だいぶ実装が楽になりました。これだけでも利用する価値は十分にあると思います。

一方のデメリットとしては

  • ベータ版であり、情報が少ない(=独自の操作を実装するのが少々大変

でしょうか。公式で出ている情報も少なく難解でまとまっていません。他にもUnityユーザーマニュアルの「UnityEngine.XR」という項目に情報が出ていたりしますが、初心者ではまずここに辿り着けません。。多少翻訳がおかしくても多くの情報が載った日本語ドキュメントがヒットするOculus Integrationとは大きく違います。

しかしこの情報が少ない中で、XR Kaigi 2020・2021で登壇されていたUnity Technologies Japan 高橋さんの講演は非常にわかりやすく、XR Interaction Toolkitを触ろうかなと思っている方にはぜひお勧めしたいです。

www.youtube.com

サンプルのデフォルト操作はどうなっている?

前置きが長くなりましたが、XR Interaction Toolkitのサンプルのコントローラー操作がどうなっているのかまず見ていきたいと思います。

開発環境は以下の通りです。

  • Oculus Quest 2
  • Unity 2020.3.24f1

今回はサンプルプロジェクトを使っていきます。このサンプルプロジェクトは優秀で、簡単なプロジェクトを作るだけであれば0からシーンを構築するよりもサンプルプロジェクトを編集して作った方が早いです。※サンプルプロジェクトをベースにしているため、0からシーンを構築する方は「新しく機能を実装する場合」の章まで読み飛ばしてください。

github.com

上記GitHubからプロジェクトをダウンロードし、Unityで開いたら、 Assets > Scenes > World Interaction Demo というシーンを開きます。

f:id:nubonubo:20211214134414p:plain

Inspectorから、XRRigDemo(=カメラにあたるもの)の中身を展開してみましょう。

f:id:nubonubo:20211214134132p:plain

※今回、LeftHandは使わないので非表示にしています。

RightHandにはRightBaseControllerという主に物体を掴んだりUI操作に使うコントローラーと、RightTeleportControllerという主にテレポーテーション移動に使うコントローラーが割り当てられています。

デフォルトではRightBaseControllerからRayが出ない状態だったので、XRDirect InteractorというコンポーネントをRemoveし、XR Ray Interactorコンポーネントを新しく追加します。(こちら も参考にさせていただきました)

f:id:nubonubo:20211214135355p:plain

この状態でビルドすると…(ビルドの仕方は上に貼った動画をご参照ください)

直線のRayが常に出ていて、人差し指のトリガーでUIをクリックできます。

f:id:nubonubo:20211214140422g:plain:w300

スティックボタンを上に倒す、もしくはAボタンでテレポートモード=曲線のRayに切り替わり、放すとRayの着地点に移動できます。

f:id:nubonubo:20211214141243g:plain:w300

スティックボタンを横に倒すことで視界が左右に45度ずつ回転します。スティックボタンを下に倒すと180度回転します。

f:id:nubonubo:20211214142524g:plain:w300

中指のトリガーを押すことでものを掴むことができます(さらに掴んだ状態で人差し指トリガーを押すことで発火するアクションも設定できるようです)。

f:id:nubonubo:20211214141825g:plain:w300

サンプルを使うだけでこんなに簡単に実装できてしまいました。

ただ、コントローラーのほとんどのボタンには「すでに機能が設定されている」わけです。

f:id:nubonubo:20211214192919j:plain:w350

独自機能を割り当てるには?

デフォルト機能を少し変えたい、無効にしたい場合

少し変える程度であればスクリプトを書く必要はなく、パラメーターの変更だけでできます。

例えば「スティックボタンを下に倒すと180度回転する」機能をオフにしたい場合。

RightBaseControllerやRightTeleportControllerにアタッチされているXRController(Action-based)コンポーネントの「Reference」で設定されている「XRI RightHand/~~~」をどれでもいいのでクリックします。

f:id:nubonubo:20211214151237p:plain

すると新しくXRI Default Input Actionsというウィンドウが開きます。どうやらHMD本体やコントローラーのあらゆるインプットについてはこちらで管理されているようです。

f:id:nubonubo:20211214152018p:plain

スティックボタンでの回転を管理しているのはこの中のTurn > Primary 2DAxis です。右側の「Interactions」にDirectionsがSouthとして設定されているものがあるので、これをNoneにすると、スティックボタンを下に倒す挙動をオフにできます。変更したら上の「Save Asset」ボタンを押すのを忘れないようにしましょう。

f:id:nubonubo:20211214153759p:plain

続いて、「スティックボタンを横に動かした時の回転を連続的にしたい」場合。デフォルトではVR酔い防止のために回転は45度ずつしか動かせなくなっていますが、回転角を変えたり、連続的に動くように変更することもできます。この回転はXRRigDemoのSnap Turn Providerで定義されています(ここで、先程のXRI RightHandが呼び出されています)。

f:id:nubonubo:20211214170151p:plain

Turn Amountを2、Debounce Time(=この時間の間は何回入力しても1回とカウントされる)を0.05に設定してみます。

f:id:nubonubo:20211214170816g:plain

スムーズに動かすことができるようになりました。なお、これより小さい値を設定してしまうとVR酔いが発生してしまうと思います。

新しく機能を実装する場合

この情報になかなか辿り着けなかったのですが、CommonUsage.primaryButtonのような形でボタンの状態を取得することができるようです。

docs.unity3d.com

コントローラーとの対応表はこちら。

docs.unity3d.com

今回上のマニュアルの、「primaryButtonの例」に記載されているコードで試してみました。どうやらこれは、primaryButton(=QuestではAボタン)を押すと、配置されたCubeが回転するというコードのようです。

適当なGameObjectにPrimaryButtonWatcherクラスを割り当て、ワールド上に配置したCubeにPrimaryReactorクラスを割り当て、紐付けます。

f:id:nubonubo:20211214185530p:plain

f:id:nubonubo:20211214185539p:plain

では、試してみると…

f:id:nubonubo:20211214190018g:plain

おわかりいただけたでしょうか。。確かにCubeは回転しているのですが、Aボタンを離した瞬間、Aボタンに元々割り当てられている「テレポートモードへの切り替え」が発動してしまっています。

つまり元々割り当てられている機能を上書きしてくれるわけではなくて、元々割り当てられている機能は無効化しておく必要があるんですね。

なので一つ前の章で書いた通り、XRI Default Input Actionsから該当箇所を無効化しておきましょう。

f:id:nubonubo:20211214190451p:plain

f:id:nubonubo:20211214190944g:plain

回転だけできるようになりました!

こんな複雑なコード書かなくてももっと簡単に書けるような気がしますが、要検証ですね…(試しにちらっとやったらうまくいかなかったので、今回は諦めます)

まとめ

XR Interaction Toolkit、まだまだ情報が少なくはありますが、デフォルトの操作を使いこなして+スクリプトで独自の操作を実装できれば、操作周りで大抵のやりたいことは実現できるんじゃないでしょうか。デフォルトの機能が本当に充実しているので、使いやすいと思います。

f:id:nubonubo:20211214192144g:plain
スナップ機能まである

もっとユーザーが増えて情報が増えることを願っています!