5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Oculus Questのコントローラー制御を覚える

Posted at

はじめに

controller.png

Oculus Questを本格的に触りだして早1~2カ月経った。いまだにコントローラーの扱いが分からない。
サンプルはあるのだが、いまいちどこで何やってるか分からない。
そこで、備忘録も兼ねて今の時点で情報を取りまとめる。

Oculus Quest用の環境構築はUnity+Oculus Quest 開発メモと言う素晴らしいまとめがあるので、一礼して参照していただきたい。

尚、本記事の構成としては

  • OVRInputを使ってみる
  • OVRCameraRigを学ぶ
  • OVRPlayerControllerを使う
  • DistanceGrabを学ぶ

はじめてのOVRInput

おそらくサンプルコードから始めてすんなり受け入れられなかった方は、OVRInputが何かから始めたほうが良い。
OVRInputをざっくりいうと、Oculus Questのセンサー値を扱うためのAPI群だ。
サンプルを見てもさっぱりなので基本中の基本、OVRInputから始める。

OVRInput

使うだけなら単純で、下記2点を組み込めばいい。

  • シーンにOVRManagerを導入する
  • メインスクリプトにOVRInput.Update()とOVRInput.FixedUpdate()の呼び出しを記述

まずはここまでやってみる。

  • 新規シーンを作成
    • 名前: FirstOVRInput
  • MainCameraを削除し、OVRCameraRigを追加
    • Assets->Oculus->VR->Prefab->OVRCameraRig
  • 新規GameObjectを作成しスクリプトを追加
    • 名前: MainController
    • スクリプトの名前: FirstOVRInputMainController

こんな状態。

image.png

ここまで来たらいったん実行する。何もない空間で、コントローラーのアイコンすら出ない状態になっていれば成功だ。
前述のOVRManager導入とか、OVRInput.Update()の呼び出しはどうした?と、思われるかもしれない。
実は、OVRCameraRigの中でOVRManagerが導入されており、OVRManager内でOVRInput.Update()を呼び出している。
なので、OVRCameraRigを追加するだけでOVRInputを使う準備ができていると言うわけ。

コントローラーを表示する

存在するはずの手が存在しないと、何をどう操作してよいか全くわからない。と言うわけで、一旦コントローラーの位置にアイコンを出現させる。
もう少し具体的に書くと、OVRInputからコントローラーの位置を取得して、取得した位置にオブジェクトを配置するだ。
一気にコード書かなきゃ感が出てきたが、安心してほしい。OVRCameraRigは内部で、トラッキングスペース、目、コントローラーの情報をオブジェクトに割り当てる動作を行っている。
その証拠に、OVRCameraRigは下記の様な改装になっているはずだ。

image.png

オブジェクトへの割り当ては、下記ようなコードが組んである(抜粋)。OVRInputからコントローラーの位置情報を取得してHandAnchorの位置を更新しているのが分かると思う。

OVRCameraRig.cs
//Need this for controller offset because if we're on OpenVR, we want to set the local poses as specified by Unity, but if we're not, OVRInput local position is the right anchor
if (OVRManager.loadedXRDevice == OVRManager.XRDevice.OpenVR)
{
	Vector3 leftPos = Vector3.zero;
	Vector3 rightPos = Vector3.zero;
	Quaternion leftQuat = Quaternion.identity;
	Quaternion rightQuat = Quaternion.identity;

	if (OVRNodeStateProperties.GetNodeStatePropertyVector3(Node.LeftHand, NodeStatePropertyType.Position, OVRPlugin.Node.HandLeft, OVRPlugin.Step.Render, out leftPos))
		leftHandAnchor.localPosition = leftPos;
	if (OVRNodeStateProperties.GetNodeStatePropertyVector3(Node.RightHand, NodeStatePropertyType.Position, OVRPlugin.Node.HandRight, OVRPlugin.Step.Render, out rightPos))
		rightHandAnchor.localPosition = rightPos;
	if (OVRNodeStateProperties.GetNodeStatePropertyQuaternion(Node.LeftHand, NodeStatePropertyType.Orientation, OVRPlugin.Node.HandLeft, OVRPlugin.Step.Render, out leftQuat))
		leftHandAnchor.localRotation = leftQuat;
	if (OVRNodeStateProperties.GetNodeStatePropertyQuaternion(Node.RightHand, NodeStatePropertyType.Orientation, OVRPlugin.Node.HandRight, OVRPlugin.Step.Render, out rightQuat))
		rightHandAnchor.localRotation = rightQuat;

}
else
{
	leftHandAnchor.localPosition = OVRInput.GetLocalControllerPosition(OVRInput.Controller.LTouch);
	rightHandAnchor.localPosition = OVRInput.GetLocalControllerPosition(OVRInput.Controller.RTouch);
	leftHandAnchor.localRotation = OVRInput.GetLocalControllerRotation(OVRInput.Controller.LTouch);
	rightHandAnchor.localRotation = OVRInput.GetLocalControllerRotation(OVRInput.Controller.RTouch);
}

