じゅころぐAR

ARのブログ

UnityでOculus Questのコントローラー操作を実装する

前回の続きです。コントローラー操作をメインで実装していきます。

f:id:jyuko49:20190714154510j:plain

シーン構成の一部変更

早速ですが、前回の記事でシーンの基本構成で"OVRCameraRig"を追加すると書いたな? あれは嘘だ!!

間違った情報を書いた訳ではないのですが、"OVRPlayerController"を追加すれば"OVRCameraRig"も入っているから、こっちを使った方が良さそうという話です。

f:id:jyuko49:20190710003652p:plain

OVRPlayerControllerがどうなっているかというと、"Character Controller"がアタッチされており、スクリプトでLinear Movement(平行移動)Rotation(回転)がON/OFFできるようになっています。

f:id:jyuko49:20190710010334p:plain

コントローラーの表示

"OVRPlayerController"を配置してもコントローラーはシーンに表示されません。

表示用のオブジェクトは、"Assets" > "Oculus" > "VR" > "Prefabs" > "OVRControllerPrefab"にあります。

f:id:jyuko49:20190713173537p:plain

これを"OVRPlayerController"の"LeftHandAnchor""RightHandAnchor"に配置します。

f:id:jyuko49:20190710004923p:plain

再度ビルドすると、コントローラーが表示されました!
・・・と言いたいところですが、よく見ると、

f:id:jyuko49:20190713092441j:plain

どっちも右だコレェ!!∑(゚Д゚)

私と同じミスを犯した方は、"LeftHandAnchor"に付けた方の"OVRControllerPrefab"でControllerを"L Tracked Remote"にすれば、ちゃんと左右のコントローラーになります。

f:id:jyuko49:20190713092208p:plain

f:id:jyuko49:20190713114140j:plain

コントローラー操作の実装

入力周りはOVRInputから取得できます。

developer.oculus.com

準備

OVRInputから入力を受けるため方法として2パターン記述されています。

  1. "OVRManager"をシーンに配置する
  2. OVRInput.Update()Updateで毎フレーム実行する

今回の構成では"OVRCameraRig"に"OVRManager"が付与されているため、既に準備は整っています。

f:id:jyuko49:20190714104936p:plain

新規のスクリプトで"OVRInputTest.cs"を作成、"OVRPlayerController"にアタッチして、このスクリプトに処理を記述していきます。

f:id:jyuko49:20190714120148p:plain

イベントの取得

イベントを取得するタイミングによって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.LTouchOVRInput.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.RTouchOVRInput.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やスクリプトで左右を入れ替えやすくなります。

f:id:jyuko49:20190714122803p:plain

コントローラーの位置の取得

公式では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);
        }
    }
}

f:id:jyuko49:20190714132829p:plain

"OVRControllerPrefab"に直接スクリプトをアタッチしているため、コントローラーの位置と向きはtransform.positiontransform.rotationで取れます。

また、左右の判別で説明した方法を利用して、"OVRControllerPrefab"の"OVRControllerHelper"から自分が左右どちらのコントローラーなのかを取得しています。これにより同一のスクリプトを左右のコントローラーにアタッチすれば同じ動きをします。
左右の違いがわかるようにsphereには別のPrefabを設定できるようにしました。

基本的には、この実装でいけるでしょう。

オブジェクトをつかむ

Colliderの追加

まず、当たり判定のためのColliderを付与します。

コントローラー側は"OVRControllerPrefab"に"Sphere Collider"を付けて、Is Trigger: trueRadius: 0.1にします。Triggerモードにすることでコントローラーのボタンを押すまではすり抜けるようになり、オブジェクトに触れることができます。

f:id:jyuko49:20190714145808p:plain

つかむ対象のオブジェクト側にもColliderを付与します。今回は"Capsule Collider"を使いました。Is Triggerはどちらか一方で有効になっていればいいのでfalseでもいいのですが、"OVRPlayerController"に付与されている"Character Controller"と干渉するので、ひとまずtrueにしました。

次に"Rigid Body"を付与します。Is Triggerを有効にしているので、Use Gravityは無効にしています。そうしないと平面をすり抜けて落ちて行ってしまうので。

f:id:jyuko49:20190714162019p:plain

スクリプトの変更

"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が返す値はtruefalseではなく、押されている強さのfloatになります。
また、コントローラーに付与した"Sphere Collider"と"OVRPlayerController"に付与されている"Character Controller"の干渉を避けるため、"OVRPlayerController"に"Player"タグを付け、スクリプト内の判定で除外しています。

f:id:jyuko49:20190714152701p:plain

簡易的ではありますが、これでコントローラーが触れたオブジェクトをつかむ処理が実装できます。

いつものようにクエリちゃんアセットを使っています。
ライセンス規約とダウンロードはこちらから。

query-chan.com

まとめ

コントローラー操作の基本的なところは、公式のマニュアルで理解できました。

ただ、最後のオブジェクトをつかむ処理については、改良の余地が多々あります。

干渉を避けるためにRigid BodyのUse Gravityを切ってしまっていますが、離した後に床に落ちたり、そのまま投げたりといった動きが自然なため、Use GravityIs Triggerをスクリプトで動的に切り替えた方がよさそうです。

また、つかんでいる間もコントローラーと完全に姿勢を同期させるより、つかんだ瞬間は元の姿勢を維持しつつ、コントローラーを動かしたら移動・回転する方が自然です。
こちらは初期状態の姿勢を内部的に保持することで実現できそうな感じはします。

上記は別の機会に試してみたいと思います。