📑

MiRZA Libraryの使い勝手をよくする

に公開

MiRZA 固有機能を利用できるMiRZA Libraryをもう少し使いやすくしたい

Snapdragon Spaces対応のMiRZAですが、ハードウェアとしての個別の機能をいくつか持っています。以前、MiRZA固有の機能を使うためのライブラリが提供されたのでその詳細を調査した記事を書きました。

https://zenn.dev/miyaura/articles/snapdragonspaces-mirza-lib-5357c8812da2c9

なお、公式ドキュメントは以下にあります。

https://www.devices.nttqonoq.com/developer/doc/mirza-library/overview/

MiRZA Libraryを利用することで、MiRZAのセンサー操作を自身のコンテンツで活用することができます。Unityで開発する場合、unitypackage形式で、MiRZA Libraryを使うための実装サンプルが提供されています。これを参考に実装することで対応することになるのですが、もう少し使い勝手を良くするために部品化を検討しました。今回はその実装について紹介します。

MiRZA Libraryを使いやすくする部品とは

MiRZAデバイスの情報を利用するためには、MiRZA Libraryを利用します。このライブラリは汎用性を考慮してJava言語で実装されています。Unityで使うためにはJavaライブラリをC#から呼び出すためのプロキシクラスを作成することになります。これらはMiRZA LibraryのUnity用のプラグインとして「MiRZA Plugin」が提供されています。ただ、実際に実装などが必要になります。具体的にはセンサー情報を取得する際のイベントを実装する等です。
つまり、、、

  1. MiRZA PluginのUnitypackageをインポートする
  2. インポートしたサンプルコードを参考に自身のコンテンツ用に実装する

という手順になります。このため毎回MiRZAデバイス用の実装が必要になります。たとえば、具体的にはタッチセンターをタップした際に処理をするためにはハードコーディングでイベント登録する必要があります。ある程度考慮をしておかないと
MiRZA用専用のコードになってしまうため流用が難しくなります。
そこで、Unityのいくつかの機能を利用して汎用性を上げたMiRZA Library用の管理クラスを構築したいと思いました。
具体的には以下の2つの要素を実現します。

  1. MiRZA LibraryのイベントをUnity Eventで扱う
  2. Input Systemを利用した実装を実現する
    1. MiRZA用のデバイスを実装
    2. InputAction経由で情報取得

現時点のソースコードは以下のgithubでも公開しています(リファクタリング含めて現在も継続改修中)。

https://github.com/TakahiroMiyaura/MiRZATouchSensor

開発環境

今回の開発環境は以下の通りです。

  • Unity 2022.3.36f1
  • Snapdragon Spaces SDK V1.0.1
  • MiRZA Library

1. MiRZA LibraryのイベントをUnity Eventで扱う

最初はMiRZA Libaryで提供されている各種センサー系のイベントをUnity Eventで実装します。作成が必要なイベントはMiRZA Libraryのサンプル(MirzaPlugin.cs)から抽出し表として整理しました。

修飾子とタイプ メソッド 説明
Action<int> OnBatteryLevelChanged 電池残量の変化に応じて発生するイベント
Action<DisplayStatus> OnDisplayStatusChanged 画面表示状態の変化に応じて発生するイベント
Action<GlassStatus> OnGlassStatusChanged グラスデバイス状態の変化に応じて発生するイベント
Action<GlassTouchGestureStatus> OnGlassTouchGestureStatusChanged グラスのタッチパネル操作情報の変化に応じて発生するイベント
Action OnPowerOffChanged グラス電源OFFの変化に応じて発生するイベント
Action<ServiceState> OnServiceStateChanged サービス状態の変化に応じて発生するイベント
Action<SpacesModeStatus> OnSpacesModeStatusChanged MRモード状態の変化に応じて発生するイベント

イベントの内容によってはセンサーの数値情報や状態などが渡されるのでそれぞれに対応したUnityイベントを作成します。
イベント発生時に情報を持っている場合は引数を受けた渡すようにしています。

[Serializable]
// サービスステータスのイベントハンドラ
public class ServiceStateChangedEvent : UnityEvent<ServiceState> {}

[Serializable]
// グラス状態のイベントハンドラ
public class GlassStatusChangedEvent : UnityEvent<GlassStatus>{ }

[Serializable]
// 電池残量のイベントハンドラ
public class BatteryLevelChangedEvent : UnityEvent<int>{ }

[Serializable]
// Spacesモードのイベントハンドラ
public class SpacesModeStatusChangedEvent : UnityEvent<SpacesModeStatus>{ }