ここまでくれば話は早い。LeftControllerAnchor, RightControllerAnchorにそれっぽいPrefab(OVRControllerPrefab)をぶちこんであげればよい。ついでに床も追加しておく(影が確認できるから)。

image.png

この状態で実行するとコントローラーが表示され、実際のコントローラーに合わせて動くはずだ。

もう少しOVRCameraRigを追う

OVRCameraRigとコントローラーの関係が分かったところで、もう少しだけOVRCameraRigをお勉強しておく。
前項までで、OVRCameraRigはOVRInputをQuest的な感じに提供してくれるものという事は何となく理解していただけたと思う。だって、コントローラーのプレファブを追加するだけでコントローラの動きに合わせて表示してくれる。便利すぎる。便利だ。ただ、これだけでは次項で登場するOVRPlayerControllerの影が薄くなってしまう。今一度OVRCameraRigを見てみよう。

OVRCameraRigプレファブの階層

  • OVRCameraRig
    • TrackingSpace
      • LeftEyeAnchor
      • CenterEyeAnchor
      • RightEyeAnchor
      • TrackerAnchor
      • LeftHandAnchor
        • LeftControllerAnchor
      • RightHandAnchor
        • RightControllerAnchor

LeftControllerAnchor,RightControllerAnchorはすでに使ったので何となく理解できると思う。問題はほかのオブジェクトだ。

オブジェクト 概要
LeftEyeAnchor 左目のカメラ。
CenterEyeAnchor 中央のカメラ(謎)。OVRCameraRigのuse per eye camerasで無効になる。
RightEyeAnchor 右目のカメラ。
TrackerAnchor Quest自体の位置情報を取得するためのセンサー。
LeftHandAnchor 左手、左手コントローラー。
RightHandAnchor 右手、右手コントローラー。

カメラやセンサーは(俺は)Questでは扱わないので、Oculus Questアプリで意識すればよいのは、すでに出てきたコントローラーぐらいだ。
階層に持っているオブジェクトが分かったところで、軽くスクリプトも見ておこう。

Script 概要
OVR Camera Rig OVRInputをいい感じにオブジェクトと連動させる
OVR Manager OculusQuest自体の情報(CPU温度,オーディオ,手の認識など)を取得・設定する
OVR Headset Emulator キーボード,マウスでの移動をエミュレート

ここまで頭に入れておくと、コントローラーの位置を取得するとかで悩んだらOVRCameraRigのOVR Camera Rigスクリプトを追えばいいと言うのが何となく理解できたと思う。

ようやくOVRPlayerController

OVRCameraRigが卵焼きならOVRPlayerControllerは幕の内弁当だ(個人的な意見)。最初っからOVRPlayerControllerを追った場合、どこで何をやってるか非常に謎だ。
が、OVRCameraRigの動きを把握した今ならOVRPlayerControllerは怖くない。

  • シーンからOVRCameraRigを削除する
  • Assets->Oculus->VR->Prefab->OVRPlayerControllerを追加する
  • OVRPlayerController.OVRCameraRig.TrackingSpace.LeftHandAnchor.LeftControllerAnchorにOVRControllerPrefabを追加する
    • RightControllerAnchorも同様。
  • OVRPlayerControllerの前にオブジェクトを追加
  • 床を削除

ここまで来たらいったん実行してみる。

image.png

