MRが楽しい

MRやVRについて学習したことを書き残す

MetaHorizonの開発ドキュメントを読む その37(コントローラーインプットとトラッキングの概要)

本日はMetaQuestの学習枠です。
MetaHorizonの開発ドキュメントを読みながら実際に開発を行ってみました。

MetaHorizonの開発ドキュメント

MetaHorizonの開発ドキュメントを実際に手を動かしながら実行時のキャプチャをしていきます。
developers.meta.com

本記事は以下の「コントローラーインプットとトラッキングの概要」の記事を試します。
developers.meta.com

コントローラーインプットとトラッキングの概要

コントローラーは、Unity XRアプリケーションでユーザーが操作するための使い慣れたインターフェースを提供します。ボタン操作やジョイスティック操作などのユーザー入力を受け付け、ユーザーの手の動きをトラッキングします。

OVRInputはMeta Quest Touchコントローラー向けに設計されたUnityにおけるコントローラー入力とトラッキングのためのMetaの統合APIです。
以下のリアルタイムデータにアクセスするためのインターフェースを提供します。

  • コントローラーの状態、ボタン、サムスティック、トリガー、静電容量式タッチ
  • 手とコントローラーのポーズ

セットアップ

OVRInputを使用するにはUnityプロジェクトにMeta XR Interaction SDKをインストールする必要があります。
OVRManagerをシーン内の任意の場所に配置します。これによりデバイスの状態と入力の更新が管理されます。

入力またはゲームロジックを処理するスクリプトでは全てのUpdateメソッドとFixedUpdateメソッドの先頭に、それぞれフレームごとにOVRInput.Update()とOVRInput.FixedUpdate()を追加します。
例えば以下のようなコードです。

public class XRInputManager : MonoBehaviour {
    void Update() {
        OVRInput.Update();
        // Handle input logic here
    }
    void FixedUpdate() {
        OVRInput.FixedUpdate();
        // Handle physics-based input here
    }
}

OVRInputのUpdate関数を呼び出す際は以下のベストプラクティスに従ってください。

  • Update関数の呼び出しはメインの入力マネージャスクリプトに集約し、状態の一貫性を確保してください。
  • 入力状態に依存する他のスクリプトよりも先にUpdate関数を呼び出してください。
  • プロジェクトで複数のシーンを使用する場合は全てのシーンに入力マネージャが存在することを確認してください。

OVRInputを組み込むと、システムは入力バインディングを自動的に追加します。
これはコントローラーのアクションがUnityにどのようにマッピングされるかを定義するものです。
これらのバインディングはプロジェクトのInputManager.asset設定ファイルで参照できます。

仕組み

以下のセクションではOVRInputを使用してコントローラーとトラッキングデータを取得する方法について説明します。

入力クエリ

OVRInputはコントローラーの状態をクエリするためのメソッドを提供します。
これには以下が含まれます。

  • Get(): ボタン、軸、またはタッチセンサーの現在の状態を返します。
  • GetDown(): ボタンがこのフレームで押されたかどうかを返します。
  • GetUp(): ボタンがこのフレームで離されたかどうかを返します。

例えば、次のコードは現在のフレームで右コントローラーのトリガーが押されたかどうかを確認します。

if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger, OVRInput.Controller.RTouch)) {
    // Trigger pressed on right controller
}

コントローラーの姿勢追跡

OVRInputはヘッドセットと同じフレーム内で初期の目の中心姿勢を基準としたコントローラーの姿勢を報告します。
低遅延レンダリングのために、姿勢はヘッドセットと同期して予測されます。

コントローラーとヘッドセットの姿勢を現在の位置にリセットするにはOVRManager.display.RecenterPose()を使用します。
OVRInputは、以下の姿勢追跡関数を提供します。

  • GetLocalControllerPosition():位置をVector3で返します。
  • GetLocalControllerRotation():向きをQuaternionで返します。

コントローラーの識別

OVRInputではMeta Quest Touchコントローラーを以下のように識別します。

  • プライマリ:左コントローラー
  • セカンダリ:右コントローラー

コントロール入力の列挙型

OVRInputはGet()メソッドのさまざまなバリエーションを提供し、以下を含むさまざまなコントロールセットへのアクセスを可能にします。

コントロール 列挙型
OVRInput.Button ゲームパッド、コントローラー、およびバックボタンにある従来のボタン。
OVRInput.Touch コントローラーにある静電容量式タッチセンサー。
OVRInput.NearTouch コントローラーにある近接センサー。
OVRInput.Axis1D トリガーなど、浮動小数点状態を報告する1次元コントロール。
OVRInput.Axis2D サムスティックなど、2次元コントロール。Vector2状態を報告する。

2つ目の列挙型セットは最初の列挙型セットを反映しており、以下のように定義されます。

  • OVRInput.RawButton
  • OVRInput.RawTouch
  • OVRInput.RawNearTouch
  • OVRInput.RawAxis1D
  • OVRInput.RawAxis2D

最初の列挙型セットは開発者がさまざまな種類のコントローラ間で動作する制御スキームを作成できるようにする仮想化された入力マッピングを提供します。
2つ目の列挙型セットはコントローラの基となる状態への未変更の生データアクセスを提供します。

ボタン、タッチ、ニアタッチ

従来のゲームパッドボタンに加えてコントローラーには静電容量式の操作面が搭載されており、ユーザーの指や親指が操作面に接触した時(タッチ)および近距離にある時(ニアタッチ)を検知します。
これにより、特定の操作面に対するユーザーの操作状態を複数検出することが可能になります。

