Hiromuブログ

HoloLens などに関するブログです

MRTKのMixedRealityInputSystemの実装確認

以前の調べ物から一か月近く立ってしまいましたが、今回は入力に関する大本とも言える MixedRealityInputSystem について調べてみました(おかしなことを書いていたら教えてください)。

確認した MRTK のバージョンは v2.4.0 です。なお、今のタイミングでこのような調べ物をしているのは MRTK がかなり安定してきており v.2.5.0 だろうが v.2.6.0 だろうが大きく変わることはないと考えているためです(HoloToolKit → MRTK になったような破壊的変更が無い限りは問題ないはず)。

さっそく継承関係から見ていきます。

継承関係

f:id:HiromuKato:20200821204700p:plain

以下のインタフェース、クラスについては、こちら で確認済みのものになります。

  • IDisposable
  • IMixedRealityService
  • IMixedRealityEventSystem
  • BaseService
  • BaseEventSystem
  • BaseCoreSystem

IMixedRealityCapabilityCheck

登録済みデータプロバイダーが現在のプラットフォームで要求された機能をサポートしているかどうかを確認するためのインタフェース。

public interface IMixedRealityCapabilityCheck
{
    // 1つ以上の登録済みデータプロバイダーが現在のプラットフォームで要求された機能をサポートしているかどうかを確認する
    bool CheckCapability(MixedRealityCapability capability);
}

引数の MixedRealityCapability は以下のような enum です。

public enum MixedRealityCapability
{
    ArticulatedHand = 0,
    GGVHand,
    MotionController,
    EyeTracking,
    VoiceCommand,
    VoiceDictation,
    SpatialAwarenessMesh,
    SpatialAwarenessPlane,
    SpatialAwarenessPoint
}

IMixedRealityInputSystem

Mixed Reality Toolkitの入力システムのマネージャーインターフェイス。入力システム機能を提供するためのすべての代替システムは、このインターフェースから派生する必要がある。

かなりの要素を持ったインタフェースですが、大半がイベントを発生させるメソッドで、例えば Visual Studio で #region Input Events でアウトラインの折り畳みを行うとそれなりにすっきりします。また、残りのメソッドは主に入力に関する情報をスタックに出し入れするものであることが分かります。

public interface IMixedRealityInputSystem : IMixedRealityEventSystem
{
    // 入力が有効なときに発生するイベント
    event Action InputEnabled;

    // 入力が無効なときに発生するイベント
    event Action InputDisabled;

    // 手やモーションコントローラーなどの入力マネージャーによって検出されたインタラクション入力ソースのリスト
    HashSet<IMixedRealityInputSource> DetectedInputSources { get; }

    // 入力マネージャーによって現在検出されているIMixedRealityControllerのリスト
    // これは、リスト内のIMixedRealityInputSourcesのサブセットであるため、このプロパティはDetectedInputSourceに似ている
    HashSet<IMixedRealityController> DetectedControllers { get; }

    // ConfigurationProfileプロパティの型付き表現
    MixedRealityInputSystemProfile InputSystemProfile { get; }

    // この入力システムによって実装されている現在のフォーカスプロバイダー
    IMixedRealityFocusProvider FocusProvider { get; }

    // この入力システムによって実装されている現在のレイキャストプロバイダー
    IMixedRealityRaycastProvider RaycastProvider { get; }

    // この入力システムによって実装されている現在の Gazeプロバイダー
    IMixedRealityGazeProvider GazeProvider { get; }

    // この入力システムによって実装されている現在の EyeGazeプロバイダー
    IMixedRealityEyeGazeProvider EyeGazeProvider { get; }

    // 入力が現在有効かどうかを示す
    bool IsInputEnabled { get; }

    // 無効な入力状態を入力システムにプッシュする
    // 入力が無効になっている間、イベントは送信されず、カーソルは待機中のアニメーションを表示する
    void PushInputDisable();

    // 無効な入力状態を入力システムからポップする
    // 最後の無効状態が解除されると、スタック入力が再度有効になる
    void PopInputDisable();