[Serializable]
// 画面表示状態のイベントハンドラ
public class DisplayStatusChangedEvent : UnityEvent<DisplayStatus>{ }

[Serializable]
// 手動電源OFFのイベントハンドラ
public class PowerOffChangedEvent : UnityEvent{ }

[Serializable]
// グラスタッチパネル操作情報のイベントハンドラ
public class GlassTouchGestureStatusChangedEvent : UnityEvent<GlassTouchGestureStatus>{ }

上記のUnityEventをMiRZA Libraryのイベントと紐づけます。たとえば、DisplayStatusの状態を処理するための実装は以下のようになります。

MiRZATouchSensorManager.cs
public class MiRZATouchSensorManager : MonoBehaviour
{
  [SerializeField]
  private bool AutoStartMonitoring;

  private AndroidJavaObject _activity;
  private AndroidJavaObject _library;

  // 画面表示状態のイベントハンドラ
  public DisplayStatusChangedEvent OnDisplayStatusChanged;

  public void Awake()
  {
#if !UNITY_EDITOR
    var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    _activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
    _library = new AndroidJavaObject("com.nttqonoq.devices.android.mirzalibrary.MirzaLibrary");
#endif
  }
  
  void OnEnable()
  {
    // モニタリング開始
    if (AutoStartMonitoring)
    {
      StartMonitoring();
    }
  }

  public void Update()
  {
    // ライブラリの更新処理
    _library?.Call("update");
  }

  public void StartMonitoring()
  {
    // コールバックを登録
    _library?.Call("setDisplayStatusCallback", new DisplayStatusCallback(InnerDisplayStatusChanged));

    // 連携開始
    _library?.Call("startMonitoring", _activity);
  }

  private void InnerDisplayStatusChanged(DisplayStatus obj)
  {
    OnDisplayStatusChanged?.Invoke(obj);
  }

AwakeメソッドではJavaライブラリであるMiRZA Libraryのオブジェクトを利用する為のAndroidJavaObjectクラスを準備します。これらの処理はUnity Editor上では実行できないため、#ifディレクティブでデプロイ資産を作成するときに有効化します。

MiRZATouchSensorManager.cs(抜粋)
  public void Awake()
  {
#if !UNITY_EDITOR
    var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    _activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
    _library = new AndroidJavaObject("com.nttqonoq.devices.android.mirzalibrary.MirzaLibrary");
#endif
  }

  public void StartMonitoring()
  {
    // コールバックを登録
    _library?.Call("setDisplayStatusCallback", new DisplayStatusCallback(InnerDisplayStatusChanged));

    // 連携開始
    _library?.Call("startMonitoring", _activity);
  }

MiRZA Libraryは最初にセンサー情報のモニタリングを開始しないと情報を取得できない仕様です。今回の管理クラスではこのコンポーネントが有効化されたときに自動的にモニタリングするように実装を入れています。これはOnEnableメソッドで実装しています。
初期処理としてStartMonitoringを用意しました。自動でモニタリングを開始しない場合はロジックでStartMonitoringメソッドを呼ぶ形にしています。
StartMonitoringメソッドではMiRZA LibraryのイベントにC#のメソッドを割り当てて状態変更時に処理されるように実装します。最後にモニタリングの開始処理を実行します。

MiRZATouchSensorManager.cs(抜粋)
  [SerializeField]
  private bool AutoStartMonitoring;

  void OnEnable()
  {
    // モニタリング開始
    if (AutoStartMonitoring)
    {
      StartMonitoring();
    }
  }

MiRZAデバイスの情報更新をするためにはMiRZA Libraryの情報更新が必要です。これはUpdateメソッドで実装します。

MiRZATouchSensorManager.cs(抜粋)
  public void Update()
  {
    // ライブラリの更新処理
    _library?.Call("update");
  }

最後に、MiRZAデバイスのセンサー情報を取得します。ここではディスプレイの状態変更時に発生するイベントを実装しています。ディスプレイの状態はDisplayStatus構造体に情報が入っているため、Unity Event イベントもDisplayStatus構造体を引数に持つ形で実装します。

MiRZATouchSensorManager.cs(抜粋)
  // 画面表示状態のイベントハンドラ
  public DisplayStatusChangedEvent OnDisplayStatusChanged;

