本日は MRTK の小ネタ枠です。
MRTK で両手から伸びるハンドレイのポインターの座標を取得する方法を記事にします。
MRTKのポインタ
MRTK のポインターは様々なポインタークラスで構成されています。
これらは InputSystem からアクセスすることができ、ハンドレイのポインターもここに含まれます。
docs.microsoft.com
docs.microsoft.com
本記事では右手と左手の両方のハンドレイからポインターの座標を取得して表示してみます。
サンプルプロジェクトの作成
MRTK をインポートしたサンプルプロジェクトを作成します。
MRTK のインポートと基本設定
MRTK のインポートと HoloLens 向けプロジェクトの基本設定を行い、サンプルプロジェクトを作成します。
手順の詳細は以下の記事を参照してください。
bluebirdofoz.hatenablog.com
サンプルシーンの作成
Unity エディター上で動作確認を行うため、Plane オブジェクトで簡単に壁を作成したシーンを準備しました。
サンプルコード
ハンドレイのポインター座標を取得する以下のサンプルスクリプトを作成しました。
左右または両手が表示されている間、ポインター座標に指定されたプレハブオブジェクトを表示します。
・HandRayTracking.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using Microsoft.MixedReality.Toolkit; using Microsoft.MixedReality.Toolkit.Utilities; using Microsoft.MixedReality.Toolkit.Input; public class HandRayTracking : MonoBehaviour, IMixedRealityHandJointHandler, IMixedRealitySourceStateHandler { [SerializeField, Tooltip("ポインターとして表示するプレハブ")] private GameObject p_PointerPrefab; [SerializeField, Tooltip("参照するハンドタイプの指定")] private Handedness p_HandednessType; /// <summary> /// スポーンオブジェクトの参照 /// </summary> private GameObject p_Pointer; /// <summary> /// 現在スクリプトが参照中のハンドレイ /// </summary> private ShellHandRayPointer p_ShellHandRayPointer; /// <summary> /// 手の検出時に発生するイベント(IMixedRealitySourceStateHandler) /// </summary> /// <param name="eventData"></param> public void OnSourceDetected(SourceStateEventData eventData) { // 既に対象を検出済みの場合は処理しない if (p_ShellHandRayPointer != null) return; // 現在監視対象のポインターが存在するか ShellHandRayPointer handRayPointer = DetectionTargetHandRay(); if (handRayPointer == null) return; // 対象が見つかった場合参照を取得しておく p_ShellHandRayPointer = handRayPointer; // ポインターオブジェクトを生成する p_Pointer = Instantiate(p_PointerPrefab); } /// <summary> /// 手のロスト時に発生するイベント(IMixedRealitySourceStateHandler) /// </summary> /// <param name="eventData"></param> public void OnSourceLost(SourceStateEventData eventData) { // 既に対象を削除済みの場合は処理しない if (p_ShellHandRayPointer == null) return; // 現在監視対象のポインターが存在するか ShellHandRayPointer handRayPointer = DetectionTargetHandRay(); if (handRayPointer != null) return; // 対象が見つからなくなっている場合ロスト処理を行う p_ShellHandRayPointer = null; // ポインターオブジェクトを破棄する Destroy(p_Pointer); } /// <summary> /// 手の更新時に発生するイベント(IMixedRealityHandJointHandler) /// </summary> /// <param name="eventData"></param> public void OnHandJointsUpdated(InputEventData<IDictionary<TrackedHandJoint, MixedRealityPose>> eventData) { // 監視対象のポインターが取得済みか否か if (p_ShellHandRayPointer == null) return; // ポインターのレイキャスト座標にポインターオブジェクトを移動する Vector3 handRayPosition = p_ShellHandRayPointer?.BaseCursor?.Position ?? new Vector3(); p_Pointer.transform.position = handRayPosition; } /// <summary> /// 有効時処理 /// </summary> private void OnEnable() { // ハンドラ登録 CoreServices.InputSystem?.RegisterHandler<IMixedRealityHandJointHandler>(this); CoreServices.InputSystem?.RegisterHandler<IMixedRealitySourceStateHandler>(this); } /// <summary> /// 無効時処理 /// </summary> private void OnDisable() { // ハンドラ解除 CoreServices.InputSystem?.UnregisterHandler<IMixedRealityHandJointHandler>(this); CoreServices.InputSystem?.UnregisterHandler<IMixedRealitySourceStateHandler>(this); } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } private ShellHandRayPointer DetectionTargetHandRay() { ShellHandRayPointer handRayPointer = null; // 現在の InputSystem に対象が存在するかチェックする foreach (IMixedRealityInputSource inputSource in CoreServices.InputSystem.DetectedInputSources) { foreach (IMixedRealityPointer pointer in inputSource.Pointers) { // ハンドレイでなければ対象外 if (pointer.GetType() != typeof(ShellHandRayPointer)) continue; // 指定のハンドタイプでなければ対象外 if (((ShellHandRayPointer)pointer).Handedness != p_HandednessType) continue; handRayPointer = (ShellHandRayPointer)pointer; } } return handRayPointer; } }
作成したスクリプトをシーン内の適当なオブジェクトに設定します。
今回は右手と左手のポインターを別々に追跡したいので[HandednessType]にそれぞれ[Right]と[Left]を設定した2つのコンポーネントを設定しておきます。