開発
開発
プラットフォームを選択

チュートリアル - ジェスチャーコントロールから基本的な入力を受け取る

更新日時: 2024/08/20
このチュートリアルでは、以下の重要なステップについて説明します。
  1. OVRCameraRigをUnityプロジェクトに追加する。
  2. プロジェクトの中でOVRHand prefabとOVRSkeleton prefabを使う。
  3. ユーザーがピンチ操作のハンドジェスチャーをしているかどうかをチェックする。
  4. ユーザーの手の骨(左手人差し指の指先)に関する位置と回転のデータを受け取る。
  5. LineRendererとして曲線(二次ベジェ曲線)を描く。
  6. その曲線をユーザーの人差し指の指先に関連付けて、オブジェクトとのインタラクションを可能にする。
  7. 物理カプセル機能を手に適用して、GameObjectとの衝突検出を可能にする。
App running
Meta Quest 2で実行するアプリ
このチュートリアルは、Meta XRオールインワンSDKを使ってジェスチャーコントロールを素早く行うための主なリファレンスです。ジェスチャーコントロール機能のドキュメント全体については、ジェスチャーコントロールを設定するをご覧ください。コントローラーインタラクションとハンドインタラクションをアプリに追加するライブラリ全体については、Interaction SDKの概要をご覧ください。
QuestでのUnity開発が初めての方は、最初のVRアプリを作成するためのHello Worldガイドをご覧ください。

前提条件

このチュートリアルを進める前に、Meta Quest VRヘッドセットのUnity Hello Worldに記載された設定ステップを完了し、必要な依存関係を含めてプロジェクトを作成します。これには、Meta Questヘッドセット上で実行できるようにすることが含まれます。このチュートリアルはそのようなプロジェクトに基づき構成されています。

ジェスチャーコントロールの基本

ジェスチャーコントロールは、さまざまな手のポーズを解析し、手首や指関節や指先などの手の特定のキーポイントの位置と回転をトラッキングします。指をさす、つまむ、放す、スクロールする、手のひら全体でつまむなど、単純な手のジェスチャーにより、手でオブジェクトの操作を実行することができます。衝突検知を有効にするため、ジェスチャーコントロールには物理カプセルのような機能も提供されています。
これらはすべてOVRHand、OVRSkeleton、OVRBoneの各クラスの下で利用可能です。それらにより、Meta XR Core SDKでのジェスチャーコントロールのための統一されたインプットシステムが提供されます。詳しくは、OVRHandOVRSkeletonOVRBoneの各クラスのリファレンスをご覧ください。

ピンチ操作についての洞察と設計上の注意事項

ピンチ操作は、直接触れる感覚をもたらすシンプルかつユニークなハンドジェスチャーです。現実世界でユーザーが人差し指で触る相手は親指であり、つまみながら実際に何かを感じています。インタラクションセットが適切に用意されているなら、ピンチ操作のこのユニークな特徴は、リアル感錯覚を実現する上で役立つことがあります。それは仮想世界を物理世界に溶け込ませる必要のあるMRエクスペリエンスにおいて重要です。これは、アプリによって描写されているシナリオが実際に起こっているという錯覚です。
このアプリでは、青いリボン(LineRenderer)がユーザーのピンチポイントを立方体GameObjectに接続します。左手と立方体の間に直接の接触はありません。立方体と衝突することがあるのは右手だけです。
Pinch gesture
仮想世界において立方体は手より小さいオブジェクトに見えるため、ユーザーはそれを「軽量」と安易に認識し、大して「引きずる」こともなく動かせると勘違いすることがあります。
薄いリボンは、立方体をユーザーの直接ピンチ操作から切り離し、離れた場所からそれを動かす手段となります。これにより、手で立方体を運ぶというインタラクションから、手でリボンを運ぶというインタラクションへと、意図的に転換しています。物理世界においてリボンは、ほとんど重さを感じない非常に軽量な物体と認識されるため、このインタラクションでは、離れた場所から軽量オブジェクトに接続することができ、さらにはそれを運ぶことも可能になります。さらに曲線LineRendererにより、金属製円筒型パイプで持ち上げる場合などと比べて、軽量リボンを保持しているという錯覚がずっと強くなります。
注: このチュートリアルでは、Unityエディターバージョン2021.3.20f1とMeta XRオールインワンSDK v59を使っています。他のバージョンを使っている場合スクリーンショットは異なる場合がありますが、機能はほぼ同じです。

