前回の続きです。コントローラー操作をメインで実装していきます。
シーン構成の一部変更
早速ですが、前回の記事でシーンの基本構成で"OVRCameraRig"を追加すると書いたな? あれは嘘だ!!
間違った情報を書いた訳ではないのですが、"OVRPlayerController"を追加すれば"OVRCameraRig"も入っているから、こっちを使った方が良さそうという話です。
OVRPlayerControllerがどうなっているかというと、"Character Controller"がアタッチされており、スクリプトでLinear Movement(平行移動)やRotation(回転)がON/OFFできるようになっています。
コントローラーの表示
"OVRPlayerController"を配置してもコントローラーはシーンに表示されません。
表示用のオブジェクトは、"Assets" > "Oculus" > "VR" > "Prefabs" > "OVRControllerPrefab"にあります。
これを"OVRPlayerController"の"LeftHandAnchor"、"RightHandAnchor"に配置します。
再度ビルドすると、コントローラーが表示されました!
・・・と言いたいところですが、よく見ると、
どっちも右だコレェ!!∑(゚Д゚)
私と同じミスを犯した方は、"LeftHandAnchor"に付けた方の"OVRControllerPrefab"でControllerを"L Tracked Remote"にすれば、ちゃんと左右のコントローラーになります。
コントローラー操作の実装
入力周りはOVRInputから取得できます。
準備
OVRInputから入力を受けるため方法として2パターン記述されています。
- "OVRManager"をシーンに配置する
OVRInput.Update()
をUpdate
で毎フレーム実行する
今回の構成では"OVRCameraRig"に"OVRManager"が付与されているため、既に準備は整っています。
新規のスクリプトで"OVRInputTest.cs"を作成、"OVRPlayerController"にアタッチして、このスクリプトに処理を記述していきます。
イベントの取得
イベントを取得するタイミングによって3つの関数が用意されています。
OVRInput.Get()
- ボタン/トリガー/スティックを押している間ずっと
OVRInput.GetDown()
- ボタン/トリガー/スティックを押したとき1度だけ
OVRInput.GetUp()
- ボタン/トリガー/スティックを離したとき1度だけ
上記の関数の第一引数でインプット(ボタン/トリガー/スティック)を指定します。インプットのマッピングはこちらに記述されています。
コード例は以下。
void Update() { if (OVRInput.GetDown(OVRInput.Button.One)) { // Aボタンが押された時の処理 } }
左右の判別
第二引数を省略またはOVRInput.Controller.Touch
を指定すると、左右のコントローラーが区別され、別々のボタンとして扱われます。
void Update() { if (OVRInput.GetDown(OVRInput.Button.One, OVRInput.Controller.Touch)) { // 右コントローラーのAボタンが押された時の処理 } if (OVRInput.GetDown(OVRInput.Button.Three, OVRInput.Controller.Touch)) { // 左コントローラーのXボタンが押された時の処理 } }
一方、第二引数にOVRInput.Controller.LTouch
、OVRInput.Controller.RTouch
を指定すると、処理を区別せずに左右のコントローラーのどちら有効にするかを指定できます。
void Update() { if (OVRInput.GetDown(OVRInput.Button.One, OVRInput.Controller.RTouch)) { // 右コントローラーのAボタンが押された時の処理 } if (OVRInput.GetDown(OVRInput.Button.One, OVRInput.Controller.LTouch)) { // 左コントローラーのXボタンが押された時の処理 } }
第一引数ではどちらもOVRInput.Button.One
を指定していますが、第二引数がOVRInput.Controller.RTouch
かOVRInput.Controller.LTouch
かによって、反応するボタンがAボタンからXボタンに変わります。
一見すると、前者の方がわかりやすいのですが、利き手によって左右を入れ替えるケースでは後者で実装しておいた方が便利です。
/** 利き手に持った方のコントローラー **/ public OVRInput.Controller dominantHandController; /** 利き手ではない手に持った方のコントローラー **/ public OVRInput.Controller otherHandController; void Update() { if (OVRInput.GetDown(OVRInput.Button.One, dominantHandController)) { // 利き手のコントローラーのボタン(AまたはX)が押された時の処理 } if (OVRInput.GetDown(OVRInput.Button.One, otherHandController)) { // 利き手ではないコントローラーのボタン(AまたはX)が押された時の処理 } }
このような実装にしておくと、Inspectorやスクリプトで左右を入れ替えやすくなります。
コントローラーの位置の取得
公式ではGetLocalControllerPosition()
で位置、GetLocalControllerRotation()
で向きを取得することができますが、LocalPositionを返すため、やや扱いにくいです。
そのため、"OVRInputTest.cs"の内容を以下に変更して、"OVRPlayerController"ではなく左右の"OVRControllerPrefab"にアタッチしました。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class OVRInputTest : MonoBehaviour { [SerializeField] private GameObject sphere; private OVRInput.Controller controller; void Start() { // OVRControllerHelperから左右どっちかを取得する controller = GetComponent<OVRControllerHelper>().m_controller; } void Update() { if (OVRInput.GetDown(OVRInput.Button.One, controller)) { Instantiate(sphere, transform.position, transform.rotation); } } }
"OVRControllerPrefab"に直接スクリプトをアタッチしているため、コントローラーの位置と向きはtransform.position
、transform.rotation
で取れます。
また、左右の判別で説明した方法を利用して、"OVRControllerPrefab"の"OVRControllerHelper"から自分が左右どちらのコントローラーなのかを取得しています。これにより同一のスクリプトを左右のコントローラーにアタッチすれば同じ動きをします。
左右の違いがわかるようにsphereには別のPrefabを設定できるようにしました。
コントローラの位置にSphereを置くとこまで#OculusQuest pic.twitter.com/WdO5rA4myJ
— jyuko (@jyuko49) 2019年7月14日
基本的には、この実装でいけるでしょう。
オブジェクトをつかむ
Colliderの追加
まず、当たり判定のためのColliderを付与します。
コントローラー側は"OVRControllerPrefab"に"Sphere Collider"を付けて、Is Trigger: true
、Radius: 0.1
にします。Triggerモードにすることでコントローラーのボタンを押すまではすり抜けるようになり、オブジェクトに触れることができます。
つかむ対象のオブジェクト側にもColliderを付与します。今回は"Capsule Collider"を使いました。Is Trigger
はどちらか一方で有効になっていればいいのでfalse
でもいいのですが、"OVRPlayerController"に付与されている"Character Controller"と干渉するので、ひとまずtrue
にしました。
次に"Rigid Body"を付与します。Is Trigger
を有効にしているので、Use Gravity
は無効にしています。そうしないと平面をすり抜けて落ちて行ってしまうので。
スクリプトの変更
"OVRControllerPrefab"に付与しているスクリプトの内容を以下に変更します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class OVRInputTest : MonoBehaviour { private OVRInput.Controller controller; private bool isTriggerDown = false; void Start() { controller = GetComponent<OVRControllerHelper>().m_controller; } void Update() { // 値が0でなければ、コントローラーのTriggerが押されている if (OVRInput.Get(OVRInput.Axis1D.PrimaryIndexTrigger, controller) > 0) { isTriggerDown = true; } else { isTriggerDown = false; } } /** 別のCollider(other)に触れている間実行 **/ void OnTriggerStay(Collider other) { // コントローラーのTriggerが押されており、対象がプレイヤー自身でない if (isTriggerDown && other.tag != "Player") { // コントローラーとつかんだオブジェクトのtransformを同期 other.gameObject.transform.position = transform.position; other.gameObject.transform.rotation = transform.rotation; } } }
処理の内容はコメントの通りです。コントローラーのTriggerが返す値はtrue
、false
ではなく、押されている強さのfloatになります。
また、コントローラーに付与した"Sphere Collider"と"OVRPlayerController"に付与されている"Character Controller"の干渉を避けるため、"OVRPlayerController"に"Player"タグを付け、スクリプト内の判定で除外しています。
簡易的ではありますが、これでコントローラーが触れたオブジェクトをつかむ処理が実装できます。
オブジェクトをつかむ。できました。
— jyuko (@jyuko49) 2019年7月14日
せっかくなので、@Query_chan をいろんな角度から眺める。#OculusQuest pic.twitter.com/Je8vnrFYCh
いつものようにクエリちゃんアセットを使っています。
ライセンス規約とダウンロードはこちらから。
まとめ
コントローラー操作の基本的なところは、公式のマニュアルで理解できました。
ただ、最後のオブジェクトをつかむ処理については、改良の余地が多々あります。
干渉を避けるためにRigid BodyのUse Gravity
を切ってしまっていますが、離した後に床に落ちたり、そのまま投げたりといった動きが自然なため、Use Gravity
やIs Trigger
をスクリプトで動的に切り替えた方がよさそうです。
また、つかんでいる間もコントローラーと完全に姿勢を同期させるより、つかんだ瞬間は元の姿勢を維持しつつ、コントローラーを動かしたら移動・回転する方が自然です。
こちらは初期状態の姿勢を内部的に保持することで実現できそうな感じはします。
上記は別の機会に試してみたいと思います。