    // 入力をすぐに再度有効にする入力無効スタックをクリアする
    void ClearInputDisableStack();

    // ゲームオブジェクトをモーダル入力スタックにプッシュする
    // ゲームオブジェクトの入力ハンドラは、フォーカスされたオブジェクトの前に入力イベントを優先する
    void PushModalInputHandler(GameObject inputHandler);

    // モーダル入力スタックから最後のゲームオブジェクトを削除する
    void PopModalInputHandler();

    // スタックからすべてのモーダル入力ハンドラーをクリアする
    void ClearModalInputStack();

    // ゲームオブジェクトをフォールバック入力スタックにプッシュする
    // モーダルオブジェクトまたはフォーカスオブジェクトがイベントを消費しない場合、ゲームオブジェクトの入力ハンドラーに入力イベントが与えられる
    void PushFallbackInputHandler(GameObject inputHandler);

    // フォールバック入力スタックから最後のゲームオブジェクトを削除する
    void PopFallbackInputHandler();

    // スタックからすべてのフォールバック入力ハンドラーをクリアする
    void ClearFallbackInputStack();

    #region Input Events  ★★★ ここから下は(上の2個を除いて)イベントを発生させるメソッド ★★★

    #region Input Source Events

    // 新しい一意の入力ソースIDを生成する
    // すべての入力ソースは、コンストラクターまたは初期化でこのメソッドを呼び出す必要がある
    uint GenerateNewSourceId();

    // 新しい入力ソースをリクエストする
    IMixedRealityInputSource RequestNewGenericInputSource(string name, IMixedRealityPointer[] pointers = null, InputSourceType sourceType = InputSourceType.Other);

    // 入力ソースが検出されたというイベントを発生させる
    void RaiseSourceDetected(IMixedRealityInputSource source, IMixedRealityController controller = null);

    // 入力ソースが失われたというイベントを発生させる
    void RaiseSourceLost(IMixedRealityInputSource source, IMixedRealityController controller = null);

    // 入力ソースの追跡状態が変更されたというイベントを発生させる
    void RaiseSourceTrackingStateChanged(IMixedRealityInputSource source, IMixedRealityController controller, TrackingState state);

    // 入力ソースの位置が変更されたというイベントを発生させる
    void RaiseSourcePositionChanged(IMixedRealityInputSource source, IMixedRealityController controller, Vector2 position);

    // 入力ソースの位置が変更されたというイベントを発生させる
    void RaiseSourcePositionChanged(IMixedRealityInputSource source, IMixedRealityController controller, Vector3 position);

    // 入力ソースの回転が変更されたというイベントを発生させる
    void RaiseSourceRotationChanged(IMixedRealityInputSource source, IMixedRealityController controller, Quaternion rotation);

    // 入力ソースのポーズ(位置・回転)が変更されたというイベントを発生させる
    void RaiseSourcePoseChanged(IMixedRealityInputSource source, IMixedRealityController controller, MixedRealityPose position);

    #endregion Input Source Events

    #region Focus Events

    // フォーカスが変更された事前イベントを発生させる
    // このイベントは、フォーカス変更イベントの前にロジックを実行するのに役立つ
    void RaisePreFocusChanged(IMixedRealityPointer pointer, GameObject oldFocusedObject, GameObject newFocusedObject);

    // フォーカス変更イベントを発生させる
    void RaiseFocusChanged(IMixedRealityPointer pointer, GameObject oldFocusedObject, GameObject newFocusedObject);

    // フォーカス開始イベントを発生させる
    void RaiseFocusEnter(IMixedRealityPointer pointer, GameObject focusedObject);

    // フォーカス終了イベントを発生させる
    void RaiseFocusExit(IMixedRealityPointer pointer, GameObject unfocusedObject);

    #endregion Focus Events

    #region Pointers

    #region Pointer Down

    // ポインタダウンイベントを発生させる
    void RaisePointerDown(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null);