ステップ1. OVRCameraRigをシーンに追加する

まだプロジェクトにOVRCameraRigを追加していない場合は、次の手順に従ってください。
Meta XR Core SDKには、OVRCameraRig prefabが含まれています。これは、Unityのデフォルトのメインカメラに代わる機能となります。
以下の手順に従って、OVRCameraRigをシーンに追加します。
  1. プロジェクトの[Hierarchy (階層)]で、[Main Camera (メインカメラ)]を右クリックし、[Delete (削除)]を選択します。
  2. [Project (プロジェクト)]タブで[All Prefabs (すべてのprefab)]を選択し、OVRCameraRigを検索し、OVRCameraRig prefabをプロジェクトの[Hierarchy (階層)]にドラッグします。
  3. [Hierarchy (階層)]の中の[OVRCameraRig]を選択します。
  4. [Inspector (インスペクター)]ウィンドウの[OVR Manager (OVRマネージャ)]コンポーネントの下で、[Target Devices (ターゲットデバイス)]から該当するヘッドセットを選択します。

ステップ2. ジェスチャーコントロールが有効になるようにシーンを設定する

  1. [Hierarchy (階層)]タブで、[OVRCameraRig]を選択します。
  2. [Inspector (インスペクター)]タブで、[OVR Manager (OVRマネージャ)] > [Tracking (トラッキング)]に移動します。
  3. [Tracking (トラッキング)]セクションで、[Tracking Origin Type (トラッキング原点タイプ)]として[Eye Level (アイレベル)]を選択します。
  4. [Use Position Tracking (位置トラッキングを使う)]が選択されていることを確認します。
    OVR Manager Tracking section with Eye Level origin type and Use Position Tracking enabled.
  5. [Hand Tracking Support (ジェスチャーコントロールのサポート)]のリストで、[Controllers and Hands (コントローラーと手)]または[Hands Only (手だけ)] (このチュートリアルではコントローラーを使いません)を選択します。
  6. [Hand Tracking Frequency (ジェスチャーコントロール頻度)]のリストから、[MAX (最大)]または[HIGH (高)]を選択します。
  7. [Hand Tracking Version (ジェスチャーコントロールのバージョン)]として[V2]を選択します。
    Hand tracking support

ステップ3. 手のprefabを追加する

  1. [Hierarchy (階層)]タブで[OVRCameraRig] > [TrackingSpace]を展開し、左手と右手のアンカーの下に手のprefabを追加します。
    Hierarchy 1
  2. [Project (プロジェクト)]タブで、OVRHand Prefabを探します。
  3. OVRHand PrefabのコピーをAssetsフォルダーにドラッグします。
    OVR Hand Prefab
  4. Assetsフォルダーからprefabをドラッグし、[Hierarchy (階層)]タブのそれぞれの手のアンカーに配置します。これを、それぞれの手について1回ずつ、合計2回実施します。
    Hierarchy 2
  5. [Hierarchy (階層)]タブで、RightHandAnchorの下からOVRHandPrefabを選択した後、[Inspector (インスペクター)]タブでその名前をOVRHandPrefabRightに変更します。
  6. [OVR Skeleton (OVR骨組み)]の下で、[Update Root Scale (ルートスケールを更新)][Enable Physics Capsules (物理カプセルを有効にする)][Apply Bone Translations (骨格変換を適用)]にチェックマークを付けてオンにします。
    Right hand prefab
  7. 同じようにして、左手のprefabの設定が以下のようになっていることを確認します。(左手のオプションは事前選択されており、このチュートリアルの場合、左手の物理カプセルを有効にする必要はありません。)
    Left hand prefab
  8. [Hierarchy (階層)]タブで、まず左手のOVRHand prefabを選択してから、[Inspector (インスペクター)]タブで、[OVR Skeleton (OVR骨組み)][OVR Mesh (OVRメッシュ)][OVR Mesh Renderer (OVRメッシュレンダラー)]の各チェックボックスがオンになっていることを確認します。右手のOVRHand prefabについても同じことをします。

