さっそくですがデモです。
表示したUIに対してコントローラーからRayを照射し、
トリガーを引いてUIのボタンを押すという超簡易デモです。
XR系のデバイスでアプリを作ろうとした際には、
ほぼ間違いなく行うのがレーザーポインターの実装です。
MagicLeapにおけるUIを選択可能なレーザーポインターの実装
についてだけ書きました。
どなたかのお役に立てるようメモを残しておきます。
バージョン情報
Unity 2019.3.4f1
Lumin OS v0.98.10
Lumin SDK 0.24.1
下準備
ビルドまでの環境構築は下記リンク参照です。
https://gaprot.jp/2020/05/11/magicleap-dev-environment/
まずはControllerのPrefabをHierarchyに配置します。
下記の場所にPrefabはあります。
Assets/MagicLeap/Examples/Assets/Prefabs

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

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

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

あとは、お好きな形でカーソルとなるオブジェクトを用意すれば準備完了です。
コード
実際のコードがこちらです。
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
MLInputModuleBehaviorを利用すれば、RayとUIの衝突点を取得できます。
LineRendererの終点にRayとUIの衝突点を設定すれば、
UIに衝突して途切れているような表現が可能です。
//UIにビームが当たってるときの処理
_beam.SetPosition(1, _inputModuleBehavior.PointerLineSegment.End.Value);
UIのアクティブ切り替え
UIのアクティブ、非アクティブは下記リンクで説明した方法を利用しています。
https://gaprot.jp/2020/05/14/magicleap-hand-tracking/
MLInputModuleBehaviorの便利な点
MLInputModuleBehaviorでUIを操作する処理を
実装するうえで便利な点として、
Unity側で用意されているEventSystemを利用できる点が挙げられます。
つまり、既存のコードの入力処理がEventSystemに依存していれば
変更することなくそのコードを活用できるということです。
例えば、EventSystemを利用したマウスクリックイベントで
UIの選択を実装しているプロジェクトがあるとします。
その既存プロジェクトをMagicLeap用に移植したい…となった際に
今回のように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;
}
}
次にMagicLeapのコントローラーから出した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)の処理はこのようにそのまま利用することができます。
最後に
三次元空間においては
必須と言っても過言ではないレーザーポインターだったので、
すんなり実装できるようになっていてMagicLeapに感謝です。
ハンドトラッキング時のRayを用いたUI選択も今後検証してみたいです。