    #endregion Pointer Down

    #region Pointer Dragged

    // ポインタのドラッグイベントを発生させる
    void RaisePointerDragged(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null);

    #endregion Pointer Dragged

    #region Pointer Click

    // ポインタクリックイベントを発生させる
    void RaisePointerClicked(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, int count, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null);

    #endregion Pointer Click

    #region Pointer Up

    // ポインタアップイベントを発生させる
    void RaisePointerUp(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null);

    #endregion Pointer Up

    #endregion Pointers

    #region Generic Input Events

    #region Input Down

    // 入力ダウンイベントを発生させる
    void RaiseOnInputDown(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction);

    #endregion Input Down

    #region Input Up

    // 入力アップイベントを発生させる
    void RaiseOnInputUp(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction);

    #endregion Input Up

    #region Float Input Changed

    // フロート入力の変更を発生させる
    void RaiseFloatInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, float inputValue);

    #endregion Float Input Changed

    #region Input Position Changed

    // 2自由度の入力イベントを発生させる
    void RaisePositionInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, Vector2 position);

    // 3自由度の入力イベントを発生させる
    void RaisePositionInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, Vector3 position);

    #endregion Input Position Changed

    #region Input Rotation Changed

    // 3自由度の入力イベントを発生させる
    void RaiseRotationInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, Quaternion rotation);

    #endregion Input Rotation Changed

    #region Input Pose Changed

    // 6自由度の入力イベントを発生させる
    void RaisePoseInputChanged(IMixedRealityInputSource source, Handedness handedness, MixedRealityInputAction inputAction, MixedRealityPose inputData);

    #endregion Input Pose Changed

    #endregion Generic Input Events

    #region Generic Gesture Events

    // ジェスチャー開始イベントを発生させる
    void RaiseGestureStarted(IMixedRealityController controller, MixedRealityInputAction action);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, Vector2 inputData);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, Vector3 inputData);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, Quaternion inputData);

    // ジェスチャー更新イベントを発生させる
    void RaiseGestureUpdated(IMixedRealityController controller, MixedRealityInputAction action, MixedRealityPose inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, Vector2 inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, Vector3 inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, Quaternion inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCompleted(IMixedRealityController controller, MixedRealityInputAction action, MixedRealityPose inputData);

    // ジェスチャー完了イベントを発生させる
    void RaiseGestureCanceled(IMixedRealityController controller, MixedRealityInputAction action);

    #endregion

    #region Speech Keyword Events

    // 音声コマンドの認識イベントを発生させる
    void RaiseSpeechCommandRecognized(IMixedRealityInputSource source, RecognitionConfidenceLevel confidence, TimeSpan phraseDuration, DateTime phraseStartTime, SpeechCommands command);

    #endregion Speech Keyword Events

    #region Dictation Events

    // ディクテーション仮説イベントを発生させる
    void RaiseDictationHypothesis(IMixedRealityInputSource source, string dictationHypothesis, AudioClip dictationAudioClip = null);

    // ディクテーション結果イベントを発生させる
    void RaiseDictationResult(IMixedRealityInputSource source, string dictationResult, AudioClip dictationAudioClip = null);

    // ディクテーション完了イベントを発生させる
    void RaiseDictationComplete(IMixedRealityInputSource source, string dictationResult, AudioClip dictationAudioClip);

    // ディクテーションエラーイベントを発生させる
    void RaiseDictationError(IMixedRealityInputSource source, string dictationResult, AudioClip dictationAudioClip = null);

    #endregion Dictation Events

    #region Hand Events

    // articulated hand の関節情報が更新されたことをシステムに通知する
    void RaiseHandJointsUpdated(IMixedRealityInputSource source, Handedness handedness, IDictionary<TrackedHandJoint, MixedRealityPose> jointPoses);

    // articulated hand のメッシュが更新されたことをシステムに通知する
    void RaiseHandMeshUpdated(IMixedRealityInputSource source, Handedness handedness, HandMeshInfo handMeshInfo);

    // タッチ開始イベントを発生させる
    void RaiseOnTouchStarted(IMixedRealityInputSource source, IMixedRealityController controller, Handedness handedness, Vector3 touchPoint);

    // タッチ更新イベントを発生させる
    void RaiseOnTouchUpdated(IMixedRealityInputSource source, IMixedRealityController controller, Handedness handedness, Vector3 touchPoint);

    // タッチ完了イベントを発生させる
    void RaiseOnTouchCompleted(IMixedRealityInputSource source, IMixedRealityController controller, Handedness handedness, Vector3 touchPoint);

    #endregion Hand Events

    #endregion Input Events
}

