アドカレ
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 |
事前準備は以下の通りです。
- 【Meta Quest】しばらく触ってないうちにMeta XR SDKがOpenXRベースになっていたので改めて整理
- 【Meta Quest】XR Interaction Toolkitを導入する
- 【Meta Quest】パススルー設定方法
デモ
GIFのように左手はコントローラー、右手はハンドトラッキングといった使い分けを想定したアプリを作る方法をメモします。
マルチモーダル
前回記事で取り上げたマルチモーダルが前提となります。
参考リンク:【Meta Quest】コントローラーとハンドトラッキングをシームレスに切り替える
そのうえで、必要なGameObject以外はXROrigin配下から削除します。今回の例では、LeftHandとRightControllerを削除しています。

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