MRTK v2でも毎フレームコントローラの値を読みたい！

# やりたいこと

タイトルの通り。

MRTK v2.xでは、ユーザの入力に応じた処理を書きたい場合は、何かしらのクラスが***IMixedRealityInputHandler***とかを実装して、特定のボタンが押されたり、入力に変化があった時に呼ばれるイベントハンドラを書くというのが基本的な処理の書き方です。

しかし自分が作っていたアプリケーションの中で、ユーザのハンドモデルの指を入力に応じていい感じに動かす必要があり、毎フレームコントローラの入力が欲しい場面があったので、自分用のメモも兼ねて記事にしておきます。
[この記事](https://qiita.com/Alupaca1363Inew/items/3017445df5fd48c48f42)の最初のGIF動画でやっていることですね。

# スクリプト

Windows MRの場合、コントローラ入力の生の値は**UnityEngine.XR.WSA.Input**名前空間の**InteractionManager.GetCurrentReading()**メソッドで一気に取得できるので、いい感じに入力タイプを指定できる列挙型とutilityクラス、あとLinqの拡張メソッドを作成します。

```CS:MRInputType
namespace UnityEngine.XR.WSA.Input
{
    public class MRInputType
    {
        public enum Button
        {
            Trigger,
            Grip,
            Thumbstick,
            TouchpadTouch,
            TouchpadPress
        }

        public enum Axis1D
        {
            Trigger
        }

        public enum Axis2D
        {
            Thumbstick,
            Touchpad
        }
    }
}
```

```CS:InteractionManagerUtility
using System;
using System.Linq;

namespace UnityEngine.XR.WSA.Input
{
    public static class InteractionManagerUtility
    {
        /// <summary>
        /// コントローラのボタンが押されているかどうか取得する
        /// </summary>
        /// <param name="button"></param>
        /// <param name="handedness"></param>
        /// <returns></returns>
        public static bool Get(MRInputType.Button button, InteractionSourceHandedness handedness)
            => Get(button, handedness, InteractionManager.GetCurrentReading());

        public static bool Get(MRInputType.Button button, InteractionSourceHandedness handedness, InteractionSourceState[] states)
        {
            if (states.TryGetFirst(s => s.source.handedness == handedness, out var state))
            {
                switch (button)
                {
                    case MRInputType.Button.Trigger:        return state.selectPressed;
                    case MRInputType.Button.Grip:           return state.grasped;
                    case MRInputType.Button.Thumbstick:     return state.thumbstickPressed;
                    case MRInputType.Button.TouchpadTouch:  return state.touchpadTouched;
                    case MRInputType.Button.TouchpadPress:  return state.touchpadPressed;
                    default: throw new Exception("[InteractionManagerUtility] Button '" + button.GetType() + "' is not supported.");
                }
            }

            return false;
        }

        /// <summary>
        /// コントローラのボタンの押し込み量を取得する
        /// </summary>
        /// <param name="axis"></param>
        /// <param name="handedness"></param>
        /// <returns></returns>
        public static float Get(MRInputType.Axis1D axis, InteractionSourceHandedness handedness)
            => Get(axis, handedness, InteractionManager.GetCurrentReading());

        public static float Get(MRInputType.Axis1D axis, InteractionSourceHandedness handedness, InteractionSourceState[] states)
        {
            if (states.TryGetFirst(s => s.source.handedness == handedness, out var state))
            {
                switch (axis)
                {
                    case MRInputType.Axis1D.Trigger: return state.selectPressedAmount;
                    default: throw new Exception("[InteractionManagerUtility] Axis1D '" + axis.GetType() + "' is not supported.");
                }
            }

            return 0.0f;
        }

        /// <summary>
        /// 2軸のボタンの押されている向きを取得する
        /// </summary>
        /// <param name="axis"></param>
        /// <param name="handedness"></param>
        /// <returns></returns>
        public static Vector2 Get(MRInputType.Axis2D axis, InteractionSourceHandedness handedness)
            => Get(axis, handedness, InteractionManager.GetCurrentReading());

        public static Vector2 Get(MRInputType.Axis2D axis, InteractionSourceHandedness handedness, InteractionSourceState[] states)
        {
            if (states.TryGetFirst(s => s.source.handedness == handedness, out var state))
            {
                switch (axis)
                {
                    case MRInputType.Axis2D.Thumbstick: return state.thumbstickPosition;
                    case MRInputType.Axis2D.Touchpad:   return state.touchpadPosition;
                    default: throw new Exception("[InteractionManagerUtility] Axis2D '" + axis.GetType() + "' is not supported.");
                }
            }

            return Vector2.zero;
        }
    }
}
```

```CS:LinqExtension
using System;
using System.Collections;
using System.Collections.Generic;

namespace System.Linq
{
    public static class LinqExtension
    {
        public static bool TryGetFirst<T>(this IList<T> list, Func<T, bool> func, out T result)
        {
            var exist = list.Any(func);
            result = exist ? list.First(func) : default;

            return exist;
        }
    }
}
```

## 使う側

```CS

[SerializeField]
private InteractionSourceHandedness handedness;

private void Update()
{
    var flex = InteractionManagerUtility.Get(MRInputType.Axis1D.Trigger, handedness);

    ...
}
```

もしくは

```CS

[SerializeField]
private InteractionSourceHandedness handedness;

private void Update()
{
    var currentStates = InteractionManager.GetCurrentReading();

    // 1フレームに複数種類の入力を取得するなら、InteractionManager.GetCurrentReading()を
    // あらかじめ呼び出し、Get()の第3引数に渡した方が無駄を省けると思われ
    var flex = InteractionManagerUtility.Get(MRInputType.Axis1D.Trigger, handedness, currentStates);
    var isGripping = InteractionManagerUtility.Get(MRInputType.Button.Grip, handedness, currentStates);

    ...
}
```

# あとがき

Linqの拡張メソッドは、個人的にスッキリすると思ったので書いて使いましたが、別に**InteractionManagerUtility**の処理の中に直接書いてもいい程度の処理だと思います。
拡張メソッドを使いたくない方は書き換えて使ってください。

# 参考

[公式ドキュメント - InteractionManager.GetCurrentReading()](https://docs.unity3d.com/ScriptReference/XR.WSA.Input.InteractionManager.GetCurrentReading.html)