ステップ4. Cube GameObjectを設定する

  1. [Hierarchy (階層)]タブの下からCube GameObjectを選択します。
  2. そのスケールを[0.02, 0.02, 0.02]に変更します。
    Cube scale
  3. [Add Component (コンポーネントを追加)]をクリックし、Rigidbodyを見つけてそれを選択します。これにより右手の衝突検出が可能になります。
  4. Rigidbodyコンポーネントの[Use Gravity (重力を使用)]チェックボックスを無効にします。
  5. [Add Component (コンポーネントを追加)]をクリックし、LineRendererを見つけてそれを選択します。これにより、立方体と左手人差し指の指先をつなぐリボンを表す線が作成されます。
  6. [Line Renderer (線レンダラー)]コンポーネントで以下の設定を適用し、リボンの幅を更新します(0.03 m以下、0.003 mまでの値で、小さいほど望ましいです)。影を落として光データを生成することがないようにし、プロジェクトに既存のマテリアルを追加します。
    Line Renderer

ステップ5. ジェスチャーコントロール管理用の新しいスクリプトを追加する

  1. [Project (プロジェクト)]タブの下で、[Assets (アセット)]フォルダに移動します。
  2. 右クリックして、[Create (作成)] > [Folder (フォルダ)]を選択し、名前をScriptsにしてから、この新しいフォルダを開きます。
  3. 右クリックして、[Create (作成)] > [C# Script (C#スクリプト)]を選択し、名前をHandTrackingScriptにします。
  4. 新規作成したスクリプトを、[Hierarchy (階層)]タブのCube GameObjectまでドラッグします。
  5. [Hierarchy (階層)]タブで、そのCube GameObjectを選択します。
  6. [Inspector (インスペクター)]で、HandTrackingScript.csをダブルクリックし、普段使っているIDEでそれを開きます。

ステップ6. HandTrackingScript.csを実装する

このスクリプトは、手のインタラクションを管理します。

変数とオブジェクトを追加する

HandTrackingScriptクラスに、以下を追加します。
    public Camera sceneCamera;
    public OVRHand leftHand;
    public OVRHand rightHand;
    public OVRSkeleton skeleton;

    private Vector3 targetPosition;
    private Quaternion targetRotation;
    private float step;
    private bool isIndexFingerPinching;

    private LineRenderer line;
    private Transform p0;
    private Transform p1;
    private Transform p2;

    private Transform handIndexTipTransform;
これらが表す内容は次のとおりです。
変数説明
sceneCamera
シーンで使うカメラ
leftHandrightHand
左手と右手のprefab
skeleton
骨組み(骨の位置と回転のデータの取得用)
targetPositiontargetRotation
リボンに取り付けた状態の立方体のアニメーションに使う位置と回転
step
アニメーション付きヘルプ(Time.deltaTime)
line
リボンを表すLineRenderer
p0p1p2
リボンLineRendererの描画のための始点、屈曲点、終点の変形
handIndexTipTransform
左手人差し指の指先の変形

Start()で立方体の初期位置をユーザーの正面に設定する

Start()関数の中で、立方体の初期位置を定義し、LineRendererコンポーネントをlineに割り当てます。
    void Start()
    {
        transform.position = sceneCamera.transform.position + sceneCamera.transform.forward * 1.0f;
        line = GetComponent<LineRenderer>();
    }
これにより立方体GameObjectが、ユーザーの正面、距離1メートルの位置に初期配置されます。

立方体をスムーズに配置して回転させるヘルパー関数を作成する

このヘルパー関数は、立方体の位置と向きの変更アニメーションを処理します。新しいpinchCube()関数を作成し、以下の行を追加します。
    void pinchCube()
    {
        targetPosition = leftHand.transform.position - leftHand.transform.forward * 0.4f;
        targetRotation = Quaternion.LookRotation(transform.position - leftHand.transform.position);

        transform.position = Vector3.Lerp(transform.position, targetPosition, step);
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, step);
    }