  private void InnerDisplayStatusChanged(DisplayStatus obj)
  {
    OnDisplayStatusChanged?.Invoke(obj);
  }

上記のとおり実装し、このコンポーネントを任意のGameObjectに追加するとInspector上でUnity Eventとして登録することができるようになります。
後は同じ要領で他のセンサー情報についてもイベント登録することでUnity Eventを利用した管理クラスを構成することが可能になります。

2. Input Systemを利用した実装を実現する

上記の管理クラスをつくれば、MiRZAデバイスを使ったコンテンツ開発時にUnity Event経由で実装できるためイベント登録が少し楽になります。
ただ、もう少しコンテンツ開発で扱いやすい形で構成を加えたいと思います。マルチデバイス対応のXR向けのコンテンツを開発する場合を考えます。Unity Eventであれば、Inspector上で設定ができるように構成するので流用は容易です。一方でデバイス毎にコンポーネントを用意する必要がでてくると思います。
たとえば、MiRZAのタッチセンサーを利用したメニュー機能をMeta Quest3で使うような実装を考えます。この場合、以下の3つの実装が必要になります。

  • メニュー機能
  • MiRZAの入力操作でメニューを操作する
  • Meta Quest 3の入力操作からメニューを操作する

Unity Eventで実現した場合、追加でMeta Questからの入力操作制御の実装が必要になります。こういった"入力デバイスを切替えてコンテンツを流用する"場合、UnityではInput Systemを利用することができます。Input Systemは入力デバイスを抽象化することで、デバイスに入力制御を、コンテンツを分離することができます。デバイス毎に用意するInput Actionを切替えるだけでコンテンツを切替えることができるのでより流用性を高めることができます。
Input Systemを利用すれば、先ほどのメニュー表示は以下のようにシンプルな実装に変わります。

  • Input Actionに対応したメニュー機能を実装
  • メニュー機能に(利用したい)デバイスのInput Actionを設定する

Unity Eventを利用する実装では、追加でMeta Quest 3用にモーションコントローラの制御を実装する必要があります。
Input Systemを利用すると、こういった実装を「何かInput Actionからイベントが通知されたらメニューを表示する」という処理で実装することができます。これにより、Input ActionにMiRZA用または、Meta Quest用のActionを定義するだけでマルチデバイス対応が完了します。
そこで、MiRZA用デバイスを作成しInput Actionを定義していきたいと思います。

2.1. MiRZA用のデバイスを実装

MiRZAのセンサー情報をInput Systemで扱うためにはMiRZA用デバイスを作成する必要あります。デバイスは以下の2つのインターフェース、クラスを継承してMiRZAデバイスを作製します。

  • IInputStateTypeInfoインターフェース
    すべての入力デバイスの状態構造体が実装するインターフェース。状態のデータや、フォーマット識別子を定義する。
  • InputDeviceクラス
    入力デバイスを表すクラス。このデバイスがどういった入力制御を持つかを定義する。

最初にMiRZAデバイス(タッチセンサー)で扱う情報を整理します。

MiRZAデバイスの入力

MiRZA用のInputDeviceを作成するために、MiRZAデバイスの入力方法を整理します。
MiRZAのタッチセンサーの状態を整理します。

  • SingleTap
  • DoublTap
  • TripleTap
  • SwipeNext(*)
  • SwipePrevious(*)
  • LongPressInitial
  • LongPressRepeat
  • Move(*)

SwipeNext,SwipePrevious,Moveについては、センサー部分を前後に動かすジェスチャーになります。このため、タッチセンサーの移動方向やセンサーの座標情報も合わせて利用する仕様で考えました。この結果から、MiRZA用のデバイスとしては以下の3つの情報が含まれている必要があります。

  • 上記8つの操作判定
    →InputDeviceではint型プロパティを利用。操作毎にbitでOn/Offを判定する
  • タッチセンサーの座標(x,y)
    →InputDeviceではVector2型プロパティを利用
  • タッチ操作方向
    →InputDeviceではFloat型プロパティを利用

これらの情報をもとにIInputStateTypeInfoで表されるMiRZAデバイスの状態は以下のような実装になります。

MiRZATouchSensorDeviceState
// Copyright (c) 2025 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

[StructLayout(LayoutKind.Explicit)]
public struct MiRZATouchSensorDeviceState : IInputStateTypeInfo
{
  public FourCC format => new('M', 'Y', 'D', 'V');