IMixedRealityDataProviderAccess

データプロバイダーを取得するためのインタフェース。

// システムが管理されたデータプロバイダーへのアクセスを提供できるようにする
public interface IMixedRealityDataProviderAccess
{
    // 登録されたデータプロバイダーのコレクションを取得する
    IReadOnlyList<IMixedRealityDataProvider> GetDataProviders();

    // 指定されたタイプの登録されたデータプロバイダーのコレクションを取得する
    IReadOnlyList<T> GetDataProviders<T>() where T : IMixedRealityDataProvider;

    // 指定した名前で登録されているデータプロバイダーを取得する
    // 指定された名前で複数のデータプロバイダーが登録されている場合、最初のプロバイダーが返される
    IMixedRealityDataProvider GetDataProvider(string name);

    // 指定された名前(オプション)で登録され、指定されたタイプに一致するデータプロバイダーを取得する
    // 指定された名前で複数のデータプロバイダーが登録されている場合、最初のプロバイダーが返される
    T GetDataProvider<T>(string name = null) where T : IMixedRealityDataProvider;
}

ここで出てくる IMixedRealityDataProvider の中身は以下の通り空です。

すべてのデータプロバイダーが IMixedRealityDataProvider を継承(というか中身がないけど実装)しなければいけない決め事と考えればよいでしょう。

// すべての Mixed Reality データプロバイダーに必要なインターフェイス
// データプロバイダーは、必要な情報(例:入力コントローラーの状態)を備えたサービスを提供するコンポーネント
public interface IMixedRealityDataProvider : IMixedRealityService
{
    // Reserved for future use.
}

BaseDataProviderAccessCoreSystem

IMixedRealityDataProvider の管理とアクセスのために定義された機能を持つ MRTK システムのコアとなる抽象クラス。

public abstract class BaseDataProviderAccessCoreSystem : BaseCoreSystem, IMixedRealityDataProviderAccess
{
    // データプロバイダーのリスト
        private readonly List<IMixedRealityDataProvider> dataProviders = new List<IMixedRealityDataProvider>();

    // 親のResetを呼んだあとに、具体的処理は各データプロバイダーに委譲
    public override void Reset()
    {
        base.Reset();

        foreach(var provider in dataProviders)
        {
            provider.Reset();
        }
    }
    // 以下も上に同じ
    public override void Enable() { }
    public override void Update() { }
    public override void LateUpdate() { }

    // コンストラクタ
    protected BaseDataProviderAccessCoreSystem(BaseMixedRealityProfile profile = null) : base(profile) { }

    // dataProviders リストからのデータプロバイダーを取得する
    #region IMixedRealityDataProviderAccess Implementation
    public virtual IReadOnlyList<IMixedRealityDataProvider> GetDataProviders() { }
    public virtual IReadOnlyList<T> GetDataProviders<T>() where T : IMixedRealityDataProvider { }
    public virtual IMixedRealityDataProvider GetDataProvider(string name) { }
    public virtual T GetDataProvider<T>(string name = null) where T : IMixedRealityDataProvider { }
    #endregion IMixedRealityDataProviderAccess Implementation