このコードは、ユーザーの左手の横、距離約0.4メートルの位置に、立方体の位置と向きをスムーズに設定します。手の実際の位置は、その手首にあります。詳しくは、Quaternion.LookRotationVector3.LerpQuaternion.Slerpに関するUnityのドキュメントをご覧ください。

リボン描画用ヘルパー関数を作成する

この関数は、LineRendererリボンを、200セグメントからなる二次ベジエ曲線として描画します。二次ベジエ曲線は、P0(始点)、P1(中間点)、P2(終点)の3点を通る関数B(t)として経路を描きます。
その式は、B(t) = (1 - t)^2 * P0 + 2 * (1 - t) * t * P1 + t^2 * P2です。
ここで、
  • BP0P1P2Vector3であり、それぞれ位置を表します。
  • tは線の全長に対する割合を表し、0以上1以下の値です。
例えば、t = 0.5なら、B(t)は点P0から点P2までの(P1を通る)曲線の途中であり、線の真ん中までが描画されます。
この方法を使うことによって、まずB(0)を計算した後、セグメントごとに反復して段階的に描画していくことができます。
HandTrackingScriptクラスに以下を追加します。
    void DrawCurve(Vector3 point_0, Vector3 point_1, Vector3 point_2)
    {
        line.positionCount = 200;
        Vector3 B = new Vector3(0, 0, 0);
        float t = 0f;

        for (int i = 0; i < line.positionCount; i++)
        {
            t += 0.005f;
            B = (1 - t) * (1 - t) * point_0 + 2 * (1 - t) * t * point_1 + t * t * point_2;
            line.SetPosition(i, B);
        }
    }
DrawCurve()関数では、線の始点(point_0)、屈曲点(point_1)、終点(point_2)の位置情報Vector3が必須パラメーターです。200セグメントのすべてがそれぞれレンダリングされるまでループ処理します。

左手がトラッキングされていて、ユーザーがピンチ操作していることを確認する

Update()関数に、以下を追加します。
    void Update()
    {
        step = 5.0f * Time.deltaTime;

        if (leftHand.IsTracked)
        {
            isIndexFingerPinching = leftHand.GetFingerIsPinching(OVRHand.HandFinger.Index);
            if (isIndexFingerPinching)
            {
                line.enabled = true;
                // Animate cube smoothly next to left hand
                pinchCube();

                // ...

            }
            else
            {
                line.enabled = false;
            }
        }
    }
このスニペットでは、次のことを実行します。
  1. 立方体アニメーションのためのステップ値を定義します。詳しくは、Time.deltaTimeに関するUnityのドキュメントをご覧ください。
  2. leftHand OVRHandオブジェクトがisTracked()関数によってトラッキングされているかどうかを調べます。トラッキングされている場合、trueが返されます。
  3. ユーザーが人差し指を使ってピンチ操作をしていることを確認するためにGetFingerIsPinching(OVRHand.HandFinger.Index)関数を呼び出し、その戻り値をboolean変数isIndexFingerPinchingに代入します。この関数の定義は、bool OVRHand.GetFingerIsPinching (HandFinger finger)です。HandFinger列挙で定義されている残りの指について詳しくは、OVRHandをご覧ください。
  4. ユーザーがピンチ操作をしているならline LineRendererを有効にし、ユーザーに対して表示されるようにします。ピンチ操作をしていない場合は、無効にします。
  5. pinchCube()ヘルパー関数を呼び出して、立方体の位置と向きを設定します(ユーザーがピンチ操作をしている場合)。