違いが分かっただろうか。おそらく、追加した立方体が上方にすっ飛ぶはずだ。正確にはプレイヤーが落ちて行ってる。
この動きで分かる通り、OVRPlayerControllerなるものは、Questにゲーム内のキャラクターとしての動きを提供する。歩いていて何かにぶつかったりするにはこのOVRPlayerControllerが必要と言うわけだ。
次のステップに進む前にOVRPlayerControllerが持っている情報を見ておく。

Script 概要
OVR Player Controller ジャンプや移動の加速度など、一般的にキャラクターが保持するであろう情報を提供する
OVR Scene Simple Controller ESCでゲーム中断などの一般的なゲームシーンのキー操作を提供する
OVR Debug Info SpaceキーでFPSなどのデバッグ情報を表示する

もうお腹いっぱいな位高機能。ゲーム内での移動などはOVRPlayerControllerのOVR Player Controllerスクリプトを追えばいいが何となく分かれば大丈夫。

Oculus.SampleFramework.Usage.DistanceGrabを理解する

OVRPlayerControllerが幕の内弁当なら、DistanceGrabは花見セットだ(個人的な意見)。
このDistanceGrab、コントローラーを使って何かしたいときのサンプルとして提供されている。
が、最初っからこれを追いかけるとドツボにはまる。ある程度中身知ってないとうまく流用できない。
原初にして最強。
この最難関のOculusサンプルを理解すればコントローラーの情報を扱えるようになったと言っても過言じゃないハズ。

前項までで下記の事が分かっている。

  • OVRInputでQuestの情報(コントローラーなど)を取得する
  • OVRCameraRigでコントローラーの情報をオブジェクトに反映してくれる
  • OVRPlayerControllerがキャラクタの情報を管理してくれる

DistanceGrubのシーンを開くと下記の様になっている。

image.png
ついでにちょこちょこ動かしてみて動きを確認しておく

もう、オブジェクトが多くて泣きそうになる。が、今までの情報を理解していればそこまで難しい事は無い。ただ一つ気づいていただきたいのは、前項までのようにOVRCameraRig配下に手のオブジェクトが無いという事だ。
ざっとオブジェクトの紹介をしておく。

object 概要
OVRPlayerController 前述。割愛。
Environment#Static 動かせないオブジェクト
Environment#Dynamic 動かせるオブジェクトと手(重要)
CanvasWithDebug デバッグ情報表示用
DistanceGrabberSampleScript オブジェクトを投げられるかどうかのデバッグ情報設定
Occluder 見えるけど掴めないオブジェクト

はい。これだけあって、OVRPlayerControllerを追わなくていい事に感謝だ。
Environment#Dynamic配下のオブジェクトを見てみよう。

オブジェクト 概要
WoodBlocks つかめるブロック達
Billiards つかめるビリヤードの玉達
DistanceGrabHandLeft 左手(重要)
DistanceGrabHandRight 右手(重要)
DistanceGrabOculusWaterBottlePf 水とか入れるボトル。つかめる。
DistanceGrabSoccerBallPf つかめるサッカーボール。
DistanceGrabCubeCrosshair つかめる十字線の入った箱。

DistanceGrabHandLeft(Right)とつかまれる側の何かしらを追ってしまえば勝ったも同然だ。

つかむ側

DistanceGrabHandLeftをじっくり見てゆこう。まずはオブジェクトの階層から。

image.png

いきなり難しい感じ出してくるよね

object description
DistanceGrabHandLeft 物つかんだりする制御。重要。
gripTrans 割愛。
GrabVolumeCone 割愛。
GrabVolumeBig 割愛。
GrabVolumeSmall 割愛。
l_hand_skeletal_lowres 手のメッシュを構成する。アニメーターあり
hands:hands_geom レンダラーの親オブジェクト
hands:Lhand 手の描画。メッシュレンダラー。
hands:l_hand_world 指などの各部分の親オブジェクト
hands:b_l_hand 指などの各部分

スクリプトはこんな感じ

object script description
DistanceGrabHandLeft Hand ThumsUpなどのアニメーションを制御する。そこまで重要じゃない。
DistanceGrabHandLeft DistanceGrabber OVRGrabberを継承している。実際にモノを掴んだりするスクリプト。重要。

DistanceGrabberのスクリプトを軽く追ってみると、実際に掴んだり動かしたりの記述がみられる。

