【Oculus Quest 開発TIPS】テレポートやGUI操作を簡単に解決する

# 要約
Oculus Quest での開発初期に、こういうのとりあえず実装したいみたいになりませんか？

* テレポートしたい
* 水平移動したい
* レーザーでGUIを操作したい
* バーチャルキーボードで入力したい
* 3Dオブジェクトを選択したい

Oculus Integration に含まれているサンプルを利用することで簡単に解決できます。
解決方法だけでなく、ある程度の範囲が調整できるので、それらも含めた紹介です。

# 前提
Name               | Version
-------------------|---------------
Unity              | 2019.4.9f1
Oculus Integration | 25.0
Oculus Quest       | 1 or 2

# テレポートしたい
いきなり他の方の実装方法の紹介になりますが、
[Oculus Integration を用いた テレポート / ワープ の実装方法 [Oculus Quest]](https://xrdnk.hateblo.jp/entry/2020/03/25/002404) が詳細でわかりやすいです。

## 操作方法
* 右手のスティックを倒すとテレポート後のポイントが表示され、スティックを離すとテレポートします。
* 右手のスティックを倒している方向で、テレポート後の向きが決まります。

## サンプルでこんな調整もできます
* テレポートできる最大距離を設定することが可能です。
    * TeleportAimHandlerLaser の Range を変更してください。単位はメートルです。
* 直線レーザーだけでなく、釣り竿式の間がレーザーも出力できます。
    * TeleportAimHandlerLaser の代わりに、TeleportAimHandlerParabolic を設定してください。
* テレポート後の回転はなくすこともできます。
    * TeleportOrientationHandlerThumstic の Thumbstick に None を設定してください。

## 補足
* 上記手順で実行すると正常に動いているように見えますが、裏で NullReferenceException が出ていました。  
CharacterCameraConstraint で OVRScreenFade の参照がないので出ているエラーでした。同じオブジェクトに OVRScreenFade を追加して参照を入れてあげれ解決されます。

# 水平移動したい
`テレポートの設定方法` がされている前提です。
SimpleCapsuleWithStickMovment の Speed を0より大きい値で設定してあげてください。
左手のスティックで移動できるようになります。

![2021-03-26_19h39_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506075/6d551935-91ce-db0e-5ab9-a16213d1497a.png)

## 操作方法
* 左手のスティックを倒すと、前後左右に水平移動します。

## サンプルでこんな調整もできます
Speed と書いてある通り、値を大きくすると移動スピードが大きくなります。
水平移動はVR酔いしやすいので取扱注意です。
個人的には Speed=1 とか遅すぎても酔いやすい気がしています。

## おまけ
* デフォルトの設定では SimpleCapsuleWithStickMovement の RotationAngle に 45 が設定されています。これは右手のスティックを左右に倒すと回転する角度です。0 にすることでオフにすることができます。

# レーザーでGUIを操作したい
1. 操作したい uGUI のキャンバスに OVRRayCaster を設定します。  
![2021-03-29_10h18_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506075/c21db907-b787-f8a2-7663-c6293138a355.png)
2. Oculus/SmapleFramework/Core/DebugUI/Prefabs/UIHelpers を操作したい手に入れます。  
![2021-03-29_23h09_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506075/88f03123-3f8d-5554-a48d-7795ad14de56.png)
3. UIHelpers を展開するとその下に EventSystem が含まれています。これが VR 用の EventSystem となります。
4. EventSystem に設定されている OVRInputModule に以下設定を入れます。
![2021-03-29_23h11_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506075/8e5acefd-138f-2460-3f1a-2d3e7adb3156.png)
5. 既存の EventSystem は削除します。

## 操作方法
* OVRRayCaster を設定したキャンバス上に、通常の uGUI のつもりでボタン等を設置していくだけです。
* UIHelpers だけでレーザーも出るようになっています。
* 右手から出ているレーザーでキャンバス上のボタンを指し示した状態で、右手のトリガーをひけば、ボタンをクリックできます。

## サンプルでこんな調整もできます
* 右手に入れたUIHelpersを左手に入れれば左手で操作できます。
* Secondary -> Primary に変更すれば左手で決定できます。

## おまけ
* Canvas を左手、あるいは頭と連動させることで、移動してもついてくるよなUIも作れます。（サンプルにはありませんが・・・）

# バーチャルキーボードで入力したい。
レーザーでGUIを操作したいができている前提です。

1. OVRCameraRig に設定されている、OVRCameraManagaer にある以下設定のチェックボックスを ON にします。
![2021-03-29_23h29_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506075/a262fed5-87af-ab9c-dbb0-316b24e382af.png)
InputField への Text 入力は超簡単

以上！！

## 操作方法
* uGUIにインプットフィールドを作っておいて、レーザー選択（トリガーを押す）とバーチャルキーボードが表示されます
    * 私が実行した限り Go とかのボタンは効かず、元に戻るときは一番下のキーボードみたいなボタンを押していました

# 3Dオブジェクトを選択したい？

## VRPhyisicsRaycaster を利用して選択する方法も
Oculus が準備している方式で、サンプルにも含まれています。ただ以下理由から私自身は採用するのをやめました。

* 基になっている UnityEngine.EventSystems が Unity 2019.4 版からドキュメントがありません。これ以上伸びないかもしれないAPIを利用するのは負債にしかならない気がしました。
* Ray がヒットされるほうにイベントを書くので、GUI 的な使い方なら問題ないかなと思いますが、ヒットさせたほうで何かしたい時にはコードが歪になるかなと思いました。

## それでもやるなら
レーザーでGUIを操作したいができている前提です。

1. OVRCameraRig が入っているコンポーネントのころに、OVRPhysicsRaycaster を追加します。
    * OVRPhysicsRaycaster が OVRCameraRig を前提としています
2. 以下コードを書いて、レイキャスト `されるほう（するほうじゃなくてされるほう）` に設定します。

```C#
public class PointerButton : MonoBehaviour, IPointerEnterHandler, IPointerClickHandler, IPointerExitHandler
{
    [SerializeField] private TextMeshProUGUI text1;
    [SerializeField] private TextMeshProUGUI text2;
      
    public void OnPointerEnter(PointerEventData eventData)
    {
        text1.text = "Raycast hit :) at Plane";
    }
    public void OnPointerExit(PointerEventData eventData)
    {
        text1.text = "Raycast out :( at Plane";
    }
    public void OnPointerClick(PointerEventData eventData)
    {
        var obj = eventData.pointerCurrentRaycast.gameObject;
        var pos = eventData.pointerCurrentRaycast.worldPosition;
        text2.text = $"{go.name}({obj}) is Clicked!";
    }
}
```

## 参考
[How to check if Oculus Go controller on OVRInputModule is clicking on GUI object or not]((https://stackoverflow.com/questions/57884510/how-to-check-if-oculus-go-controller-on-ovrinputmodule-is-clicking-on-gui-object))
> Furthermore, whatever "Event Mask" you select for your OVRPhysicsRaycaster in the camera rig, any normal (non-UI) 3D object that is in that layer will detect the same IPointerEnterHandler and IPointer...Handler events just like a UI button, so you can use the same code for those objects as well, not just the UI objects.

## 他の方法で選択したい
こちらの [Oculus Goでコントローラ操作(3DオブジェクトとuGUI)](https://qiita.com/okyk/items/efca6be5bc874333e8eb#ray%E3%81%8C%E3%82%B2%E3%83%BC%E3%83%A0%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AB%E5%BD%93%E3%81%9F%E3%81%A3%E3%81%9F%E3%82%89%E4%BD%95%E3%81%8B%E3%81%99%E3%82%8B) を参考にすればとわかりやすいかと思います。

## 他参考
* [How do I use Oculus Quest controllers for clicking on Buttons?](https://stackoverflow.com/questions/62644877/how-do-i-use-oculus-quest-controllers-for-clicking-on-buttons) という stack overflow の記事
* [IPointerClickHandler](https://stackoverflow.com/questions/57884510/how-to-check-if-oculus-go-controller-on-ovrinputmodule-is-clicking-on-gui-object) など