左手骨組みの骨格に関する変形データを取得する

Update()関数を以下のように修正します。(pinchCube()呼び出しの下に新たな行が複数挿入されていることに注意。)
    void Update()
    {
        step = 5.0f * Time.deltaTime;

        if (leftHand.IsTracked)
        {
            isIndexFingerPinching = leftHand.GetFingerIsPinching(OVRHand.HandFinger.Index);

            if (isIndexFingerPinching)
            {
                line.enabled = true;

                pinchCube();

                // New lines added below this point
                foreach (var b in skeleton.Bones)
                {
                    if (b.Id == OVRSkeleton.BoneId.Hand_IndexTip)
                    {
                        handIndexTipTransform = b.Transform;
                        break;
                    }
                }

                p0 = transform;
                p2 = handIndexTipTransform;
                p1 = sceneCamera.transform;
                p1.position += sceneCamera.transform.forward * 0.8f;

                DrawCurve(p0.position, p1.position, p2.position);
               // New lines added above this point
            }
            else
            {
                line.enabled = false;
            }
        }
    }
追加されたコードは、次のことを実行します。
  1. OVRSkeletonオブジェクトの骨すべてについて反復処理します。
    これは、左手の骨組みを表します。
    注:skeleton.Bonesは、骨のすべてのIDを保管するOVRBoneリストです。骨IDすべての定義については、OVRSkeletonの中のenum OVRSkeleton.BoneIdをご覧ください。
  2. OVRSkeleton.BoneId.Hand_IndexTip骨IDが検出されるかどうかを調べます。これは、左手人差し指の指先を表します。
  3. 見つかったら、骨の変形データを変数handIndexTipTransformに保管します。
  4. 立方体の変形をp0に、また左手人差し指の指先の変形をp2に保管します。
  5. ユーザーのヘッドポーズ正面の位置に関する変形を、p1に代入します。これが、LineRendererを曲げる点となります。
  6. DrawCurve()ヘルパー関数を呼び出して、リボンLineRendererを描画します。
注: このチュートリアルをマスターしたなら、p1の定義に関する実験をしてみるようおすすめします。それに、手や立方体に関連したさまざまな異なる値を代入してみてください。

ステップ7. Cube GameObjectを更新してアプリを実行する

  1. Unityエディターを開き、[Hierarchy (階層)]タブでCube GameObjectを選択します。
  2. [Inspector (インスペクター)]の下で、CenterEyeAnchorのカメラを選択します。これは、左目と右目のポーズの平均と常に一致します。
  3. 残りのprefabを次のように選択します。
    Script component settings
  4. プロジェクトを保存して、[File (ファイル)] > [Build And Run (ビルドして実行)]をクリックします。
  5. ヘッドセットを装着します。
  6. ヘッドセットで、[Settings (設定)] > [Movement tracking (動作トラッキング)] > [Hand tracking (ジェスチャーコントロール)]に移動して、[Hand and body tracking (ジェスチャーコントロールとボディトラッキング)]オプションをオンにしてください。
アプリが起動したら、頭を動かしたり、手を伸ばしたり、手を動かしたりして、ジェスチャーコントロールを有効にしてみてください。手のレンダリングが表示されたら、左手で立方体をつまんで(ピンチ操作)、リボンで立方体を運んでください。右手のひらで立方体にそっと触れて、軽く押します。

参考スクリプト

