見出し画像

【Magic Leap】レーザーポインターでUI操作

さっそくですがデモです。
表示したUIに対してコントローラーからRayを照射し、トリガーを引いてUIのボタンを押すという超簡易デモです。

画像1

XR系のデバイスでアプリを作ろうとした際には、ほぼ間違いなく行うのがレーザーポインターの実装です。
今回は、Magic Leap におけるUIを選択可能なレーザーポインターの実装についてだけ書きました。

どなたかのお役に立てるようnoteにまとめていきます。

バージョン情報
・Unity 2019.3.4f1
・Lumin OS v0.98.10
・Lumin SDK 0.24.1

下準備:環境構築する

ビルドまでの環境構築は前の記事を参照ください。

カーソルのオブジェクトを用意する

まずは Controller の PrefabをHierarchy に配置します。下記の場所に Prefab はあります。
Assets/MagicLeap/Examples/Assets/Prefabs

画像2

次に Controller の Prefab を解除して、子階層から余分なオブジェクトを削除します。最後に、画像のコンポーネントだけにします。
(画像の状態に、記事内で後述する Script を Add Component します。)

画像3

次に、EventSystem を Hierarchy に Create し、
MLInputModuleBehavior を Add Component します。

画像4

Canvas側の設定として MLInputRaycasterBehavior を追加します。

画像5

あとは、お好きな形でカーソルとなるオブジェクトを用意すれば準備完了です。

コード
実際のコードがこちらです。

using UnityEngine;
using UnityEngine.XR.MagicLeap;

/// <summary>
/// レーザーポインターでUIを選択可能な機能 コントローラーにアタッチ
/// </summary>
[RequireComponent(typeof(LineRenderer))]
public class LaserBeamForUI : MonoBehaviour
{
   [SerializeField] private MLInputModuleBehavior _inputModuleBehavior;
   [SerializeField] private GameObject _cursor;
   
   private  Transform  _controllerTransform;
   private LineRenderer _beam;
   
   private float _laserDistance = 1.0f;
   private float _laserStartPosAdjuster = 0.2f;
   private float _cursorEndPosAdjuster = 0.025f;
   private float _lineWidth = 0.001f;
   
   private void Start()
   {
       _beam = this.gameObject.GetComponent<LineRenderer>();
       
       //レーザーの太さ
       _beam.startWidth = _lineWidth;
   }
   
   private void Update()
   {
       //---------------------------------------------------------------
       // ビームに関して
       //---------------------------------------------------------------

       _controllerTransform = this.gameObject.transform;
       
       //ビームの始点
       _beam.SetPosition(0, _controllerTransform.position + (_controllerTransform.forward.normalized * _laserStartPosAdjuster));
           
       if (_inputModuleBehavior.PointerLineSegment.End.HasValue)
       {
           //UIにビームが当たってるときの処理
           _beam.SetPosition(1, _inputModuleBehavior.PointerLineSegment.End.Value);

           _cursor.SetActive(true);
               
           //カーソルが重なっていると見づらいので少しだけ手前に浮かす
           _cursor.transform.position = _inputModuleBehavior.PointerLineSegment.End.Value - (_controllerTransform.forward.normalized * _cursorEndPosAdjuster);
           _cursor.transform.localRotation = Quaternion.LookRotation(_inputModuleBehavior.PointerLineSegment.Normal, _cursor.transform.up);
       }
       else
       {
           //UIにビームが当たってない
           _cursor.SetActive(false);
           _beam.SetPosition(1, _controllerTransform.position + _controllerTransform.forward * _laserDistance);
       }
   }
}

MLInputModuleBehavior を使ってRayとUIの衝突点を取得する

MLInputModuleBehavior を利用すれば、RayとUIの衝突点を取得できます。LineRenderer の終点にRayとUIの衝突点を設定すれば、UIに衝突して途切れているような表現が可能です。

//UIにビームが当たってるときの処理
_beam.SetPosition(1, _inputModuleBehavior.PointerLineSegment.End.Value);

UIのアクティブ・非アクティブを切り替える

UIのアクティブ、非アクティブは前の記事での方法を利用しています。

MLInputModuleBehavior の便利な点

MLInputModuleBehavior でUIを操作する処理を実装するうえで便利な点として、Unity側で用意されている EventSystem を利用できる点が挙げられます。

つまり、既存のコードの入力処理が EventSystem に依存していれば変更することなくそのコードを活用できるということです。

例えば、EventSystem を利用したマウスクリックイベントでUIの選択を実装しているプロジェクトがあるとします。

その既存プロジェクトを Magic Leap 用に移植したい…となった場合は、今回のように MLInputModuleBehavior を使用すれば書き換えが発生しません。

もっと具体的にコードで例を上げて話をしてみます。

まずはマウスクリックイベントの場合です。Button側の実装を見ていきます。
下記Scriptをボタンにアタッチするだけでクリックするたびに任意のテキストのカウンターが増加します。

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// Button押下時に行いたい処理を登録 ボタンにアタッチ
/// </summary>
public class ButtonClickEvent : MonoBehaviour
{
   [SerializeField] private Text _counterText;

   private Button _button;
   private int _count;
   
   private void Start()
   {
       _button = this.gameObject.GetComponent<Button>();
       _button.onClick.AddListener(OnClickButton);
   }

   private void OnClickButton()
   {
       _count++;
       _counterText.text = "Count " + _count;
   }
}

次に Magic Leap のコントローラーから出したRayで入力する場合のButton側の実装を見ていきます。

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// Button押下時に行いたい処理を登録 ボタンにアタッチ
/// </summary>
public class ButtonClickEvent : MonoBehaviour
{
   [SerializeField] private Text _counterText;

   private Button _button;
   private int _count;
   
   private void Start()
   {
       _button = this.gameObject.GetComponent<Button>();
       _button.onClick.AddListener(OnClickButton);
   }

   private void OnClickButton()
   {
       _count++;
       _counterText.text = "Count " + _count;
   }
}

はい、ご覧の通り何も変えなくていいんです。素晴らしいですね。
入力される側(Button)の処理はこのようにそのまま利用することができます。

おわりに

三次元空間においては必須と言っても過言ではないレーザーポインターだったので、すんなり実装できるようになっていて Magic Leap に感謝です。

ハンドトラッキング時のRayを用いたUI選択も今後検証してみたいです。

この記事が気に入ったらサポートをしてみませんか?