さっそくですがデモです。
表示した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します)

次に、EventSystemHierarchyに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選択も今後検証してみたいです。



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる