つくるの大好き。

つくるのが大好きな人の記録。

MRTK v2.4.0 ボタンクリックイベントの送信元を取得する (Update)

しばらくアウトプットする暇も取れない感じでしたが久しぶりの投稿(小ネタ)です!

MRTKのUIコンポーネントを利用し、複数ボタンのOnClickイベントを同一のイベントハンドラにリンクさせました。
この時OnClickイベントはイベント送信元の情報をパラメータで渡すようにはなっていないため、どのボタンからイベントが送信されたか判断できず困ってしまいました。
ボタンごとにイベントハンドラを書くなんて嫌ですよね笑!

解決した方法をサンプルプロジェクトとしてシェアします。
(もっとスマートな方法が用意されているんじゃないのかなぁ。。)

2020/06/07 Update

すぐにホロラボの @deco_c_ 君がヒントをくれました! 確かにこの手があったか~。シンプルで素敵。

こちらのケースもサンプルプロジェクトに追加しました。

問題

OnClick()イベントはパラメータを持たないので送信元の情報などは一切通知されません。
(senderが欲しいだけだったのですがかなり調べることにww)

f:id:peugeot-106-s16:20200606225317p:plain https://github.com/microsoft/MixedRealityToolkit-Unity/blob/72ea542dff0ab5cbd229d9c93a4f138f37172cc5/Assets/MRTK/SDK/Features/UX/Scripts/Buttons/ButtonConfigHelper.cs#L87-L99

case01: Post detect

サンプルプロジェクトの構成

f:id:peugeot-106-s16:20200606223602p:plain
Multi buttons click event are linked to an event handler

プロジェクト一式
github.com

解決策

イベントハンドラ内でFocusProviderよりフォーカスが当たっているInteractableを取得する。
そこにたどり着くまでに色々とステップがある(下記 GetEventSender() メソッド参照)

using Microsoft.MixedReality.Toolkit;
using Microsoft.MixedReality.Toolkit.UI;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    public ToolTip toolTip;

    public void ButtonClicked()
    {
        this.toolTip.ToolTipText = this.GetEventSender()?.name;
    }

    private GameObject GetEventSender()
    {
        foreach (var inputSource in CoreServices.InputSystem.DetectedInputSources)
        {
            foreach (var pointer in inputSource.Pointers)
            {
                if (pointer.IsInteractionEnabled)
                {
                    var sender = CoreServices.InputSystem.FocusProvider.GetFocusedObject(pointer);
                    if (sender?.GetComponent<Interactable>() != null)
                    {
                        return sender;
                    }
                }
            }
        }
        return null;
    }
}

https://github.com/satoshi-maemoto/MRTKButtonSenderSample/blob/master/Assets/Scripts/NewBehaviourScript.cs

こんな感じに動きます。


MRTK Button Sender Sample

case02: Event wrap

サンプルプロジェクトの構成

f:id:peugeot-106-s16:20200607110420p:plain
OnClick extender are attached to each buttons

解決策

各ボタンにアタッチした ClickEventRouter コンポーネントが、ButtonConfigHelper.OnClickイベントをフックして自オブジェクトをパラメータに追加したかたちでInvoke()する。

using Microsoft.MixedReality.Toolkit.UI;
using System;
using UnityEngine;
using UnityEngine.Events;

public class ClickEventRouter : MonoBehaviour
{
    [Serializable]
    public class InternalButtonClickedEvent : UnityEvent<GameObject> { }

    public InternalButtonClickedEvent OnClick;

    void Start()
    {
        this.GetComponent<ButtonConfigHelper>()?.OnClick.AddListener(() =>
        {
            this.OnClick?.Invoke(this.gameObject);
        });
    }
}

https://github.com/satoshi-maemoto/MRTKButtonSenderSample/blob/master/Assets/Scripts/ClickEventRouter.cs

こんな感じにcase01と同様に動きます。


MRTK Button Sender Sample take2

まとめ

複数UIコンポーネントイベントハンドラを共有するという構成はコードをシンプルにするためによく使われるものだと思うのでSDKにきっと機能が用意されてているに違いないと探しまくったのですがどうやら無さそうなのでひと手間必要そうです(笑)