    // 指定されたタイプのデータプロバイダーを登録する
    protected bool RegisterDataProvider<T>(
        Type concreteType,
        SupportedPlatforms supportedPlatforms = (SupportedPlatforms)(-1),
        params object[] args) where T : IMixedRealityDataProvider
    {
        return RegisterDataProviderInternal<T>(
            true, // Retry with an added IMixedRealityService parameter
            concreteType,
            supportedPlatforms,
            args);
    }

    // 指定された具象型のインスタンスを作成し、プロバイダーを登録する内部メソッド
    private bool RegisterDataProviderInternal<T>(
        bool retryWithRegistrar,
        Type concreteType,
        SupportedPlatforms supportedPlatforms = (SupportedPlatforms)(-1),
        params object[] args) where T : IMixedRealityDataProvider
    {
        // 略...

        T dataProviderInstance;

        try
        {
            // ★データプロバイダーインスタンスの生成
            dataProviderInstance = (T)Activator.CreateInstance(concreteType, args);
        }
        // 略...

        return RegisterDataProvider(dataProviderInstance);
    }

    // 指定されたタイプのサービスを登録する
    protected bool RegisterDataProvider<T>(T dataProviderInstance) where T : IMixedRealityDataProvider
    {
        // 略...

        dataProviders.Add(dataProviderInstance);
        dataProviderInstance.Initialize();

        return true;
    }

    // 指定したタイプのデータプロバイダーの登録を解除する
    // name 引数が指定されていない場合、最初のインスタンスが登録解除される
    protected bool UnregisterDataProvider<T>(string name = null) where T : IMixedRealityDataProvider
    {
        T dataProviderInstance = GetDataProvider<T>(name);
        // 略...
        return UnregisterDataProvider(dataProviderInstance);
    }

    /// データプロバイダーの登録を解除する
    protected bool UnregisterDataProvider<T>(T dataProviderInstance) where T : IMixedRealityDataProvider
    {
        // 略...
        if (dataProviders.Contains(dataProviderInstance))
        {
            dataProviders.Remove(dataProviderInstance);

            dataProviderInstance.Disable();
            dataProviderInstance.Destroy();

            return true;
        }

        return false;
    }
}

簡単にまとめると内容としては、

  • dataProviders というデータプロバイダーのリストを持っており
  • データプロバイダーインスタンスを生成し dataProviders リストへの登録・解除を行い
  • データプロバイダーを取得する Get メソッドを持ち
  • 各データプロバイダーに対して、ライフサイクル関連のメソッド呼び出しを行う

クラスになっています。

MixedRealityInputSystem

ボリュームも多いのでポイントを絞って見ていきます。

まず、Proiory が 1 であり、他のすべてのマネージャーの前に処理されるためこの入力システムが重要なものであることがわかります。

public override uint Priority => 1;

次に初期化処理です。細かい部分を省くと以下の通りです。

// 初期化処理
public override void Initialize()
{
    // プロファイルの取得
    MixedRealityInputSystemProfile profile = ConfigurationProfile as MixedRealityInputSystemProfile;

    // インプットモジュールのチェック
    BaseInputModule[] inputModules = UnityEngine.Object.FindObjectsOfType<BaseInputModule>();

    // InputSystemProfileのチェック

    // InputActionRulesProfileのチェック

    // PointerProfileのチェック

    // イベントデータのインスタンス生成
    sourceStateEventData = new SourceStateEventData(EventSystem.current);
    ...

    // データプロバイダの生成
    CreateDataProviders();
}

データプロバイダの生成(CreateDataProviders)では、

  • GazeProvider が無ければ生成
  • プロファイルの Input Data Providers の内容(インスペクタで設定されているもの)に基づきデータプロバイダを生成し、dataProviders リストに登録

しています。

f:id:HiromuKato:20200821204657p:plain

次にポイントとなるのは、イベントハンドリングを行う Handle〇〇メソッドの部分になると思います。以下4つあります。

  • HandleEvent
  • HandlePointerEvent
  • HandleFocusChangedEvents
  • HandleFocusEvent

