本日はMetaQuest3の技術調査枠です。
MRTKv2.xを使ってMetaQuest3向けのUnityプロジェクト作成を行う手順を記事にします。
本記事はボタンを押したコントローラにメニューを追従させる方法です。
前提条件
以下の記事で作成したサンプルスクリプトとUnityプロジェクトの設定を組み合わせてボタンを押したコントローラにメニューを追従させるようにしてみました。
bluebirdofoz.hatenablog.com
bluebirdofoz.hatenablog.com
コントローラのボタンの長押しを検知する
MRTKの入力イベントを使ってコントローラを直接追従するサンプルスクリプトを以下の通り改修しました
・MRTKMotionControllerSolverHandler.cs
using System; using System.Collections; using System.Collections.Generic; using Microsoft.MixedReality.Toolkit; using Microsoft.MixedReality.Toolkit.Input; using Microsoft.MixedReality.Toolkit.Utilities; using UnityEngine; using UnityEngine.Serialization; public class MRTKMotionControllerSolverHandler : MonoBehaviour, IMixedRealityInputHandler<MixedRealityPose>, IMixedRealitySourceStateHandler { /// <summary> /// 追跡対象のコントローラの種類に右手を含めるか /// </summary> /// <param name="isOn"></param> public void SwitchTrackedTargetHandednessRight(bool isOn) { if (isOn) { targetTrackedHandedness |= TrackedTargetHandednessType.Right; } else { targetTrackedHandedness &= ~TrackedTargetHandednessType.Right; // 右手を追跡中なら状態をリセットする currentTrackingSourceId = 0; currentTrackingHandedness = Handedness.None; lastUpdateTime = 0; } } /// <summary> /// 追跡対象のコントローラの種類に左手を含めるか /// </summary> /// <param name="isOn"></param> public void SwitchTrackedTargetHandednessLeft(bool isOn) { if (isOn) { targetTrackedHandedness |= TrackedTargetHandednessType.Left; } else { targetTrackedHandedness &= ~TrackedTargetHandednessType.Left; // 左手を追跡中なら状態をリセットする currentTrackingSourceId = 0; currentTrackingHandedness = Handedness.None; lastUpdateTime = 0; } } /// <summary> /// 追従の有効化 /// </summary> public bool UpdateSolver = true; /// <summary> /// 追従するコントローラの種類 /// </summary> [SerializeField] private TrackedTargetHandednessType targetTrackedHandedness = TrackedTargetHandednessType.None; /// <summary> /// 向きの振る舞い /// </summary> [SerializeField] private RotationBehaviorType rotationBehavior = RotationBehaviorType.LookAtMainCamera; /// <summary> /// 位置のオフセット /// </summary> [SerializeField] private float saveZoneOffset = 0.1f; /// <summary> /// エディター上ではハンドオブジェクトをコントローラ替わりに追従する /// </summary> /// <returns></returns> [SerializeField] private bool handTrackingInEditor = true; /// <summary> /// 現在追従中のソースID(追従していないとき:0) /// </summary> [SerializeField] private uint currentTrackingSourceId = 0; /// <summary> /// 現在追跡中のコントローラの種類 /// </summary> [SerializeField] private Handedness currentTrackingHandedness = Handedness.None; private void OnEnable() { // コントローラの位置と回転を追従するためにInputSystemに登録する CoreServices.InputSystem?.RegisterHandler<IMixedRealityInputHandler<MixedRealityPose>>(this); CoreServices.InputSystem?.RegisterHandler<IMixedRealitySourceStateHandler>(this); } private void OnDisable() { // InputSystemから登録を解除する CoreServices.InputSystem?.UnregisterHandler<IMixedRealityInputHandler<MixedRealityPose>>(this); CoreServices.InputSystem?.UnregisterHandler<IMixedRealitySourceStateHandler>(this); } /// <summary> /// 最後の更新時間 /// </summary> private float lastUpdateTime = 0; public void OnSourceDetected(SourceStateEventData eventData) { // 追従が無効なら何もしない if(UpdateSolver == false) return; // OnSourceDetectedではHandednessが取得できないので追跡対象の判定はOnInputChangedで行う } public void OnSourceLost(SourceStateEventData eventData) { // 追従が無効なら何もしない if(UpdateSolver == false) return; // 現在追従中のソースがロストすれば追跡対象を解除する if (currentTrackingSourceId == eventData.SourceId) { currentTrackingSourceId = 0; lastUpdateTime = 0; } } public void OnInputChanged(InputEventData<MixedRealityPose> eventData) { // 追従が無効なら何もしない if(UpdateSolver == false) return; // 入力ソースの種類、ソースID、ハンドタイプを取得する var trackedSourceId = eventData.SourceId; var trackedTargetType = eventData.InputSource.SourceType; var trackedHandedness = eventData.Handedness; // 現在追従中のソースがなければ追跡対象か判定を行う if (currentTrackingSourceId == 0) { // 追従対象ならソースIDを保持する if (IsTrackingTargetData(eventData)) currentTrackingSourceId = trackedSourceId; currentTrackingHandedness = trackedHandedness; } // 現在追従中のソースと異なるソースの場合は何もしない if (currentTrackingSourceId != trackedSourceId) return; // 追従対象の場合は現在のオブジェクトの向きを設定に応じて更新する switch (rotationBehavior) { case RotationBehaviorType.None: this.transform.rotation = Quaternion.Lerp(this.transform.rotation, eventData.InputData.Rotation, 0.01f); break; case RotationBehaviorType.LookAtMainCamera: this.transform.LookAt(CameraCache.Main.transform); break; default: throw new ArgumentOutOfRangeException(); } // 追従対象の場合は現在のオブジェクトの位置をコントローラの位置と現在の回転で更新する var controllerPosition = eventData.InputData.Position; var selfRotation = this.transform.rotation; var direction = trackedHandedness switch { Handedness.Left => selfRotation * Vector3.left, Handedness.Right => selfRotation * Vector3.right, _ => Vector3.zero }; var targetPosition = controllerPosition + direction * saveZoneOffset; // 更新間隔を基にlerpの係数を決定する var lerpFactor = Mathf.Clamp01((Time.time - lastUpdateTime) * 10.0f); this.transform.position = Vector3.Lerp(this.transform.position, targetPosition, lerpFactor); lastUpdateTime = Time.time; } /// <summary> /// イベントデータが追従対象のコントローラか判定する /// </summary> /// <param name="eventData"></param> /// <returns></returns> private bool IsTrackingTargetData(InputEventData<MixedRealityPose> eventData) { // 入力ソースの種類、ソースID、ハンドタイプを取得する var trackedSourceId = eventData.SourceId; var trackedTargetType = eventData.InputSource.SourceType; var trackedHandedness = eventData.Handedness; // エディター上かつhandTrackingInEditorが有効ならハンドオブジェクトをコントローラ替わりに追従する if (Application.isEditor && handTrackingInEditor && trackedTargetType == InputSourceType.Hand) { trackedTargetType = InputSourceType.Controller; } // コントローラでない場合は追従しない if (trackedTargetType != InputSourceType.Controller) return false; // 追従するコントローラの種類と異なる場合は追従しない switch (this.targetTrackedHandedness) { case TrackedTargetHandednessType.None: // Noneの場合追従しない return false; case TrackedTargetHandednessType.Left: if (trackedHandedness != Handedness.Left) return false; break; case TrackedTargetHandednessType.Right: if (trackedHandedness != Handedness.Right) return false; break; case TrackedTargetHandednessType.Both: // 全ての種類を追従する break; } // 追従対象と判定する return true; } [Flags] private enum TrackedTargetHandednessType { None = 0, Left = 1 << 0, Right = 1 << 1, Both = ~0, } private enum RotationBehaviorType { None, LookAtMainCamera, } }
ボタン押下開始時と終了時のイベントを設定する
適当なゲームオブジェクトに2つのInputActionHandlerコンポーネントを追加します。
常にボタンのイベントを検知できるように[IsFocusRequired]のチェックを外し、[InputAction]にそれぞれ右手と左手のアクションを指定します。
[OnInputActionStarted]と[OnInputActionEnded]イベントにボタン押下開始と終了時のイベントとしてサンプルスクリプトのSwitch~関数を登録し、右手と左手の追跡設定を変更します。
ビルドと動作確認
以下の記事を参考にプロジェクトのビルドとQuest3へのデプロイを実行してください。
bluebirdofoz.hatenablog.com
MetaQuest3でデプロイしたアプリを起動し、左手のコントローラのXボタンを押します。
メニューの左手の追従設定が有効になり、ボタン押下中は左手のコントローラにメニューが追従します。
同じように右手のコントローラのAボタンを押すと、右手のコントローラにメニューが追従します。