MRが楽しい

MRやVRについて学習したことを書き残す

Mixed Reality Toolkit 3 パブリックプレビューのドキュメントを少しずつ読み解く データバインディングとフレームワークのテーマ化(設計と機能)

本日は Mixed Reality Toolkit 3 の調査枠です。
Mixed Reality Toolkit 3 パブリックプレビューのドキュメントを少しずつ翻訳しつつ読み進めていきます。

Mixed Reality Toolkit 3 のドキュメント

以下のドキュメントを読み進めていきます。
docs.microsoft.com

今回は「データバインディングフレームワークのテーマ化」のページの設計と機能に関する項目を読み進めます。
docs.microsoft.com

概要

本項は MRTK3 のデータバインディングフレームワークのテーマ化についてです。
このフレームワークは1つ以上のデータソースから提供されるデータによって、実行時、動的に設定および更新可能なビジュアル要素を作成できます。

データバインディングとは

データバインディングはアプリケーションのUX(ビュー)と表示されるデータ(モデル)の接続を確立するためのプロセスです。
バインディングに正しい設定を行い、データが適切な通知を提供したとします。
このときにデータの値が変更されると、そのデータにバインドされている要素に変更が自動的に反映されます。

一般的なデータバインディングフレームワークの例

Delphi
Windows Presentation Framework (WPF .NET)
Windows Forms
・Angular
・Backbond
JavaFX Bindings

Windows Presentation Frameworkのデータバインディングブロック図

画像は参照ページより引用

MRTK3と同等のブロック図

画像は参照ページより引用

設計目標

クロスプラットフォーム
・あらゆる組織構造とデータソースの発信元をサポートする
・既存のコードベースまたはグリーンフィールドコードベースへ容易に統合できる
・デザイナーと開発者に優しい
・アプリケーションのライフサイクル中にいつでも有効/無効を切り替え可能とする
・現実世界のエンタープライズシナリオ(バックエンドDB、複雑なUXプレハブテンプレート)をサポートする
・MRTKではないUXコンポーネントや新しいビジュアル要素に簡単に適用できる
・スプライト、画像、マテリアル、アニメーション、オーディオクリップなどの任意のデータ型をバインドする
・既存のコードベースに触れることなく機能を簡単に拡張できる
・CPU、RAM、GC、フレームタイムを効率的に使用する
・様々なローカルまたはバックエンドのデータソースと容易に統合できる
・埋め込み/ランタイム状態/バックエンドのデータ・ソースを任意に同時に組み合わせる
・リスト表示のために任意のサイズのコレクションを効率的に処理する
・テーマ設定とデータバインディングを組み合わせてテーマ設定可能な動的データ要素とする
・変数データを表示する前に、オープンエンドの方法で検証および操作する
・他のMRTK機能への依存関係を最小限にする
・MRTK v2 および MRTK3 との互換性を持つ
・最小限の労力で簡単にホワイトラベルを付けたり、ストック資産にブランド化を適用する

主な機能

・オープンエンドのデータソースは永続化されたリモートデータストラテジ、またはRAMデータストラテジをサポートする
・オープンエンドのデータコンシューマはあらゆる UX バインディングとテーマのニーズをサポートする
・データソースとコンシューマ間の自動検出により、接続を簡素化する
バインディングプロファイルからオプションを自動構成する
・分離されたデータモデルとビューは MVC パターンと MVVM パターンをサポートする
・ページングとスクロールによるナビゲーションを備えた仮想化されたコレクション
・スムーズなリストナビゲーションのためのコレクション項目の予測プリフェッチ
・コレクションオブジェクトをプールして再利用して GC を減らす
・データの違いをマップしたり、キーパス名前空間を表示できます。

現在の機能

1. データコンシューマによる可変データの可視化

現在サポートされているもの

・TextMeshPro および TextMesh
・テキストスタイルシート (テーマとアクセシビリティ用)
・Sprite テクスチャ
・boolean トリガー
・Quad テクスチャ
・フォントアイコン
・コレクション(可変データを設定したプレハブを含む任意のサイズのリスト)
・IDataConsumer インターフェイスをサポートするその他のコンシューマー (直接または基本クラス派生経由)

