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

※ この記事は2021年に投稿したブログ記事をQiitaに移植したものです。Vision Proの流れがきている今のうちに移植します🙇‍♀️

## はじめに
この記事は [STYLYアドベントカレンダー Advent Calendar 2021](https://qiita.com/advent-calendar/2021/styly) 15日目の記事です。

[STYLY](https://styly.cc/ja/)には大変お世話になっておりますが、今回はSTYLYに関係ないOculus Quest開発の話になります🙇‍♀️

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

## XR Interaction Toolkitのメリット・デメリット
※知っている方は読み飛ばしてください

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

https://assetstore.unity.com/packages/tools/integration/oculus-integration-82022

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

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

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

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

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

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

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

でしょうか。[公式で出ている情報](https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@1.0/manual/index.html)も少なく難解でまとまっていません。他にもUnityユーザーマニュアルの「UnityEngine.XR」という項目に情報が出ていたりしますが、初心者ではまずここに辿り着けません。。多少翻訳がおかしくても多くの情報が載った日本語ドキュメントがヒットする[Oculus Integration](https://developer.oculus.com/documentation/unity/unity-gs-overview/)とは大きく違います。

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

https://www.youtube.com/watch?v=mqLQi2IpAL8

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

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

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

* Oculus Quest 2
* Unity 2020.3.24f1

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

https://github.com/Unity-Technologies/XR-Interaction-Toolkit-Examples

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/893a9030-1218-6151-3bc9-4db23e06e661.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/4c5f573e-7eaa-695a-e4e1-1e44ff8b6584.png)

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

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

デフォルトではRightBaseControllerからRayが出ない状態だったので、XRDirect InteractorというコンポーネントをRemoveし、XR Ray Interactorコンポーネントを新しく追加します。（[こちら](https://xrdnk.hateblo.jp/entry/2020/08/03/213914) も参考にさせていただきました）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/0642bca7-8910-2ebc-3761-33e54543f585.png)

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

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

<img width="400" alt="20211214140422.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/0ea38218-33fc-ca28-6d4c-9eac02ad654e.gif">

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

<img width="400" alt="20211214141243.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/ed25f888-05fc-db88-88f9-9ce2c89081fc.gif">

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

<img width="400" alt="20211214142524.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/86507370-b464-1d8b-f216-3f7580a8d26c.gif">

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

<img width="400" alt="20211214141825.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/c98b118c-b8d6-8d47-d236-8e52b9d688f4.gif">

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

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

<img width="400" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/3a42f24e-0694-f823-fca9-05410976ce4f.png">

## 独自機能を割り当てるには？
### デフォルト機能を少し変えたい、無効にしたい場合
少し変える程度であればスクリプトを書く必要はなく、パラメーターの変更だけでできます。

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/dffcab15-6955-ba75-cde3-1497330a7620.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/2dc019f9-fa75-1875-bbfe-154a1b2b01c0.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/7176f77d-cde9-1691-3969-3a4ef2dd84a5.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/da4e864f-9cdf-e2a8-052d-cf65ed3abccf.png)

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

![20211214170816.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/dff1c384-3c76-b688-9002-7cca99e68ab0.gif)

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

### 新しく機能を実装する場合
この情報になかなか辿り着けなかったのですが、`CommonUsage.primaryButton`のような形でボタンの状態を取得することができるようです。

https://docs.unity3d.com/ja/2019.4/ScriptReference/XR.CommonUsages.html

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

https://docs.unity3d.com/ja/current/Manual/xr_input.html


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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/c28c7d0d-09ee-4ce9-9654-03aad175abcf.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/14d0fbb9-9c25-346a-667a-6cf744f4dc77.png)

では、試してみると…

![20211214190018.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/091ec9b4-d9b9-7b49-b505-8d97e5eb8201.gif)

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/9e22572f-057d-e085-9116-ed12202b18da.png)

![20211214190944.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/c1878bef-e387-b0a7-62ce-1e09b182e48f.gif)

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

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

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

![20211214192144.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279026/be865f71-1f18-300c-d8b3-715bc762aada.gif)
↑ スナップ機能まであります

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