Handle〇〇メソッドが主に行っているのはイベントのディスパッチ処理で、イベントディスパッチメソッドは以下3つあります。

  • DispatchEventToGlobalListeners : グローバルリスナーにイベントをディスパッチ
  • DispatchEventToObjectFocusedByPointer : ポインターにフォーカスされたオブジェクトにイベントをディスパッチ
  • DispatchEventToFallbackHandlers : フォールバック処理(上記のどちらも行われなかった場合に行う処理)

グローバルリスナーとはオブジェクトにフォーカスしていない状態でイベントを実行するためのものになります。(例えば、何もフォーカスしていない状態でもエアタップしたらCubeを表示したい、といった場合などに利用する)

Handle〇〇メソッドは(大量にある)Raise△△メソッドから呼ばれています。

  • RaiseSourceDetected
  • RaiseSourceLost
  • RaiseSourceTrackingStateChanged
  • etc ...

イベント処理に関しては、すべて見ても重複する内容が多いので、例として ポインターをクリックした際に呼ばれる RaisePointerClicked メソッドについて確認をしてみます。

この RaisePointerClicked 自体は、ソリューションを検索をするとわかりますが何かしらのポインターから呼ばれるもので、 必要な情報が引数で渡ってきます。

f:id:HiromuKato:20200821204705p:plain

// まず public メソッドになっているここが外部から呼ばれる
public void RaisePointerClicked(IMixedRealityPointer pointer, MixedRealityInputAction inputAction, int count, Handedness handedness = Handedness.None, IMixedRealityInputSource inputSource = null)
{
    // インプットイベントデータを生成する
    pointerEventData.Initialize(pointer, inputAction, handedness, inputSource, count);

    HandleClick();
}
private void HandleClick()
{
    // HandlePointerEventにイベントデータとハンドラーを渡す
    HandlePointerEvent(pointerEventData, OnInputClickedEventHandler);
}

// HandlePointerEvent の引数にある OnInputClickedEventHandler は以下のように定義された delegate
private static readonly ExecuteEvents.EventFunction<IMixedRealityPointerHandler> OnInputClickedEventHandler =
        delegate (IMixedRealityPointerHandler handler, BaseEventData eventData)
        {
            // OnInputClickedEventHandler が呼ばれたら以下が実行される
            var casted = ExecuteEvents.ValidateEventData<MixedRealityPointerEventData>(eventData);
            handler.OnPointerClicked(casted); // ←★イベントが発生すると最終的にこれが呼ばれる
        };

// ポインタイベントの処理
private void HandlePointerEvent<T>(BaseEventData eventData, ExecuteEvents.EventFunction<T> eventHandler) where T : IMixedRealityPointerHandler
{
    var baseInputEventData = ExecuteEvents.ValidateEventData<BaseInputEventData>(eventData);

    // グローバルリスナーにイベントをディスパッチする
    DispatchEventToGlobalListeners(baseInputEventData, eventHandler);

    if (baseInputEventData.used)
    {
        // 使用済みとマークされている場合、イベントがそれ以上進行しないようにする
        return;
    }

    // ポインターにフォーカスされたオブジェクトにイベントをディスパッチする
    DispatchEventToObjectFocusedByPointer(pointerEventData.Pointer, baseInputEventData, false, eventHandler);

    if (!baseInputEventData.used)
    {
        // フォールバック処理を行う
        DispatchEventToFallbackHandlers(baseInputEventData, eventHandler);
    }
}

流れを追うと

  1. クリック(エアタップ)をすると RaisePointerClicked メソッドが何かしらのポインターから呼ばれる
  2. HandlePointerEvent メソッドに何かしらのデータと実行させたいメソッド(ハンドラー)を渡して呼ぶ
  3. グローバルリスナーにデータとハンドラーを送信する(イベントをディスパッチする)
  4. eventHandler.Invoke((T)handlerEntry.handler, eventData); が呼ばれ、渡されたメソッドが実行される(詳細については BaseEventSystem の HandleEvent 参照)
  5. ポインターにフォーカスされたオブジェクトにデータとハンドラーを送信する(イベントをディスパッチする)
  6. ExecuteEvents.ExecuteHierarchy(focusedObject, baseInputEventData, eventHandler); が呼ばれ、渡されたメソッドが実行される
  7. 上記処理が行われなかった場合(イベントデータのusedフラグがfalseの場合)はフォールバック処理が行われる