DistanceGrabber
       protected override void GrabBegin()
        {
            DistanceGrabbable closestGrabbable = m_target;
            Collider closestGrabbableCollider = m_targetCollider;

            GrabVolumeEnable(false);

            if (closestGrabbable != null)
            {
                if (closestGrabbable.isGrabbed)
                {
                    ((DistanceGrabber)closestGrabbable.grabbedBy).OffhandGrabbed(closestGrabbable);
                }

                m_grabbedObj = closestGrabbable;
                m_grabbedObj.GrabBegin(this, closestGrabbableCollider);
                SetPlayerIgnoreCollision(m_grabbedObj.gameObject, true);

                m_movingObjectToHand = true;
                m_lastPos = transform.position;
                m_lastRot = transform.rotation;

                // If it's within a certain distance respect the no-snap.
                Vector3 closestPointOnBounds = closestGrabbableCollider.ClosestPointOnBounds(m_gripTransform.position);
                if(!m_grabbedObj.snapPosition && !m_grabbedObj.snapOrientation && m_noSnapThreshhold > 0.0f && (closestPointOnBounds - m_gripTransform.position).magnitude < m_noSnapThreshhold)
                {
                    Vector3 relPos = m_grabbedObj.transform.position - transform.position;
                    m_movingObjectToHand = false;
                    relPos = Quaternion.Inverse(transform.rotation) * relPos;
                    m_grabbedObjectPosOff = relPos;
                    Quaternion relOri = Quaternion.Inverse(transform.rotation) * m_grabbedObj.transform.rotation;
                    m_grabbedObjectRotOff = relOri;
                }
                else
                {
                    // Set up offsets for grabbed object desired position relative to hand.
                    m_grabbedObjectPosOff = m_gripTransform.localPosition;
                    if (m_grabbedObj.snapOffset)
                    {
                        Vector3 snapOffset = m_grabbedObj.snapOffset.position;
                        if (m_controller == OVRInput.Controller.LTouch) snapOffset.x = -snapOffset.x;
                        m_grabbedObjectPosOff += snapOffset;
                    }

                    m_grabbedObjectRotOff = m_gripTransform.localRotation;
                    if (m_grabbedObj.snapOffset)
                    {
                        m_grabbedObjectRotOff = m_grabbedObj.snapOffset.rotation * m_grabbedObjectRotOff;
                    }
                }

            }
        }

ここで一通りスクリプトを追ってみて分かると思うが、コントローラーの位置を反映する部分が見つからない。
なぜかと言うと、DistanceGrabberの継承元であるOVRGrabberで制御を行っているからだ。
インスペクションにあるParent Transformがそれ。ここでマスターすべきは下記2点。

  • ものをつかむ制御を行いたいなら、OVRGrabberを継承してクラスを作る。
    • Oculus.VR.Scripts.Util.OVRGrabber
  • コントローラーの延長線上にあるオブジェクトを探すならDistanceGrabberのスクリプトが参考になる

つかまれる側

ついでに掴まれる側も軽く追っておく。追うのはDistanceGrabOculusWaterBottlePf。
つかむ側を追った今、正直に言うとつかまれる側を追うのはちょろい。追うべきスクリプトはDistanceGrabbableのみだ。

DistanceGrabbableは100行にも満たないスクリプト。やっている事をざっくり言うと、つかめる状態になったり、つかまれた時に色をかえたりするだけだ。
処理の大部分はつかむ側が行っている。

覚えておくべきは

  • つかんで持ち歩けるオブジェクトを作りたいならOVRGrabbableを継承したスクリプトを追加する
    • Oculus.VR.Scripts.Util.OVRGrabbable

ふりかえり

OculusQuestを買って早1年、ようやっと本腰をいれてコンテンツを作ろうと思った。
作り始めて速攻で躓いた。コントローラーの位置すら取れなかった。
Oculusの公式サイトではOVRCameraRigを導入しましょう!と軽く書いてあるが、裏で何やってるかさっぱり分からない。

公式サンプルは下記特徴を持つので初心者の心が折れる

  • 多機能
  • 提供されているプレファブを拡張した形で作りこんである
  • シーンの構成を追っかける所から始まる

コントローラーを使ってやりたい事がある時に、どこを追えばいいかが分かってしまえば、とりあえずOVRGrabber付けたけどどうすればいい?みたいなことにはならないと思う。

5
4
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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?