0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Meta Quest】コントローラーとハンドトラッキングを左右で使い分けるアプリの作り方

0
Posted at

アドカレ

KENTOのひとりアドカレ19日目の記事です。
https://qiita.com/advent-calendar/2025/kento

環境情報

ツール/SDK バージョン
Unity 6000.0.62f1
Meta XR Core SDK 81.0.0
Open XR Plugin 1.16.0
XR Interaction Toolkit 3.0.9
XR Hands 1.7.1

事前準備は以下の通りです。

デモ

GIFのように左手はコントローラー、右手はハンドトラッキングといった使い分けを想定したアプリを作る方法をメモします。

2025AdventCalendar32.gif

マルチモーダル

前回記事で取り上げたマルチモーダルが前提となります。

参考リンク:【Meta Quest】コントローラーとハンドトラッキングをシームレスに切り替える

そのうえで、必要なGameObject以外はXROrigin配下から削除します。今回の例では、LeftHandとRightControllerを削除しています。
image.png

手のモデルの表示管理

マルチモーダルにしても、コントローラー利用側の手が表示されてしまうので、独自に管理が必要です。私の知る限りでは、どちらかの手のみハンドトラッキングの認識を無効化するということはできないので、表示だけオフにするようにします。

using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR.Hands;

/// <summary>
/// 手のメッシュ表示を管理するクラス。
/// </summary>
public class HandDrawer : MonoBehaviour
{
    [SerializeField] private XRHandMeshController _xrHandMeshControllerRight;
    [SerializeField] private XRHandMeshController _xrHandMeshControllerLeft;
    [SerializeField] private HandSelection _handSelection = HandSelection.RightOnly;

    private enum HandSelection
    {
        None,
        Both,
        LeftOnly,
        RightOnly,
    }

    private async UniTaskVoid Start()
    {
        var ct = this.GetCancellationTokenOnDestroy();
        var xrHandSubsystemUtility = XRHandSubsystemUtility.Instance;
        await UniTask.WaitUntil(() => xrHandSubsystemUtility.IsSubsystemRunning, cancellationToken: ct);

        var leftHandTracked = xrHandSubsystemUtility.IsLeftHandTracked;
        var rightHandTracked = xrHandSubsystemUtility.IsRightHandTracked;

        UpdateMeshControllerVisibility(Handedness.Left, leftHandTracked && ShouldDisplayHand(Handedness.Left));
        UpdateMeshControllerVisibility(Handedness.Right, rightHandTracked && ShouldDisplayHand(Handedness.Right));

        SubscribeHandSubsystem();
    }

    private void OnDestroy()
    {
        UnsubscribeHandSubsystem();
        UpdateMeshControllerVisibility(Handedness.Left, false);
        UpdateMeshControllerVisibility(Handedness.Right, false);
    }

    private void SubscribeHandSubsystem()
    {
        var xrHandSubsystemUtility = XRHandSubsystemUtility.Instance;
        if (!xrHandSubsystemUtility.IsSubsystemRunning) return;

        xrHandSubsystemUtility.Subsystem.trackingAcquired += OnTrackingAcquired;
        xrHandSubsystemUtility.Subsystem.trackingLost += OnTrackingLost;
        xrHandSubsystemUtility.Subsystem.updatedHands += OnUpdatedHands;
    }

    private void UnsubscribeHandSubsystem()
    {
        var xrHandSubsystemUtility = XRHandSubsystemUtility.Instance;
        if (!xrHandSubsystemUtility.IsSubsystemRunning) return;

        xrHandSubsystemUtility.Subsystem.trackingAcquired -= OnTrackingAcquired;
        xrHandSubsystemUtility.Subsystem.trackingLost -= OnTrackingLost;
        xrHandSubsystemUtility.Subsystem.updatedHands -= OnUpdatedHands;
    }

    private bool ShouldDisplayHand(Handedness handedness)
    {
        switch (_handSelection)
        {
            case HandSelection.Both:
                return true;
            case HandSelection.LeftOnly:
                return handedness == Handedness.Left;
            case HandSelection.RightOnly:
                return handedness == Handedness.Right;
            case HandSelection.None:
                return false;
            default:
                return true;
        }
    }

    private void UpdateMeshControllerVisibility(Handedness handedness, bool shouldDisplay)
    {
        XRHandMeshController meshController = null;

        switch (handedness)
        {
            case Handedness.Left:
                meshController = _xrHandMeshControllerLeft;
                break;
            case Handedness.Right:
                meshController = _xrHandMeshControllerRight;
                break;
        }

        if (meshController)
        {
            meshController.showMeshWhenTrackingIsAcquired = shouldDisplay;
            meshController.handMeshRenderer.enabled = shouldDisplay;
        }
    }

    private void OnTrackingAcquired(XRHand hand)
    {
        UpdateMeshControllerVisibility(hand.handedness, ShouldDisplayHand(hand.handedness));
    }

    private void OnTrackingLost(XRHand hand)
    {
        UpdateMeshControllerVisibility(hand.handedness, false);
    }

    private void OnUpdatedHands(
        XRHandSubsystem subsystem, 
        XRHandSubsystem.UpdateSuccessFlags updateSuccessFlags,
        XRHandSubsystem.UpdateType updateType)
    {
        if (updateType == XRHandSubsystem.UpdateType.Dynamic) return;

        var leftHandTracked = subsystem.leftHand.isTracked;
        var rightHandTracked = subsystem.rightHand.isTracked;

        UpdateMeshControllerVisibility(Handedness.Left, leftHandTracked && ShouldDisplayHand(Handedness.Left));
        UpdateMeshControllerVisibility(Handedness.Right, rightHandTracked && ShouldDisplayHand(Handedness.Right));
    }
}

以前作成したXRHandSubsystemUtilityを使っています。

参考リンク:【MetaQuest】XR Hands経由でボーン情報を扱う

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?