2. 様々なデータソースを使用して可変データを提供する

JSON テキスト (直接または URL フェッチによる)
・可変データ要素の Dictionary
・オブジェクト(ノードベースの構造化データ)
・任意の C# オブジェクトのリフレクション
・プログラムによって変更されたデータ
・IDataSource インターフェイスをサポートするその他のメソッド

3. リストの視覚的なマニフェスト化を管理するためのリストアイテムの配置設定

4. ページング、スクロール、ビジュアルの一覧表示

・データは可視時または処理中の場合にのみフェッチされる
・任意に大きいバックエンドデータセットをサポートする
・フェッチは複数のフレーム間で負荷分散されます

5. プレハブプールの一覧表示

・プレハブは再利用され GCインスタンス化の時間が短縮されます。

6. 実行時に要素にテーマを動的に適用する

ロードマップの機能

既に利用可能なものに加えて、追加機能の最優先事項は以下のとおりです。

1. データマニピュレーターパイプライン

・データとビュー間の値の変換
ローカリゼーション (Unity ローカリゼーションとのシームレスな統合)
・フォーマッティング
・検証

2. 予測リスト項目のプリフェッチによるスクロール/ページングを高速/スムーズ化

3. より多種のデータコンシューマ

コンポーネントにパブリックプロパティを設定する
チェックボックスのオン/オフ状態を設定する
・スライダー値を設定する
・グループ内のラジオボタンを設定する
・カラーなどの個々のマテリアルプロパティを設定する

4.テーマ化

・アプリケーションを実行していない時もエディターで適用されたテーマを表示する
・テーマを反映させるためにプレハブを更新し、それがデフォルトのテーマになるようにする
・テーマ/スタイルを継承する

HoloLens2でホロモンアプリを作る その101(MRTK3環境で音声入力のキーワードを設定する)

本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。

今回は MRTK3 環境で音声入力のキーワードを設定する改修を行いました。

MRTK3環境で音声入力のキーワードを設定する

MRTK3 環境では PhraseRecognitionSubsystem サブシステムから音声入力のキーワードが設定できます。
bluebirdofoz.hatenablog.com

CreateOrGetEventForPhrase 関数からキーワードとそれに対応するアクションを登録できます。
>|cs|// Get the first running phrase recognition subsystem.
// 最初に動作するフレーズ認識サブシステムを取得します。
var phraseRecognitionSubsystem = XRSubsystemHelpers.GetFirstRunningSubsystem();

// If we found one...
// 見つかった場合...
if (phraseRecognitionSubsystem != null)
{
// Register a phrase and its associated action with the subsystem.
// サブシステムにフレーズとそれに関連するアクションを登録する。
phraseRecognitionSubsystem.CreateOrGetEventForPhrase("your phrase").AddListener*1;
}
|

サンプルスクリプト

指定した音声入力のキーワードと対応するアクションを登録するサンプルスクリプトを作成しました。

SpeechCommandEvent 型で設定したキーワードとイベントをアプリ起動時に自動的に登録します。
これにより、音声入力でキーワードを検知すると直ちに設定したイベントが実行されます。
・SpeechCommandEvent.cs

using System;
using UnityEngine.Events;

namespace HoloMonApp.Content.XRPlatform.MRTK3
{
    [Serializable]
    public class SpeechCommandEvent
    {
        public string Keyword;

        public UnityEvent KickEvent;
    }
}

・SpeechInputHandlerMRTK3.cs

#if XRPLATFORM_MRTK3
using Microsoft.MixedReality.Toolkit;
using Microsoft.MixedReality.Toolkit.Subsystems;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

namespace HoloMonApp.Content.XRPlatform.MRTK3
{
    public class SpeechInputHandlerMRTK3 : MonoBehaviour
    {
        [SerializeField]
        private List<SpeechCommandEvent> p_SpeechCommandEventList;