今後の参考のため、以下にHandTrackingScript.csのコード全体を掲載します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandTrackingScript : MonoBehaviour
{
    public Camera sceneCamera;
    public OVRHand leftHand;
    public OVRHand rightHand;
    public OVRSkeleton skeleton;

    private Vector3 targetPosition;
    private Quaternion targetRotation;
    private float step;
    private bool isIndexFingerPinching;

    private LineRenderer line;
    private Transform p0;
    private Transform p1;
    private Transform p2;

    private Transform handIndexTipTransform;

    // Start is called before the first frame update
    void Start()
    {

        // Set initial cube's position in front of user
        transform.position = sceneCamera.transform.position + sceneCamera.transform.forward * 1.0f;

        // Assign the LineRenderer component of the cube GameObject to line
        line = GetComponent<LineRenderer>();
    }

    // Update is called once per frame
    void Update()
    {
        // Define step value for animation
        step = 5.0f * Time.deltaTime;

        // If left hand is tracked
        if (leftHand.IsTracked)
        {
            // Gather info whether left hand is pinching
            isIndexFingerPinching = leftHand.GetFingerIsPinching(OVRHand.HandFinger.Index);

            // Proceed only if left hand is pinching
            if (isIndexFingerPinching)
            {
                // Show the Line Renderer
                line.enabled = true;

                // Animate cube smoothly next to left hand
                pinchCube();

                // Loop through all the bones in the skeleton
                foreach (var b in skeleton.Bones)
                {
                    // If bone is the hand index tip
                    if (b.Id == OVRSkeleton.BoneId.Hand_IndexTip)
                    {
                        // Store its transform and break the loop
                        handIndexTipTransform = b.Transform;
                        break;
                    }
                }

                // p0 is the cube's transform and p2 the left hand's index tip transform
                // These are the two edges of the line connecting the cube to the left hand index tip
                p0 = transform;
                p2 = handIndexTipTransform;

                // This is a somewhat random point between the cube and the index tip
                // Need to reference as the point that "bends" the curve
                p1 = sceneCamera.transform;
                p1.position += sceneCamera.transform.forward * 0.8f;

                // Draw the line that connects the cube to the user's left index tip and bend it at p1
                DrawCurve(p0.position, p1.position, p2.position);
            }
            // If the user is not pinching
            else
            {
                // Don't display the line at all
                line.enabled = false;
            }
        }

    }

    void DrawCurve(Vector3 point_0, Vector3 point_1, Vector3 point_2)
    /***********************************************************************************
    # Helper function that draws a curve between point_0 and point_2, bending at point_1.
    # Gradually draws a line as Quadratic Bézier Curve that consists of 200 segments.
    #
    # Bézier curve draws a path as function B(t), given three points P0, P1, and P2.
    # B, P0, P1, P2 are all Vector3 and represent positions.
    #
    # B = (1 - t)^2 * P0 + 2 * (1-t) * t * P1 + t^2 * P2
    #
    # t is 0 <= t <= 1 representing size / portion of line when moving to the next segment.
    # For example, if t = 0.5f, B(t) is halfway from point P0 to P2.
    ***********************************************************************************/
    {
        // Set the number of segments to 200
        line.positionCount = 200;
        Vector3 B = new Vector3(0, 0, 0);
        float t = 0f;

        // Draw segments
        for (int i = 0; i < line.positionCount; i++)
        {
            // Move to next segment
            t += 0.005f;

            B = (1 - t) * (1 - t) * point_0 + 2 * (1 - t) * t * point_1 + t * t * point_2;
            line.SetPosition(i, B);
        }
    }

    void pinchCube()
    // Places and rotates cube smoothly next to user's left hand
    {
        targetPosition = leftHand.transform.position - leftHand.transform.forward * 0.4f;
        targetRotation = Quaternion.LookRotation(transform.position - leftHand.transform.position);

        transform.position = Vector3.Lerp(transform.position, targetPosition, step);
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, step);
    }
}
ナビゲーションロゴ
日本語
© 2026 Meta