例えばユーザーの人差し指が操作面から完全に離れると、その操作面のニアタッチはfalseと表示されます。
指が操作面に近づき近接すると、ユーザーが実際に接触する前にニアタッチはtrueと表示されます。
ユーザーが実際に接触すると、その操作面のタッチはtrueと表示されます。
ユーザーが人差し指のトリガーを押すと、その操作面のボタンはtrueと表示されます。

これらの状態を区別することで、コントローラーに対するユーザーの操作を正確に検知して多様な操作方法を実現できます。

使用例

以下の例はボタン、サムスティック、トリガー、静電容量式タッチセンサーなど様々な入力タイプを照会する方法を示しています。

// プライマリボタン(通常は「A」ボタン)が現在押されている場合は true を返します。
OVRInput.Get(OVRInput.Button.One);

// プライマリボタン(通常は「A」ボタン)がこのフレームで押されていた場合は true を返します
OVRInput.GetDown(OVRInput.Button.One);

// 「X」ボタンがこのフレームで離されていた場合は true を返します。
OVRInput.GetUp(OVRInput.RawButton.X);

// プライマリサムスティック(通常は左サムスティック)の現在の状態を表す Vector2 を返します。
// (X/Y の範囲は -1.0f ~ 1.0f)
OVRInput.Get(OVRInput.Axis2D.PrimaryThumbstick);

// プライマリサムスティックが現在押されている(ボタンとしてクリックされている)場合は true を返します。
OVRInput.Get(OVRInput.Button.PrimaryThumbstick);

// プライマリサムスティックが半分以上上に動かされている場合は true を返します。
// (上/下/左/右 - サムスティックを方向キーとして解釈します)
OVRInput.Get(OVRInput.Button.PrimaryThumbstickUp);

// セカンダリ(通常は右)人差し指トリガーの現在の状態を float 型で返します。
// (0.0f ~ 1.0f の範囲)
OVRInput.Get(OVRInput.Axis1D.SecondaryIndexTrigger);

// 左人差し指トリガーの現在の状態を float 型で返します。
// (0.0f ~ 1.0f の範囲)
OVRInput.Get(OVRInput.RawAxis1D.LIndexTrigger);

// 左人差し指トリガーが半分以上押されている場合は true を返します。
// (トリガーをボタンとして解釈します)
OVRInput.Get(OVRInput.RawButton.LIndexTrigger);

// セカンダリゲームパッドボタン(通常は「B」ボタン)がユーザーによって現在タッチされている場合は true を返します。
OVRInput.Get(OVRInput.Touch.Two);

Get()メソッドはコントロールを指定するだけでなく、オプションでコントローラーパラメータも受け取ります。
特定のコントロール方式を特定のコントローラータイプのみで使用する場合はコントローラーを指定できます。

Get()にコントローラーパラメータが指定されていない場合、デフォルトではアクティブコントローラーが使用されます。
アクティブコントローラーとは最後にユーザー入力を報告したコントローラーのことです。

例えばユーザーがコントローラーを2つ使用し、それらを置いてからXboxコントローラーを手に取った場合、ユーザーがXboxコントローラーで入力を行うとアクティブコントローラーはXboxコントローラーに切り替わります。
現在のアクティブコントローラーはOVRInput.GetActiveController()で、接続されているすべてのコントローラーのビットマスクはOVRInput.GetConnectedControllers()で取得できます。

次の例は左右のトリガーを照会するために、コントローラーのペアを明示的に指定する方法を示しています。

// 左コントローラー上のハンドトリガーの現在の状態を表す float 型の値を返します。
OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, OVRInput.Controller.Touch);

// 右コントローラー上のハンドトリガーの現在の状態を表す float 型の値を返します。
OVRInput.Get(OVRInput.Axis1D.SecondaryHandTrigger, OVRInput.Controller.Touch);

Meta Quest Touchコントローラーは「ペアとしてOVRInput.Controller.Touchを使用」、または「個別にOVRInput.Controller.LTouchとRTouchを使用」を指定できます。
これはLTouchまたはRTouchを指定すると、異なる仮想入力マッピングセットが使用されるため、利き手に依存しない入力コードをより容易に開発できるため重要です。

次の例はPrimaryHandTriggerを個別のコントローラーで使用することで、利き手に依存しない入力を実現する方法を示しています。
この場合、Primaryは常に指定された手にマッピングされます。

// 左コントローラー上のハンドトリガーの現在の状態を表す float 型の値を返します。
OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, OVRInput.Controller.LTouch);

// 右コントローラー上のハンドトリガーの現在の状態を表す float 型の値を返します。
OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, OVRInput.Controller.RTouch);

このアプローチは外部から設定される変数(例えばパブリック変数)にコントローラを指定することで、同じコードを左右どちらの手でも動作するように拡張できます。
以下の例はコントローラを構成可能な変数として公開することで、再利用可能な入力コードを記述する方法を示しています。

// UnityインスペクターでLTouchまたはRTouchに設定できるパブリック変数
public OVRInput.Controller controller;

// controller変数で指定されたコントローラー上のハンドトリガーの現在の状態を表す浮動小数点数を返します。
OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, controller);

// controller変数で指定されたコントローラーのプライマリボタン(「A」または「X」)が押された場合にtrueを返します。
OVRInput.Get(OVRInput.Button.One, controller);

これにより、左右どちらの手でも入力が対応しているかを確認するためのif/else文による一般的なチェックパターンを回避できます。