        void Start()
        {
            // Get the first running phrase recognition subsystem.
            PhraseRecognitionSubsystem phraseRecognitionSubsystem = XRSubsystemHelpers.GetFirstRunningSubsystem<PhraseRecognitionSubsystem>();

            // If we found one...
            if (phraseRecognitionSubsystem != null)
            {
                foreach(SpeechCommandEvent speechCommandEvent in p_SpeechCommandEventList)
                {
                    string keyword = speechCommandEvent.Keyword;
                    UnityEvent kickEvent = speechCommandEvent.KickEvent;

                    // Register a phrase and its associated action with the subsystem
                    phraseRecognitionSubsystem.CreateOrGetEventForPhrase(keyword).AddListener(() => kickEvent.Invoke());
                }
            }
        }
    }
}
#endif

動作確認

シーンを再生してシミュレーションで「じゃんけん」キーワードを発声すると、ホロモンがじゃんけんのリアクションをしてくれました。

*1:) => Debug.Log("Phrase recognized"

MRTKのシェーダをビルトインレンダラー環境からURP環境への変更に合わせてアップグレードする

本日は MRTK の小ネタ枠です。
MRTKのシェーダをビルトインレンダラー環境からURP環境への変更に合わせてアップグレードする手順を記事にします。

サンプルシーンの作成

初めにビルトインレンダラー環境で構築した MRTK のサンプルシーンを用意しました。
このシーンには MRTKStandardShader を反映したゲームオブジェクトを配置しています。

URP環境への変更

まず Unity プロジェクト自体をビルトインレンダラーの環境からURPの環境に変更します。
メニューから[Window -> Package Manager]を開きます。

[Packages: Unity Registry]から[Universal RP]を検索して[Install]を実行します。

Assets フォルダから右クリックで[Create -> Rendering -> Universal Render Pipeline -> Pipeline Asset]を担当する人を選択します。
これで URP のパイプラインアセットが生成されます。

メニューから[Edit -> Project Settings..]ダイアログを開き、[Graphics]タブを開きます。

[scriptable Render Pipeline Settings]の項目に作成したパイプラインアセットを設定してURP環境への変更は完了です。

MRTKのシェーダをアップグレード

Unity プロジェクトを URP 環境に変更すると MRTKStandardShader を反映したゲームオブジェクトが正常に表示されなくなります。
正常に表示するには MRTKStandardShader を URP に対応したシェーダにアップグレードする必要があります。

メニューから[Mixed Reality -> Toolkit -> Utilities -> Upgrade MRTK Standard Shader for Universal Render Pipeline]を実行することでシェーダをアップグレードできます。

シェーダの変更後は元に戻すことはできない旨のダイアログが表示されます。
[OK]を選択してアップグレードを実行します。

以下の通り MRTK のシェーダがアップグレードされ、URP 環境でも正常に表示されるようになりました。

参考ページ

docs.microsoft.com

Shader Graphを使ってシェーダを作成する

本日は Unity の小ネタ枠です。
Shader Graph を使ってシェーダを作成する手順を記事にします。

Shader Graph

Shader Graph はシェーダーを視覚的に構築するためのツールです。Unity 2018.1 以降の Unity で使用できます。
URP(ユニバーサルレンダーパイプライン)やHDRP(HDレンダーパイプライン)などのスクリプタブルレンダーパイプラインをインストールすると、自動的にプロジェクトにインストールされます。
docs.unity3d.com

Unityプロジェクトの作成

Shader Graph は URP または HDRP 環境のプロジェクトで利用できます。
今回は[3D Sample Scene]をサンプルシーンに選択して Unity プロジェクトを新規作成しました。

ShaderGraphの作成

新規に Shader Graph でシェーダを作成する場合は、Asset フォルダで右クリックから[Create -> Shader -> Blank Shader Graph]を選択します。

ファイルが生成されるのでダブルクリックで選択すると Shader Graph の編集画面が開きます。

初めにターゲットのレンダーパイプラインを指定します。
[Graph Inspector]の[Graph Settings -> Active Targets]の[+]ボタンから[Universal]を選択します。

デフォルトの Lit Shader Graph が生成されます。
編集画面右上の[Save Asset]で Shader Graph の変更内容を保存します。

Materialへの設定とシーンへの反映

作成したシェーダを元にマテリアルを作成し、シーンに反映してみます。
Asset フォルダで右クリックから[Create -> Material]を選択します。

作成したマテリアルを選択して[Shader]項目から作成した Shader Graph のシェーダを選択します。

これでシェーダを利用したマテリアルが作成できました。
[Scene]画面を開き、マテリアルを反映したいオブジェクトに直接ドロップすることでマテリアルをシーンに反映できます。

ShaderGraphの編集

この状態で Shader Graph を編集してシーンへの変化を確認してみます。
再び Shader Graph の編集画面を開きます。[スペースキー]または右クリックから[Create Node]を選択して Node を追加します。

今回はシェーダのベースカラーを変更してみます。
[Color]ノードを選択して追加します。

追加された[Color]ノードの[Out]スロットから接続ラインをドラッグして[Fragment]ノードの[Base Color]に接続します。

[Color]ノードのカラーバーをクリックして、カラーピッカーからベースカラーとしたい色を選択します。

変更内容を保存します。編集画面右上の[Save Asset]をクリックします。

シーンを確認すると、設定したマテリアルの見た目が変化しています。

参考ページ

docs.unity3d.com

HoloLens2でホロモンアプリを作る その100(MRTK3環境でハンドジョイントの情報を取得する)

本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。

今回は MRTK3 環境でハンドジョイントの情報を取得する改修を行いました。

MRTK3環境でハンドジョイントの情報を取得する

MRTK3 環境では HandsAggregatorSubsystem サブシステムからハンドジョイントの情報が取得できます。
bluebirdofoz.hatenablog.com

TryGetEntireHand 関数で右手または左手の指定をすることで各関節のポーズ情報を取得できます。

// Get an entire hand's worth of joints from the left hand.
// 左手から手のひら全体のジョイントデータを取る。
bool allJointsAreValid = aggregator.TryGetEntireHand(XRNode.LeftHand, out IReadOnlyList<HandJointPose> joints);

サンプルスクリプト

ハンドジョイントの情報が更新された場合はイベントを発火するサンプルスクリプトを作成しました。

TryGetEntireHand 関数で取得できるポーズ情報のリストは手首(Wrist)から小指の先(PinkyTip)までの 26 箇所の関節のポーズ情報です。
スクリプトではこれらを MRTK2 と処理を共通化するために自作の型に落とし込んでイベントを投げています。
・HandTrackingHandlerMRTK3

#if XRPLATFORM_MRTK3
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

using UniRx;
using UniRx.Triggers;

using UnityEngine.XR;
using UnityEngine.XR.WSA.Input;

using Microsoft.MixedReality.Toolkit;
using Microsoft.MixedReality.Toolkit.Subsystems;
using Microsoft.MixedReality.Toolkit.Input;

namespace HoloMonApp.Content.XRPlatform.MRTK3
{
    public class HandTrackingHandlerMRTK3
    {
        private HandsAggregatorSubsystem p_Aggregator;

        private IDictionary<XRNode, bool> prevAllJointsAreValid;

        private IDictionary<HandTrackingHandJoint, HandTrackingJointPose> p_ConvertHandJointPoseDictionary;

        public HandTrackingHandlerMRTK3(Component a_Component)
        {
            p_ConvertHandJointPoseDictionary = CreateHandJointPoseDictionary();

            p_Aggregator = XRSubsystemHelpers.GetFirstRunningSubsystem<HandsAggregatorSubsystem>();

            prevAllJointsAreValid = new Dictionary<XRNode, bool>();

            a_Component.UpdateAsObservable()
                .Subscribe(_ =>
                {
                    CheckUpdate();
                })
                .AddTo(a_Component);
        }


        // 手の検出時に呼び出すアクション
        public Action<HandTrackingSourceStateEventData> OnSourceDetectedAction;

        // 手のロスト時に呼び出すアクション
        public Action<HandTrackingSourceStateEventData> OnSourceLostAction;

        // 手の更新時に呼び出すアクション
        public Action<HandTrackingInputEventData> OnHandJointsUpdatedAction;


        #region "privete"
        private void CheckUpdate()
        {
            if (p_Aggregator == null) return;
            HandTrackingEventHandle(XRNode.RightHand);
            HandTrackingEventHandle(XRNode.LeftHand);
        }

        private void HandTrackingEventHandle(XRNode a_XRNode)
        {
            if (!prevAllJointsAreValid.ContainsKey(a_XRNode)) prevAllJointsAreValid[a_XRNode] = false;

            // 指定の手のハンドトラッキングを調べる
            bool allJointsAreValid = p_Aggregator.TryGetEntireHand(a_XRNode, out IReadOnlyList<HandJointPose> jointList);

            // IDを取得する
            uint sourceId = (uint)(XRNodeExtensions.ToHandedness(a_XRNode));

            // ハンド種別を取得する
            HandTrackingHandedness handedness = ConvertHandnessMRTK3(XRNodeExtensions.ToHandedness(a_XRNode));

            // ジョイントリストをDictionary型に変換する
            p_ConvertHandJointPoseDictionary = ConvertHandJointPosesMRTK3(jointList, p_ConvertHandJointPoseDictionary);

            // イベント実行
            if (allJointsAreValid && !prevAllJointsAreValid[a_XRNode])
            {
                // 検出
                HandTrackingSourceStateEventData data = new HandTrackingSourceStateEventData(DateTime.Now, sourceId);

                // アクション呼び出し
                OnSourceDetectedAction?.Invoke(data);
            }
            else if (!allJointsAreValid && prevAllJointsAreValid[a_XRNode])
            {
                // ロスト
                HandTrackingSourceStateEventData data = new HandTrackingSourceStateEventData(DateTime.Now, sourceId);

                // アクション呼び出し
                OnSourceLostAction?.Invoke(data);
            }

            if (allJointsAreValid)
            {
                // 検出中処理
                HandTrackingInputEventData data = new HandTrackingInputEventData(sourceId, handedness, p_ConvertHandJointPoseDictionary);

                // アクション呼び出し
                OnHandJointsUpdatedAction?.Invoke(data);
            }

            prevAllJointsAreValid[a_XRNode] = allJointsAreValid;
        }
        #endregion


        #region "MRTK3 private"
        private IDictionary<HandTrackingHandJoint, HandTrackingJointPose> CreateHandJointPoseDictionary()
        {
            IDictionary<HandTrackingHandJoint, HandTrackingJointPose> handJointPosesDictionary
                = new Dictionary<HandTrackingHandJoint, HandTrackingJointPose>();
            HandTrackingJointPose handJointPose = null;

            // ジョイントに対応するキー名を付与する
            for (int index = 0; index < (int)Enum.GetNames(typeof(HandTrackingHandJoint)).Length; index++)
            {
                HandTrackingHandJoint trackedHandJointKey = (HandTrackingHandJoint)index;
                handJointPosesDictionary.Add(trackedHandJointKey, handJointPose);
            }

            return handJointPosesDictionary;
        }


        private HandTrackingHandedness ConvertHandnessMRTK3(Handedness a_Handedness)
        {
            HandTrackingHandedness convertHandedness = HandTrackingHandedness.None;
            switch (a_Handedness)
            {
                case Handedness.None:
                    break;
                case Handedness.Left:
                    convertHandedness = HandTrackingHandedness.Left;
                    break;
                case Handedness.Right:
                    convertHandedness = HandTrackingHandedness.Right;
                    break;
            }
            return convertHandedness;
        }

        private IDictionary<HandTrackingHandJoint, HandTrackingJointPose> ConvertHandJointPosesMRTK3
            (IReadOnlyList<HandJointPose> a_JointPoseList,
            IDictionary<HandTrackingHandJoint, HandTrackingJointPose> a_HandJointPosesDictionary)
        {
            // ジョイントに対応するキー名を付与する
            for (int index = 0; index < (int)TrackedHandJoint.TotalJoints; index++)
            {
                // None 識別子を無視するためインデックスより 1 インクリメントする
                // (最初に Wrist の情報が代入されている)
                TrackedHandJoint trackedHandJointKey = (TrackedHandJoint)index + 1;
                HandTrackingHandJoint convertTrackedHandJoint = ConvertTrackedHandJointMRTK3(trackedHandJointKey);
                HandTrackingJointPose convertMixedRealityPose = ConvertMixedRealityPoseMRTK3(a_JointPoseList[index]);
                a_HandJointPosesDictionary[convertTrackedHandJoint] = convertMixedRealityPose;
            }

            return a_HandJointPosesDictionary;
        }

        private HandTrackingHandJoint ConvertTrackedHandJointMRTK3(TrackedHandJoint a_TrackedHandJoint)
        {
            HandTrackingHandJoint convertTrackedHandJoint = HandTrackingHandJoint.None;
            switch (a_TrackedHandJoint)
            {
                case TrackedHandJoint.None:
                    break;
                case TrackedHandJoint.Wrist:
                    convertTrackedHandJoint = HandTrackingHandJoint.Wrist;
                    break;
                case TrackedHandJoint.Palm:
                    convertTrackedHandJoint = HandTrackingHandJoint.Palm;
                    break;
                case TrackedHandJoint.ThumbMetacarpalJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.ThumbMetacarpalJoint;
                    break;
                case TrackedHandJoint.ThumbProximalJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.ThumbProximalJoint;
                    break;
                case TrackedHandJoint.ThumbDistalJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.ThumbDistalJoint;
                    break;
                case TrackedHandJoint.ThumbTip:
                    convertTrackedHandJoint = HandTrackingHandJoint.ThumbTip;
                    break;
                case TrackedHandJoint.IndexMetacarpal:
                    convertTrackedHandJoint = HandTrackingHandJoint.IndexMetacarpal;
                    break;
                case TrackedHandJoint.IndexKnuckle:
                    convertTrackedHandJoint = HandTrackingHandJoint.IndexKnuckle;
                    break;
                case TrackedHandJoint.IndexMiddleJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.IndexMiddleJoint;
                    break;
                case TrackedHandJoint.IndexDistalJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.IndexDistalJoint;
                    break;
                case TrackedHandJoint.IndexTip:
                    convertTrackedHandJoint = HandTrackingHandJoint.IndexTip;
                    break;
                case TrackedHandJoint.MiddleMetacarpal:
                    convertTrackedHandJoint = HandTrackingHandJoint.MiddleMetacarpal;
                    break;
                case TrackedHandJoint.MiddleKnuckle:
                    convertTrackedHandJoint = HandTrackingHandJoint.MiddleKnuckle;
                    break;
                case TrackedHandJoint.MiddleMiddleJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.MiddleMiddleJoint;
                    break;
                case TrackedHandJoint.MiddleDistalJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.MiddleDistalJoint;
                    break;
                case TrackedHandJoint.MiddleTip:
                    convertTrackedHandJoint = HandTrackingHandJoint.MiddleTip;
                    break;
                case TrackedHandJoint.RingMetacarpal:
                    convertTrackedHandJoint = HandTrackingHandJoint.RingMetacarpal;
                    break;
                case TrackedHandJoint.RingKnuckle:
                    convertTrackedHandJoint = HandTrackingHandJoint.RingKnuckle;
                    break;
                case TrackedHandJoint.RingMiddleJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.RingMiddleJoint;
                    break;
                case TrackedHandJoint.RingDistalJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.RingDistalJoint;
                    break;
                case TrackedHandJoint.RingTip:
                    convertTrackedHandJoint = HandTrackingHandJoint.RingTip;
                    break;
                case TrackedHandJoint.PinkyMetacarpal:
                    convertTrackedHandJoint = HandTrackingHandJoint.PinkyMetacarpal;
                    break;
                case TrackedHandJoint.PinkyKnuckle:
                    convertTrackedHandJoint = HandTrackingHandJoint.PinkyKnuckle;
                    break;
                case TrackedHandJoint.PinkyMiddleJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.PinkyMiddleJoint;
                    break;
                case TrackedHandJoint.PinkyDistalJoint:
                    convertTrackedHandJoint = HandTrackingHandJoint.PinkyDistalJoint;
                    break;
                case TrackedHandJoint.PinkyTip:
                    convertTrackedHandJoint = HandTrackingHandJoint.PinkyTip;
                    break;
            }

            return convertTrackedHandJoint;
        }

        private HandTrackingJointPose ConvertMixedRealityPoseMRTK3(HandJointPose a_HandJointPose)
        {
            HandTrackingJointPose convertMixedRealityPose = new HandTrackingJointPose(a_HandJointPose.Position, a_HandJointPose.Rotation);
            return convertMixedRealityPose;
        }
#endregion
    }
}
#endif

動作確認

シーンを再生してシミュレーションでハンドを表示すると、ホロモンがハンドジェスチャーに対する反応をしてくれました。
HandsAggregatorSubsystem サブシステムは Unity エディター上ではシミュレータのハンド情報を、実機上ではデバイスで取得されたハンド情報を返却します。

HoloLens Meetup vol.30での発表資料

HoloLens Meetup vol.30 での発表資料です。
発表内容は「ホロモンアプリをMRTK3で動かしてみた(ハンドトラッキングと音声入力の対応)」です。

www.slideshare.net

イベント情報

イベント情報は以下になります。
hololens.connpass.com

HoloLens2でホロモンアプリを作る その99(MRTK3環境とMRTKv2環境で動作するコードを切り替える)

本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。

今回は MRTK3 の習熟を兼ねてホロモンアプリを MRTK3 環境で動作するように改修を行いました。

MRTK3環境とMRTKv2環境の切り替え

現在(2022/6/22)時点では MRTK3 はパブリックプレビューです。
あくまでメインの開発環境は MRTKv2 としたいので MRTK3 環境と MRTKv2 環境で動作するコードを切り替える仕組みを追加してみました。

Scripting Define Symbols

今回は Scripting Define Symbols の設定を使って MRTK3 と MRTKv2 の利用を切り替えました。
docs.unity3d.com

実装例

例えば MRTKv2 でプレイヤーの頭部座標を取得する際、Microsoft.MixedReality.Toolkit.Utilities.CameraCache でカメラ情報が取得できます。
しかし MRTK3 では Microsoft.MixedReality.Toolkit.CameraCache のパスに切り替わっているようでした。
このような MRTKv2 と MRTK3 に依存する処理を別クラスでまとめて、以下のように定義で参照を切り替える形にコードを見直しました。

カメラ情報の取得例

・MainCameraAccess.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if XRPLATFORM_MRTK3
using HoloMonApp.Content.XRPlatform.MRTK3;
#elif XRPLATFORM_MRTKV2
using HoloMonApp.Content.XRPlatform.MRTKv2;
#endif

namespace HoloMonApp.Content.XRPlatform
{
    /// <summary>
    /// カメラ参照の切り替えクラス
    /// </summary>
    public class MainCameraAccess
    {
#if XRPLATFORM_MRTK3
        private MainCameraAccessMRTK3 p_MainCameraAccess;
#elif XRPLATFORM_MRTKV2
        private MainCameraAccessMRTKv2 p_MainCameraAccess;
#endif

        public MainCameraAccess(Component a_Component)
        {
#if XRPLATFORM_MRTK3
            p_MainCameraAccess = new MainCameraAccessMRTK3();
#elif XRPLATFORM_MRTKV2
            p_MainCameraAccess = new MainCameraAccessMRTKv2();
#endif
    }

    public Camera GetMainCamera()
        {
            Camera camera = null;
#if XRPLATFORM_MRTK3
            camera = p_MainCameraAccess.GetMainCamera();
#elif XRPLATFORM_MRTKV2
            camera = p_MainCameraAccess.GetMainCamera();
#endif
            return camera;
        }
    }
}

・MainCameraAccessMRTKv2.cs

#if XRPLATFORM_MRTKV2
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// CameraCacheへのアクセスのため
using Microsoft.MixedReality.Toolkit.Utilities;

namespace HoloMonApp.Content.XRPlatform.MRTKv2
{
    public class MainCameraAccessMRTKv2
    {
        public MainCameraAccessMRTKv2()
        { }

        public Camera GetMainCamera()
        {
            return CameraCache.Main;
        }
    }
}
#endif

・MainCameraAccessMRTK3.cs

#if XRPLATFORM_MRTK3
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// CameraCacheへのアクセスのため
using Microsoft.MixedReality.Toolkit;

namespace HoloMonApp.Content.XRPlatform.MRTK3
{
    public class MainCameraAccessMRTK3
    {
        public MainCameraAccessMRTK3(){ }

        public Camera GetMainCamera()
        {
            return CameraCache.Main;
        }
    }
}
#endif

その他、ハンドトラッキングや音声入力なども同じように同様の処理を実現する実装方法を調査して上記スクリプトのように比較できる形で進めていきます。