という形になります。

上の流れの中から、最終的には OnInputClickedEventHandler の中の

handler.OnPointerClicked(casted);

という部分が呼ばれることになりますが、これにより開発者が独自に定義した部分が呼ばれ、またその際にイベントデータがパラメータとして渡ってきます。

イベントデータに関しては以下のような継承関係になっています。

f:id:HiromuKato:20200821204652p:plain

詳細の説明については省略しますが、ポインターのクリックに関するイベントは以下で定義されているものになります。

private MixedRealityPointerEventData pointerEventData;

この pointerEventData は RaisePointerClicked メソッドが呼ばれたときに以下の部分で生成されています。

    // インプットイベントデータを生成する
    pointerEventData.Initialize(pointer, inputAction, handedness, inputSource, count);

このデータがパラメータとして渡って来るので必要があれば利用することができます。 どのようなデータが渡ってきているのかは以下のように実際に見てみるのが早いでしょう。

f:id:HiromuKato:20200821204655p:plain

これらのイベント処理の確認として簡単なサンプルを以下に示します。

例えば以下のようなクラスを実装して Hierarchy の GameObject にアタッチすると、オブジェクトにフォーカスした状態でクリックすると OnPointerClicked メソッド内の処理が実行されます。

public class PointerSample : MonoBehaviour, IMixedRealityPointerHandler
{
    public void OnPointerClicked(MixedRealityPointerEventData eventData)
    {
        // ★クリックしたときに呼ばれる処理
    }

    public void OnPointerDown(MixedRealityPointerEventData eventData)
    {
        // 何かしらの実装
    }

    public void OnPointerDragged(MixedRealityPointerEventData eventData)
    {
        // 何かしらの実装
    }

    public void OnPointerUp(MixedRealityPointerEventData eventData)
    {
        // 何かしらの実装
    }
}

グローバルリスナーを利用する場合(オブジェクトにフォーカスしていなくてもクリック時に何かしらの処理を行いたい場合)には以下のような実装になります。

public class PointerSample : MonoBehaviour, IMixedRealityPointerHandler
{
    private void OnEnable()
    {
        // グローバルリスナーに登録
        CoreServices.InputSystem?.RegisterHandler<IMixedRealityPointerHandler>(this);
    }

    private void OnDisable()
    {
        // グローバルリスナーの登録を解除
        CoreServices.InputSystem?.UnregisterHandler<IMixedRealityPointerHandler>(this);
    }

    public void OnPointerClicked(MixedRealityPointerEventData eventData)
    {
        // ★クリックしたときに呼ばれる処理(オブジェクトにフォーカスしていなくても実行される)
    }

    ...
}

以上を簡単にまとめると 「クリックした、オブジェクトにフォーカスした、ジェスチャーを開始した」などのイベントがますどこかで発生し、MixedRealityInputSystem の対応する Raise△△メソッドが呼ばれ、最終的に開発者が実装した処理が実行される という流れになります。

その他の部分についてはモーダル処理を行う部分があります。 これについては、一般的なGUIのアプリケーションでもあるようにダイアログ表示中などに他のボタンを押したり操作できないようにするもので、同様の物と考えれば良いと思います。

まとめ

ということで、MixedRealityInputSystem の実装について確認してみました。

ざっくりとまとめると MixedRealityInputSystem とは

  • データプロバイダーへのアクセスを提供する
  • 各種イベントの処理を行う

ものであると言えるかと思います(入力を取り扱う門番的なイメージ)。

Inputに関しては、 プロファイルが階層構造になっており、さらにいくつかのプロファイルがあったり、データプロバイダーもいくつかの種類があるので今後そのあたりについても見ていきたいと思います。