  [FieldOffset(0)]
  [InputControl(displayName = "Single Tap", name = "SingleTap", layout = "Button", bit = 0)]
  [InputControl(displayName = "Double Tap", name = "DoubleTap", layout = "Button", bit = 1)]
  [InputControl(displayName = "Triple Tap", name = "TripleTap", layout = "Button", bit = 2)]
  [InputControl(displayName = "Long Press Initial", name = "LongPressInitial", layout = "Button", bit = 3)]
  [InputControl(displayName = "Long Press Repeat", name = "LongPressRepeat", layout = "Button", bit = 4)]
  [InputControl(displayName = "Swipe Previous", name = "SwipePrevious", layout = "Button", bit = 5)]
  [InputControl(displayName = "Swipe Next", name = "SwipeNext", layout = "Button", bit = 6)]
  [InputControl(displayName = "Move", name = "Move", layout = "Button", bit = 7)]
  public int Buttons;

  [FieldOffset(4)]
  [InputControl(displayName = "Touch Sensor", name = "TouchSensorPosition", layout = "vector2")]
  public Vector2 TouchSensorPosition;

  [FieldOffset(12)]
  [InputControl(displayName = "Movement", name = "Movement", layout = "Axis")]
  public float Movement;

}

次に対応するInput Deviceを作成します。InputDeviceは先ほど作った構造体の名前に対応するプロパティを定義を実装します。

MiRZATouchSensorDevice
// Copyright (c) 2025 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

[InputControlLayout(stateType = typeof(MiRZATouchSensorDeviceState))]
#if UNITY_EDITOR
// Unityエディタで初期化処理を呼び出すのに必要
[InitializeOnLoad]
#endif
public class MiRZATouchSensorDevice : InputDevice
{
  public ButtonControl SingleTap { get; private set; }
  public ButtonControl DoubleTap { get; private set; }
  public ButtonControl TripleTap { get; private set; }
  public ButtonControl LongPressInitial { get; private set; }
  public ButtonControl LongPressRepeat { get; private set; }
  public ButtonControl SwipePrevious { get; private set; }
  public ButtonControl SwipeNext { get; private set; }
  public ButtonControl Move { get; private set; }
  public Vector2Control TouchSensorPosition { get; private set; }
  public AxisControl Movement { get; private set; }

  static MiRZATouchSensorDevice()
  {
    InputSystem.RegisterLayout<MiRZATouchSensorDevice>();
    foreach (var inputDevice in InputSystem.devices)
        if (inputDevice is MiRZATouchSensorDevice)
            return;
    var mirzaTouchSensorDevice = InputSystem.AddDevice<MiRZATouchSensorDevice>();
    InputSystem.EnableDevice(mirzaTouchSensorDevice);
  }

  protected override void FinishSetup()
  {
    base.FinishSetup();
    SingleTap = GetChildControl<ButtonControl>("SingleTap");
    DoubleTap = GetChildControl<ButtonControl>("DoubleTap");
    TripleTap = GetChildControl<ButtonControl>("TripleTap");
    LongPressInitial = GetChildControl<ButtonControl>("LongPressInitial");
    LongPressRepeat = GetChildControl<ButtonControl>("LongPressRepeat");
    SwipePrevious = GetChildControl<ButtonControl>("SwipePrevious");
    SwipeNext = GetChildControl<ButtonControl>("SwipeNext");
    Move = GetChildControl<ButtonControl>("Move");

    TouchSensorPosition = GetChildControl<Vector2Control>("TouchSensorPosition");
    Movement = GetChildControl<AxisControl>("Movement");
    }
  }

上記の実装が完了するとInput Actionの定義が可能になります。

MiRZA LibraryからInputDeviceに情報を送る

次に、MiRZA Libraryから取得したセンサーの操作情報をInputDeviceに通知するロジックを作成します。これは先ほど作ったUnity Eventを利用する管理クラスをカスタマイズすることで実現可能です。

MiRZATouchSensorManager(抜粋)
MiRZATouchSensorManager(抜粋)
public class MiRZATouchSensorManager : MonoBehaviour
{
    [SerializeField]
    private bool AutoStartMonitoring;

    private AndroidJavaObject _activity;

    private AndroidJavaObject _library;

    // グラスタッチパネル操作情報のイベントハンドラ
    public GlassTouchGestureStatusChangedEvent OnGlassTouchGestureStatusChanged;

    private MiRZATouchSensorDevice _mirzaTouchSensorDevice;

    public void Awake()
    {
#if !UNITY_EDITOR
        var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        _activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        _library = new AndroidJavaObject("com.nttqonoq.devices.android.mirzalibrary.MirzaLibrary");
        _mirzaTouchSensorDevice = InputSystem.AddDevice<MiRZATouchSensorDevice>();
#endif
    }
    private void OnDestroy()
    {
        // デバイスの削除
        InputSystem.RemoveDevice(_mirzaTouchSensorDevice);
    }

