Meta XR Core SDKを利用して、Unity GUIをコントローラーからのレーザーで操作する

# はじめに
Oculus Integrationでは、サンプル実装として**UIHelper** というPrefabが提供されていて、コントローラーからのレーザーでUIを操作することができました。
Meta XR xxx SDKとなったものでは同様のPrefabが提供されていないようだったので試してみました。

# 参考にさせて頂きました
https://blog.chellwo.com/meta-quest-unity-button

# 環境
Unity 2022.3.16f1
Meta XR Core SDK 60.0.0
Meta Quest2
Meta Quest3

# 環境構築
1. Unity Projectの作成
3D(URP)でプロジェクトを作成します

1. Android Platformに **Switch Platform**

1. **Package Manager** から **Meta XR Core SDK** を **Install**

1. **Project Settings > Oculus** の警告、推奨設定を適用
![スクリーンショット 2024-01-08 005822.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729904/b2a79598-fa55-caa0-9683-086d2d7d3128.png)
    1. **Fix All**をクリック
    1. **Apply All** をクリック

1. **Project Settings > Player** の設定を修正
    1. **Company Name** を変更
    1. **Other Settings > Identification > Override Default Package Name** のチェックを外す

# シーン作成
1. 新しいシーンを作成して、**Main Camera** を削除

1. **Oculus > Tools > Building Blocks** を開く
![スクリーンショット 2024-01-08 010648.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729904/32be9ed2-39e5-e3c5-ad62-ce88c07e96a1.png)
   1. **Camera Rig** を追加
   1. **Controller Tracking** を追加
![スクリーンショット 2024-01-08 010904.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729904/43d5fed5-88aa-0d2d-8d2d-6e4c4f41e542.png)

1. **Unity GUI** を利用して **Text** と **Button** を配置
※ VR向けに **Canvas** の **Render Mode** は **World Space** に設定
![スクリーンショット 2024-01-08 011825.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729904/31a66d9c-497b-e0c2-bac8-1902a159aa9b.png)

1. **Canvas** オブジェクトに対して、Meta QuestのUIインタラクションを処理できるように設定を追加
   1. **GraphicsRaycaster** コンポーネントを無効化
   1. **OVRRaycaster** コンポーネントを追加
![スクリーンショット 2024-01-08 012558.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729904/53e9e950-0df4-01bd-05eb-fd30a13e79d8.png)

1. **EventSystem** オブジェクトに対して、Meta QuestのUIインタラクションを処理できるように設定を追加
   1. **StandaloneInputModule** コンポーネントを無効化
   1. **OVRInputModule** コンポーネントを追加
![スクリーンショット 2024-01-08 013111.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729904/66ab5b44-93c2-529f-ac53-0d798e3a91f5.png)

1. レーザーの先のポインターを作成
   1. 空のGameObjectとしてOVRGazePointer(**名前は自由**)を作成
   1. 空のGameObjectとしてGazeIcon(**名前はこの名前でないとNG**)を作成し、上で作成したOVRGazePointerの子オブジェクトにする
   1. GazeIconの子オブジェクトとして、レーザーの先のポインターとなるオブジェクトを追加
   1. OVRGazePointerオブジェクトに**OVRGazePointer** コンポーネントを追加
       1. **Depth Scale Mulitplier** の項目でポインターの大きさを調整
       1. **Ray Transform** の項目でレーザーの始点となる位置を設定
 ![スクリーンショット 2024-01-08 014855.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729904/4c9898f4-9a01-cc0f-6069-b2ea73d0c3cf.png)
    1. **EventSystem** オブジェクトの **OVRInputModule** コンポーネントを設定
       1. **Ray Transform** の項目でレーザーの始点となる位置を設定
       1. **Cursor** の項目に **OVRGazePointer** オブジェクトを設定
![スクリーンショット 2024-01-08 022851.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729904/30a5db08-d71d-f5e5-cb51-ee2cd57c7a63.png)


1. レーザーを作成
   1. 空のGameObjectとしてOVRPointerVisualizer(**名前は自由**)を作成 
   1. **LineRenderer** コンポーネントを追加
      1. **Width** の項目でレーザーの太さを調整
      1. **Materials** の項目でレーザーの見た目を調整
   1. **OVRPointerVisualizer** コンポーネントを追加
      1. **Ray Transform** の項目でレーザーの始点となる位置を設定
      1. **Line Pointer** の項目で上で追加した **LineRenderer** コンポーネントを設定
![スクリーンショット 2024-01-08 020100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2729904/87f2bb46-45ba-3963-0914-0bbbd87c5ef9.png)
      ※今回、**OVRPointerVisualizer** コンポーネントは、デフォルトで提供されているものをカスタマイズして、ポインターが表示されている場合は、LineRendererの長さをポインターに合わせています。
      
   ```csharp:OVRPointerVisualizer.cs
   using UnityEngine;

   namespace UIControllerLaserPointer
   {
    public class OVRPointerVisualizer : MonoBehaviour
    {
        [Tooltip("Object which points with Z axis. E.g. CentreEyeAnchor from OVRCameraRig")]
        public Transform rayTransform;

        [Header("Visual Elements")] [Tooltip("Line Renderer used to draw selection ray.")]
        public LineRenderer linePointer = null;

        [Tooltip("Visually, how far out should the ray be drawn.")]
        public float rayDrawDistance = 2.5f;
        
        [SerializeField] private OVRGazePointer _ovrGazePointer = null;

        void LateUpdate()
        {
            linePointer.enabled = (OVRInput.GetActiveController() == OVRInput.Controller.Touch);
            Ray ray = new Ray(rayTransform.position, rayTransform.forward);
            linePointer.SetPosition(0, ray.origin);
            
            if (!_ovrGazePointer.hidden)
            {
                linePointer.SetPosition(1, _ovrGazePointer.transform.position);
                return;
            }
            linePointer.SetPosition(1, ray.origin + ray.direction * rayDrawDistance);
        }
    }
   }
   ```

# 実行結果
※　ボタンをクリックした際の処理の説明は割愛します

https://youtube.com/shorts/qDu5UcZntdE?feature=share