    void OnEnable()
    {
        // モニタリング開始
        if (AutoStartMonitoring)
        {
            StartMonitoring();
        }
    }
    public void Update()
    {
        // ライブラリの更新処理
        _library?.Call("update");
    }

    /// <summary>
    ///     モニタリング開始
    /// </summary>
    public void StartMonitoring()
    {
        // コールバックを登録
        _library?.Call("setGlassTouchGestureStatusCallback",
            new GlassTouchGestureStatusCallback(InnerGlassTouchGestureStatusChanged));

        // 連携開始
        _library?.Call("startMonitoring", _activity);
    }

    private void InnerGlassTouchGestureStatusChanged(GlassTouchGestureStatus obj)
    {
        OnGlassTouchGestureStatusChanged?.Invoke(obj);

        switch (obj.Operation)
        {
            case TouchOperation.Up:
                Debug.Log("Up");
                OnTouchSensorUp?.Invoke(obj);
                break;
            case TouchOperation.Down:
                Debug.Log("Down");
                OnTouchSensorDown?.Invoke(obj);
                break;
        }

        var state = new MiRZATouchSensorDeviceState();
        switch (obj.Type)
        {
            case TouchType.Tap:
                switch (obj.Operation)
                {
                    case TouchOperation.Single:
                        Debug.Log("Single Tap");
                        OnSingleTap?.Invoke(obj);
                        state.Buttons |= 1 << 0;
                        state.TouchSensorPosition = new Vector2(obj.XCoordinate, obj.YCoordinate);
                        break;
                    case TouchOperation.Double:
                        Debug.Log("Double Tap");
                        OnDoubleTap?.Invoke(obj);
                        state.Buttons |= 1 << 1;
                        state.TouchSensorPosition = new Vector2(obj.XCoordinate, obj.YCoordinate);
                        break;
                    case TouchOperation.Triple:
                        Debug.Log("Triple Tap");
                        OnTripleTap?.Invoke(obj);
                        state.Buttons |= 1 << 2;
                        state.TouchSensorPosition = new Vector2(obj.XCoordinate, obj.YCoordinate);
                        break;
                    case TouchOperation.LongPressInitial:
                        Debug.Log("Long Press Initial");
                        OnLongPressInitial?.Invoke(obj);
                        state.Buttons |= 1 << 3;
                        state.TouchSensorPosition = new Vector2(obj.XCoordinate, obj.YCoordinate);
                        break;
                    case TouchOperation.LongPressRepeat:
                        Debug.Log("Long Press Repeat");
                        OnLongPressRepeat?.Invoke(obj);
                        state.Buttons |= 1 << 4;
                        state.TouchSensorPosition = new Vector2(obj.XCoordinate, obj.YCoordinate);
                        break;
                }

                break;
            case TouchType.Swipe:
                switch (obj.Operation)
                {
                    case TouchOperation.Previous:
                        Debug.Log("Swipe Previous");
                        OnSwipePrevious?.Invoke(obj);
                        state.Buttons |= 1 << 5;
                        state.Movement = obj.Movement;
                        if (state.Movement > short.MaxValue)
                            state.Movement -= 65536;
                        state.TouchSensorPosition = new Vector2(obj.XCoordinate, obj.YCoordinate);
                        break;
                    case TouchOperation.Next:
                        Debug.Log("Swipe Next");
                        OnSwipeNext?.Invoke(obj);
                        state.Buttons |= 1 << 6;
                        state.Movement = obj.Movement;
                        if (state.Movement > short.MaxValue)
                            state.Movement -= 65536;
                        state.TouchSensorPosition = new Vector2(obj.XCoordinate, obj.YCoordinate);
                        break;
                }

                break;
            case TouchType.Motion:
                switch (obj.Operation)
                {
                    case TouchOperation.Move:
                        Debug.Log("Move");
                        OnMove?.Invoke(obj);
                        state.Buttons |= 1 << 7;
                        state.Movement = obj.Movement;
                        if (state.Movement > short.MaxValue)
                            state.Movement -= 65536;
                        state.TouchSensorPosition = new Vector2(obj.XCoordinate,obj.YCoordinate);
                        break;
                }

                break;
        }
        if(_mirzaTouchSensorDevice != null)
            InputSystem.QueueStateEvent(_mirzaTouchSensorDevice, state);
    }
}

先ほど作ったMiRZA用のInputDeviceを利用する為にいくつか実装を加えました。まずはInputDeviceとして利用できるようにするため、Input Systemに登録します。

_mirzaTouchSensorDevice = InputSystem.AddDevice<MiRZATouchSensorDevice>();

登録したInputDeviceにセンサー情報を送るために、MiRZA Libraryから発生したイベント処理の中でInput Systemに必要な情報を送ります。たとえば、ジェスチャーのMoveに対応する情報を送る場合は以下のように、InputDeviceに情報を送信します。


var state = new MiRZATouchSensorDeviceState();

state.Buttons |= 1 << 7;
state.Movement = obj.Movement;
if (state.Movement > short.MaxValue)
  state.Movement -= 65536;
state.TouchSensorPosition = new Vector2(obj.XCoordinate,obj.YCoordinate);

if(_mirzaTouchSensorDevice != null)
  InputSystem.QueueStateEvent(_mirzaTouchSensorDevice, state);

上記の処理はMoveジェスチャーを行った際にMove操作に対するbitを立てて、その際のタッチジェスチャーの方向、座標をInputDeviceに送信します。InputDeviceはこの情報をもとに後程定義するInput Actionに情報を送ります。

MiRZA用のInput Actionを定義する。

最後に、Input Actionを定義します。
ProjectパネルのAssets内の任意の場所を選択し、メニューから[Assets]-[Create]-[Input Actions]を選択し、空のInput Actionを作成します。ダブルクリックで開きます。開いたパネルの左上のメニューから[Add Control Schema]を選択し先ほど作ったInputDeviceを紐づけてスキーマを定義します。

次にAction Mapを作成し、Input Actionsを追加していきます。Input ActionsはMiRZATouchSensorDeviceで作成したプロパティ分全てを作成します。
たとえばSingleTapのInput Actionについては、Actionを[Button]に設定し、Bindingとして[SingleTap]を割り当てます。なお、MovementとTouchSensorPositionは以下の通り設定します。

  • Movement
    • Action Type:Value
    • Control Type:Axis
  • TouchSensorPosition
    • Action Type:Value
    • Control Type:Vector2

全てのInput Actionを実装すると以下のようになります。

定義したInput Actionは Input Action Managerに登録することで有効になります。

後は、Input Action経由で入力デバイスに対する処理を実装します。

2.2. InputAction経由で情報取得

今回は簡単に入力デバイスの情報をTextで表示するようなコンポーネントを実装します。
空のコンポーネントを作成し以下のように実装してみてください。今回はジェスチャー操作時のセンサーの座標をTextで表示するコンポーネントとして実装しています。
Input Actionの状態を監視し情報が通知されたらその情報をテキストに表示するロジックを実装しています。

DeviceInfoSample.csの実装
DeviceInfoSample.cs
// Copyright (c) 2025 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php

using System;
using System.Threading;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;

public class DeviceInfoSample : MonoBehaviour
{
    [SerializeField]
    private InputActionReference touchSensorPosition;

    [SerializeField]
    private TextMeshProUGUI touchSensorPositionText;

    private SynchronizationContext mainThreadContext;


    // Start is called before the first frame update
    private void Start()
    {
        mainThreadContext = SynchronizationContext.Current;

        touchSensorPosition.action.performed += ctx => mainThreadContext.Post(_ =>
        {
            touchSensorPositionText.text = $"{ctx.ReadValue<Vector2>()}";
        }, null);

    }

    // Update is called once per frame
    private void Update()
    {
    }
}

Inspectorでは先ほど定義したInput Actionの中からMiRZAのタッチジェスチャーの座標を返すInput Actionと表示用のTextオブジェクトを設定します。

実機で動かすとジェスチャー操作に応じてx座標が変化することを確認することができます。
たとえば、このコンポーネントのInput ActionをMeta Questのスティック操作用のInput Actionに変更すれば、Meta Questではスティック操作時の座標を取得できるようになります。メニュー表示等であれば、MiRZAでは長押し、Meta Questではメニューボタンといった入力デバイスに応じたInput Actionを変更するだけで容易にマルチデバイス対応が可能になります。

まとめ

今回はMiRZA LibraryをよりInput Systemで扱えるようにする試みについて紹介しました。入力デバイスの違いを吸収できるInput Systemを利用することでコンテンツの流用性を高めることができます。
現在GitHubに公開しているコードはセンサー情報のほとんどを扱えるのですがまだ整備途中で、いくつか実験したいこともあります。引続き調査しながら実装を進めていく予定です。

Discussion