VRでのインタラクション

確認済のバージョン: 5.3

-

難易度: 初級

概要

VR では、ユーザーが見ているオブジェクトを頻繁にアクティベートする必要があります。Unity は VR Samples 用に、ユーザーがオブジェクトとインタラクションできるようにするためのシンプルで拡張可能かつ低負荷なシステムを作成しました。このシステムは、VREyeRaycaster、VRInput、VRInteractiveItem の 3 つの主要なスクリプトで構成されています。これらの各クラスに関する簡単な説明は以下をご覧ください。ソースコードも解説されています。

VREyeRaycaster

VREyeRaycaster

このスクリプトは Main Camera にアタッチする必要があります。 Update() のたびに、スクリプトは Physics.Raycast を使ってレイを前方へ 1 つキャストし、それがいずれかのコライダーに当たるかどうか確認します。これは、特定のレイヤーを除外して行うことも可能です。パフォーマンスへの影響を考慮した場合、シーンによっては、インタラクティブなオブジェクトを全て別のレイヤーに移動したほうが良い場合もあるでしょう。

レイキャストがいずれかのコライダーに当たった場合、このスクリプトは、このゲームオブジェクト上の VRInteractiveItem コンポーネントを探します。

Code snippet

VRInteractiveItem interactible = hit.collider.GetComponent<VRInteractiveItem>();    //当たったオブジェクトの VRInteractiveItem の取得を試みる

これにより、ユーザーがオブジェクトを見ているのか、あるいはオブジェクトを見るのを止めたのか、判断することができます。ユーザーがオブジェクトを見始めたり、見るのを止めた場合、それに応じて何かしらの処理([例]メソッドを実行するなど)を行うことができます。

VRInput

VRInput

VRInput は、GearVR でスワイプやタップやダブルタップ(あるいは DK2 使用の場合はそれらに相当する PC 入力)が起こったかどうかを判定する、簡単なクラスです。

VRInput 上で直接イベントにサブスクライブすることができます。

Code snippet

public event Action<SwipeDirection> OnSwipe;                // スワイプ中に経過する全てのフレームで呼び出される(スワイプが無い場合も含む)。
public event Action OnClick;                                // Fire1 が解放され、ダブルクリックでない場合に呼び出される。
public event Action OnDown;                                 // Fire1 が押されると呼び出される。
public event Action OnUp;                                   // Fire1 が解放されると呼び出される。
public event Action OnDoubleClick;                          // ダブルクリックが検知されると呼び出される。
public event Action OnCancel;                               // Cancel が押されると呼び出される。

イベントへのサブスクリプションに関して、詳細はイベント チュートリアルをご覧ください。

VRInteractiveItem

このコンポーネントを追加すること、ゲームオブジェクトをVR 内でインタラクティブにできます。追加先のオブジェクトはコライダーを持っている必要があります。

サブスクライブできるイベントは 6 つあります。

Code snippet

public event Action OnOver;             // ゲイズ(視線)がこのオブジェクト上に来ると呼び出される。
public event Action OnOut;              // ゲイズ(視線)がこのオブジェクトを離れると呼び出される。
public event Action OnClick;            // ゲイズ(視線)がこのオブジェクト上にある間にクリック入力が検知されると呼び出される。
public event Action OnDoubleClick;      // ゲイズ(視線)がこのオブジェクト上にある間にダブルクリック入力が検知されると呼び出される。
public event Action OnUp;               // ゲイズ(視線)がこのオブジェクト上にある間にFire1 が解放されると呼び出される。
public event Action OnDown;             // ゲイズ(視線)がこのオブジェクト上にある間にFire1 が押されると呼び出される。

現在 Over であるか確認するために使用できるブーリアンが 1 つあります。

Code snippet

public bool IsOver
{
    get { return m_IsOver; }              // 現在、このオブジェクト上にゲイズ(視線)があるか?
}

これらのイベントに反応するスクリプトを独自に作成できます。以下は、上記のイベントのいくつかを使用した、ごく簡単な例です。

Code snippet

using UnityEngine;
using VRStandardAssets.Utils;

namespace VRStandardAssets.Examples
{
    // このスクリプトは、イベントをハンドルすることで、アイテムを使用して
    // ゲームオブジェクトにインタラクティブな変更を加える簡単な例です。
    public class ExampleInteractiveItem : MonoBehaviour
    {
        [SerializeField] private Material m_NormalMaterial;                
        [SerializeField] private Material m_OverMaterial;                  
        [SerializeField] private Material m_ClickedMaterial;               
        [SerializeField] private Material m_DoubleClickedMaterial;         
        [SerializeField] private VRInteractiveItem m_InteractiveItem;
        [SerializeField] private Renderer m_Renderer;


        private void Awake ()
        {
            m_Renderer.material = m_NormalMaterial;
        }


        private void OnEnable()
        {
            m_InteractiveItem.OnOver += HandleOver;
            m_InteractiveItem.OnOut += HandleOut;
            m_InteractiveItem.OnClick += HandleClick;
            m_InteractiveItem.OnDoubleClick += HandleDoubleClick;
        }


        private void OnDisable()
        {
            m_InteractiveItem.OnOver -= HandleOver;
            m_InteractiveItem.OnOut -= HandleOut;
            m_InteractiveItem.OnClick -= HandleClick;
            m_InteractiveItem.OnDoubleClick -= HandleDoubleClick;
        }


        //Over イベントをハンドルする
        private void HandleOver()
        {
            Debug.Log("Show over state");
            m_Renderer.material = m_OverMaterial;
        }


        //Out イベントをハンドルする
        private void HandleOut()
        {
            Debug.Log("Show out state");
            m_Renderer.material = m_NormalMaterial;
        }


        //Click イベントをハンドルする
        private void HandleClick()
        {
            Debug.Log("Show click state");
            m_Renderer.material = m_ClickedMaterial;
        }


        //DoubleClick イベントをハンドルする
        private void HandleDoubleClick()
        {
            Debug.Log("Show double click");
            m_Renderer.material = m_DoubleClickedMaterial;
        }
    }
}

この基本的な例が、VRSampleScenes/Scenes/Examples/ にある InteractiveItem シーンでご確認いただけます。

SelectionRadial と SelectionSlider

ユーザーが Fire1 の長押しによってインタラクションの決定(実行)を行う際に表示するゲージは、円形選択ゲージ(SelectionRadial)と直線選択ゲージ(SelectionSlider)が使用可能です。

SelectionRadial(円形選択ゲージ)

SelectionSlider(直線選択ゲージ)

入力が押されている間は選択ゲージが満ちて行き、いっぱいになると OnSelectionCompleteOnBarFilled イベントが実行されます。このコードは SelectionRadial.csSelectionSlider.cs でご確認いただけます(全面的にコメント付きです)。ユーザーエクスペリエンスの観点から見た場合、 VR においては、ユーザーが常に状況を理解して快適に操作を行えることが重要です。一定時間の「長押し」方式による決定操作は、素早さの面では妥協することになりますが誤決定(誤選択)を防ぐことができます。

VRSampleScenes におけるインタラクションの例

VR Samples プロジェクトに含まれるインタラクションの例をいくつか紹介します。各シーンで使用されているインタラクションについて、それがどのように実装されているかも含め見て行きましょう。

Menu シーンにおけるインタラクション

各メニュー画面にはいくつかのコンポーネントが含まれています。ここでは、その中の MenuButtonVRInteractiveItemMesh Collider に注目してみましょう。

Menu

MenuButton コンポーネントは VRInteractiveItem 上の OnOverOnOut イベントにサブスクライブしており、レティクル(照準)がメニュー画面上に来ると円形選択ゲージが表示され、メニューアイテムから視線を外すとゲージが消えるようになっています。ゲージの表示中に Fire1 が長押しされると、ゲージが満ちて行きます。

SelectionRadial

またこのクラスは SelectionRadial 上の OnSelectionComplete イベントにもサブスクライブしており、選択ゲージがいっぱいに満ちると HandleSelectionComplete が呼び出され、カメラがフェードアウトして選択されたステージが読み込まれるようになっています。

Code snippet

private void OnEnable ()
{
    m_InteractiveItem.OnOver += HandleOver;
    m_InteractiveItem.OnOut += HandleOut;
    m_SelectionRadial.OnSelectionComplete += HandleSelectionComplete;
}


private void OnDisable ()
{
    m_InteractiveItem.OnOver -= HandleOver;
    m_InteractiveItem.OnOut -= HandleOut;
    m_SelectionRadial.OnSelectionComplete -= HandleSelectionComplete;
}
      

private void HandleOver()
{
    // ユーザーがシーンの画像に視線を向けたら円形選択ゲージを表示する。
    m_SelectionRadial.Show();

    m_GazeOver = true;
}


private void HandleOut()
{
    // ユーザーがシーンの画像から目を逸らしたら円形選択ゲージを非表示にする。
    m_SelectionRadial.Hide();

    m_GazeOver = false;
}


private void HandleSelectionComplete()
{
    // 円形選択ゲージによる選択の完了時にユーザーがシーンの画像を見ていれば、ボタンがアクティベートされる。
    if(m_GazeOver)
        StartCoroutine (ActivateButton());
}


private IEnumerator ActivateButton()
{
    // カメラが既にフェードアウトしていたら無視する。
    if (m_CameraFade.IsFading)
        yield break;

    // OnButtonSelected イベントにサブスクライブしているものがあれば、それを呼び出す。
    if (OnButtonSelected != null)
        OnButtonSelected(this);

    // カメラがフェードアウトするのを待つ。
    yield return StartCoroutine(m_CameraFade.BeginFadeOut(true));

    // ステージを読み込む。
    SceneManager.LoadScene(m_SceneToLoad, LoadSceneMode.Single);
}

Selection Radial(円形選択ゲージ)の例を見てみましょう。以下の画像の中心にあるピンク色の点にご注目ください。

レティクル(照準)のみ

円形選択ゲージ オフの状態

ユーザーがメニュー画面を見ており、円形選択ゲージ(全く満ちていない)が表示された状態

円形選択ゲージ オンの状態

円形選択ゲージが途中まで満ちている状態(ユーザーがメニュー画面を見ながら Fire1 入力を行っている途中)

満ちていく途中の円形選択ゲージ

選択(決定)の方式は、サンプルプロジェクト全体を通して、直線ゲージあるいは円形ゲージが一定時間でいっぱいに満ちるという同じスタイルを保っています。これは実際の VR プロジェクトを開発する上でも念頭に置くべきことです。一貫性は快適なユーザー体験をもたらし、新しいスタイルのデバイスや入力方式に慣れる上での助けとなるものだからです。

Maze シーンにおけるインタラクション

Maze(迷路)ゲームではテーブルゲーム型のインタラクションの例を見ることができます。これは砲塔から撃たれる弾を回避しながらキャラクターを出口まで導くゲームです。砲塔はスイッチをオフにして停止させることができます。(「ネタバレ」ですが!)

キャラクターの目的地点を選択すると、目的地点マーカーとそこへ至るルートが表示されます。タッチパッドをスワイプするか、方向キーあるいはゲームパッドの左スティックを動かすと、表示角度を変えることができます。

Maze の概観

MazeFloor オブジェクトには、VR でのインタラクションを可能にするための MeshColliderVRInteractiveItem が付属しています。

Maze Floor

MazeCourse ゲームオブジェクトは、MazeFloor および MazeWalls ゲームオブジェクトを含む親です。MazeFloor および MazeWalls ゲームオブジェクトは迷路のレイアウトに使用されるジオメトリ(地形)を含んでいます。

Maze Course

MazeCourse には MazeTargetSetting スクリプトが添付されています。このスクリプトは MazeFloorVRInteractiveItem コンポーネントへの参照を持っています。

MazeTargetSetting

MazeTargetSettingVRInteractiveItemOnDoubleClick イベントにサブスクライブしています。このイベントは OnTargetSet イベントを実行します。 OnTargetSet イベントはパラメーターとしてレティクルの Transform を渡します。

Code snippet

public event Action<Transform> OnTargetSet;                     // これは目的地点が設定されるとトリガーされます。

   
private void OnEnable()
{
    m_InteractiveItem.OnDoubleClick += HandleDoubleClick;
}


private void OnDisable()
{
    m_InteractiveItem.OnDoubleClick -= HandleDoubleClick;
}


private void HandleDoubleClick()
{
    // ターゲット設定がアクティブで OnTargetSet へのサブスクライバーがあれば、呼び出す。
    if (m_Active && OnTargetSet != null)
        OnTargetSet (m_Reticle.ReticleTransform);
}

MazeCharacter ゲームオブジェクトの Player コンポーネントと MazeDestinationMarkerGUI ゲームオブジェクトの DestinationMarker コンポーネントは、両方ともこのイベントにサブスクライブしており、それ相応に反応します。

キャラクターはナビメッシュシステムを使用して迷路内の経路を探索します。Player コンポーネントには HandleSetTarget 関数があり、これがナビメッシュ・エージェントの目的地点をレティクルの Transform の位置に設定し、エージェントのトレイル(キャラクターのパスの視覚的表現)を更新します。

Code snippet

private void HandleSetTarget(Transform target)
{
    // ゲームが終了していなければ、キャラクターおよびそのパスを示すトレイルを制御している AI の目的地点を設定する。
    if (m_IsGameOver)
        return;
            
    m_AiCharacter.SetTarget(target.position);
    m_AgentTrail.SetDestination();
}

DestinationMarker がマーカーをレティクルの Transform の位置に動かします。

Code snippet

private void HandleTargetSet(Transform target)
{
    // ターゲットが設定されたらマーカーを表示する。
    Show();

    // マーカーの位置をターゲットの位置に設定する。
    transform.position = target.position;

    // オーディオを再生する。
    m_MarkerMoveAudio.Play();

    // アニメーションを、そのレイヤー上で、時間オフセットなしで再生する。
    m_Animator.Play(m_HashMazeNavMarkerAnimState, -1, 0.0f);
}

レティクル、目的地点マーカー、プレイヤー、トレイルが確認できます。

Maze

Maze 内のスイッチもまた、オブジェクトとのインタラクションの一例です。これは ColliderVRInteractiveItemSelectionSlider クラスを使用しています。

SelectionSlider

上記で他のインタラクティブなオブジェクトと並んで確認できる通り、 SelectionSlider スクリプトは VRInteractiveItemVRInput が実行するイベントをリッスンします。

Code snippet

private void OnEnable ()
{
    m_VRInput.OnDown += HandleDown;
    m_VRInput.OnUp += HandleUp;

    m_InteractiveItem.OnOver += HandleOver;
    m_InteractiveItem.OnOut += HandleOut;
}

Flyer シーンにおけるインタラクション

Flyer シーンは、一定時間の「無限飛行」です。プレイヤーは周囲を見回しながら飛行船を操縦し、Fire1 の入力で射撃し、小惑星に命中させるか空中のゲートをくぐり抜けるとポイントを獲得できます。『パイロットウィングス』や『スターフォックス』などと同類のゲームプレイです。

インタラクションという観点では、Flyer は比較的シンプルな例です。FlyerLaserControllerVRInputOnDown イベントにサブスクライブしていることで、レーザー射撃が可能になっています。

Code snippet

private void OnEnable()
{
    m_VRInput.OnDown += HandleDown;
}


private void HandleDown()
{
    // ゲームが進行中でなければ戻る。
    if (!m_GameController.IsGameRunning)
        return;

    // 各位置からレーザーを発射する。
    SpawnLaser(m_LaserSpawnPosLeft);
    SpawnLaser(m_LaserSpawnPosRight);
}

Shooter180 シーンと Shooter360 シーン(Target Gallery ・ Target Arena)におけるインタラクション

VR Samples には 2 つの射撃ゲームが含まれています。ひとつは標的の出る側に向かって 180 度視野が開けたもの、もうひとつは X-Men のセレブロのようなスタイルの、360 度の空間にプレイヤーを囲む形で標的が出るものです。

Shooter ゲーム内で生成される標的のそれぞれは ColliderVRInteractiveItemShootingTarget を持っています。

Shooting Target

Shooting Target

ShootingTarget コンポーネントは VRInteractiveItemOnDown イベントにサブスクライブしており、これにより、その標的に命中したかどうかが判定されます。この方法は瞬間的な射撃に向いています。自走ミサイルを使ったゲームでは別の方法を採用する必要があります。

以上、VR インタラクションの各種コンポーネントについて、それらが VR Samples プロジェクト内でどのように使用されているかも含め学ぶことができました。次に、VR Samples でゲイズ(視線)とレティクル(照準)がどのように機能しているか見て行きましょう。

ゲイズ(視線)

VR においては、ユーザーの見ている位置の判定が非常に重要です。これは例えばオブジェクトとのインタラクション、アニメーションのトリガー、弾丸の発射などを行う際の条件として使用されます。VR 内で何かを見ることを「ゲイズ(Gaze)」と呼びます。この用語は VR に関する Unity の記事の中で頻繁に使用されます。

ほとんどの HMD はアイ・トラッキングに未対応ですので、ユーザーのゲイズはあくまでも予測することしかできません。レンズの歪みがあるということは、一般的にユーザーはおおまかにまっすぐ正面を見ていることを意味しますので、これには簡単な解決策があります。上記の「概要」で触れた通り、カメラの中央から前方にレイを 1 つキャストし、それが何に衝突したかを判定すれば良いだけです。当然ながら、衝突する(あるいは、ゲイズによってインタラクションする)可能性のある要素にはすべて Collider コンポーネントが付いている必要があります。

レティクル(照準)

レティクルは、ユーザーの視界の中央を特定するために役立ちます。レティクルのスタイルは、プロジェクトに応じて、単純なドット(点)あるいは十時線を選択できます。

従来の 3D ゲームでは、レティクルは往々にして空間内の一定の場所(多くの場合、画面中央)に固定表示されます。VR では、レティクルの位置設定はもう少し複雑になります。ユーザーが VR 内で周囲を見渡す時、視線は、カメラに近いオブジェクトに集中します。レティクルの位置が固定されていると、物が二重に見えてしまいます。これは、実際に目の前に指を一本立てて近くの物(指)と遠くの物を同時に見てみると分かります。指に焦点を合わせると遠くの物が二重に見え、遠くの物に焦点を合わせると指が二重に見えます。これは voluntary Diplopia (意図的に左右の目の焦点をずらすこと)として知られます。

ユーザーが周囲を見渡して様々な距離にあるオブジェクトに焦点を合わせた時にレティクルが二重に見えてしまう現象を防ぐには、その時点でユーザーが見ているオブジェクトの表面と同じ(3D 空間における)位置にレティクルを配置する必要があります。

単純に空間内のその位置にレティクルを配置しただけでは、レティクルは遠距離では小さく、近距離では大きく見えてしまいます。距離に関係なく同じ大きさで見えるようにするには、レティクルの縮尺(スケール)をカメラからの距離に応じた設定にする必要があります。

この仕組みを理解するために、例として Examples/Reticle シーンの、距離と縮尺の異なるレティクルが表示された画像を見て見ましょう。

カメラから近いオブジェクトに位置するレティクル

近距離のレティクル

カメラから少し遠いオブジェクトに位置するレティクル

中距離のレティクル

はるか遠くに配置されたレティクル

遠距離のレティクル

縮尺と位置の設定のおかげで、ユーザーからは、距離に関係なく同じサイズでレティクルが表示されます。

衝突するオブジェクトがない時は、事前に定義した位置にレティクルを配置します。環境が屋外の場合は、カメラの Far クリップ面のすぐ手前でも良いかもしれません。屋内の場合はもっとずっと近くになるかも知れません。

レティクルをその他のゲームオブジェクトの上に描画する

レティクルが他のオブジェクトと同じ位置に配置されている場合、オブジェクトに食い込んで切れて表示されてしまう可能性があります。

切れて表示されたレティクル

これを解決するには、レティクルはシーン内のその他全ての要素の上(前面)にレンダーする必要があります。VR Samples には、Unity の既存の “UI/Unlit/Text” シェーダーである UIOverlay.shader を基にしたシェーダーが提供されています。マテリアル用にシェーダーを選択する際に “UI/Overlay” 以下に表示されます。

これは UI 要素とテキストの両方に使用でき、シーン内の他のオブジェクトの上に描画を行います。

切れていないレティクル

シーン内のゲームオブジェクトにレティクルを合わせる

最後に、レティクルの角度を、その当たっているオブジェクトの法線に合わせる方法を学びましょう。これは RaycastHit.normal を使って行うことができます。Reticle の設定の方法は以下の通りです。

Code snippet

public void SetPosition (RaycastHit hit)
{
    m_ReticleTransform.position = hit.point;
    m_ReticleTransform.localScale = m_OriginalScale * hit.distance;

    // レティクルが衝突先の要素の法線を使用する場合、
    if (m_UseNormal)
        // 法線に沿ったその前方ベクトルを基に角度を設定する。
        m_ReticleTransform.rotation = Quaternion.FromToRotation (Vector3.forward, hit.normal);
    else
        // ただし、その法線を使用していない場合は、そのローカル角度は元々のものと同じになる。
        m_ReticleTransform.localRotation = m_OriginalRotation;
}

これを実際に行った例を Maze シーンでご覧いただけます。

壁の法線に合わせたレティクルです。

壁のレティクル

床の法線に合わせたレティクルです。

床のレティクル

VR Samples には Reticle スクリプトのサンプルも含まれています。これは VREyeRaycaster と共に使用することでレティクルをシーン内の正しい位置に配置し、必要な場合は、衝突したオブジェクトの法線に一致させることもできます。

レティクル

上記は全て VRSampleScenes/Scenes/Examples/ 内の Reticle シーンで確認することができます。

VR における頭の角度および位置

HMD(ヘッドマウントディスプレイ)の角度と位置は、言うまでもなく VR 内で環境(周囲)を見渡すために使用されますが、その値に応じてオブジェクトを反応させることもできます。

これらの値にアクセスするには、VR.InputTracking クラスを使用して、どのVRNode にアクセスしたいか指定する必要があります。頭の角度を取得したい場合は、個別の眼ではなく VRNode.Head を使用します。詳細はGetting Started with VR Development の章の、カメラノードに関する項目をお読みください。

入力方式として角度を利用する例としては、頭の角度によってメニューやオブジェクトの角度を微妙に変えるなどのケースが挙げられます。この使用例は VRSampleScenes/Examples/Rotation シーンと ExampleRotation スクリプトでご確認いただけます。

Code snippet

// ゲームオブジェクトの Euler 角度を保存する。
var eulerRotation = transform.rotation.eulerAngles;

// y 軸中心の角度をユーザーの角度に一致させる。
eulerRotation.x = 0;
eulerRotation.z = 0;
eulerRotation.y = InputTracking.GetLocalRotation(VRNode.Head).eulerAngles.y;

ユーザーが見ている場所に応じてオブジェクトの角度が変わります。

回転 左

回転 右

サンプルの Flyer ゲームの FlyerMovementController 内で、飛行船の位置が頭の角度に応じて変わるのをご確認いただけます。

Code snippet

Quaternion headRotation = InputTracking.GetLocalRotation (VRNode.Head);
m_TargetMarker.position = m_Camera.position + (headRotation * Vector3.forward) * m_DistanceFromCamera;

Flyer

VR ゲームプレイ中のタッチパッドとキーボードによるインタラクション

Gear VR は HMD の側面にタッチパッドを搭載しています。 これは Unity にはマウスとして認識されます。したがって以下が使用可能です。

Input.mousePosition

Input.GetMouseButtonDown

Input.GetMouseButtonUp

Gear VR では、タッチパッドからスワイプデータも取得する必要があります。スワイプ、タップ、ダブルタップを扱う VRInput というサンプルスクリプトも提供しています。これによりキーボードの方向キーや Left-Ctrl(Unity デフォルトの入力定義で Fire1)(あるいは Left Mouse ボタン)でスワイプとタップをトリガーすることも可能になっています。

現時点では Unity のコンテンツを GearVR でテストする方法がないため、Unity Editor 上では、DK2 を使用して Gear VR コンテンツのテストを行うことがあるかもしれません。Gear VR タッチパッドは実質上マウスとして機能するので、単純にマウスを使用して入力をシミュレートします。HMD の装着中はキーボードのほうが探しやすいので、利便性を考えて、VRInput は、方向キーをスワイプ、Left-Ctrl (Fire1) をタップとして扱うこともできるようになっています。

ゲームパッドの基本的な機能に対応するため、左スティックがスワイプ、ボタンのひとつがタップとして機能するようになっています。

スワイプへのサブスクリプションの例が VRSampleScenes/Scenes/Examples/Touchpad でご確認いただけます。

以下の ExampleTouchpad スクリプトでは、スワイプの方向に応じて AddTorque を Rigidbody に適用することで、オブジェクトを回転させています。

Code snippet

using UnityEngine;
using VRStandardAssets.Utils;

namespace VRStandardAssets.Examples
{
    // このスクリプトでは、スワイプ制御を処理する方法
    // の簡単な例をです。
    public class ExampleTouchpad : MonoBehaviour
    {
        [SerializeField] private float m_Torque = 10f;
        [SerializeField] private VRInput m_VRInput;                                        
        [SerializeField] private Rigidbody m_Rigidbody;                                    


        private void OnEnable()
        {
            m_VRInput.OnSwipe += HandleSwipe;
        }


        private void OnDisable()
        {
            m_VRInput.OnSwipe -= HandleSwipe;
        }


        //AddTorque の Ridigbody への適用によってスワイプイベントを処理する
        private void HandleSwipe(VRInput.SwipeDirection swipeDirection)
        {
            switch (swipeDirection)
            {
                case VRInput.SwipeDirection.NONE:
                    break;
                case VRInput.SwipeDirection.UP:
                    m_Rigidbody.AddTorque(Vector3.right * m_Torque);
                    break;
                case VRInput.SwipeDirection.DOWN:
                    m_Rigidbody.AddTorque(-Vector3.right * m_Torque);
                    break;
                case VRInput.SwipeDirection.LEFT:
                    m_Rigidbody.AddTorque(Vector3.up * m_Torque);
                    break;
                case VRInput.SwipeDirection.RIGHT:
                    m_Rigidbody.AddTorque(-Vector3.up * m_Torque);
                    break;
            }
        }
    }
}

VR Samples 内の VRInput の例

上述の通り、全てのサンプルゲームで、VRInput を使用してタッチパッドとキーボードを処理しています 。Maze のカメラもスワイプに反応します。

Maze

このシーンでは、CameraOrbit がスワイプをリッスンすることによって、視点が変えられるようになっています。

Code snippet

private void OnEnable ()
{
    m_VrInput.OnSwipe += HandleSwipe;
}


private void HandleSwipe(VRInput.SwipeDirection swipeDirection)
{
    // ゲームがプレイ中でない、カメラがフェードアウト中の場合は、戻って、スワイプをハンドルしない。
    if (!m_MazeGameController.Playing)
        return;

    if (m_CameraFade.IsFading)
        return;

    // そうでない場合は、正または負のインクリメントでカメラの回転を開始する。
    switch (swipeDirection)
    {
        case VRInput.SwipeDirection.LEFT:
            StartCoroutine(RotateCamera(m_RotationIncrement));
            break;

        case VRInput.SwipeDirection.RIGHT:
            StartCoroutine(RotateCamera(-m_RotationIncrement));
            break;
    }
}

Maze(迷路)自体を回転させるのではなくシーン内でカメラを周回させているのは理由に関しては動きに関する章 をご覧ください。

以上で、VR の基本的なインタラクションに関して、 VR Samples のシーン の範囲内で、大まかに理解することができました。実際には様々な方法がありますが、VR 開発を始めるに当たって簡単に使えるものをご紹介しました。次章では、VR における各種のユーザーインターフェース についてご紹介します。また、VR に関する疑問は、他の Unity ユーザーの皆さんにもぜひ質問してみてください。VR に関する Unity フォーラム もご活用いただけます。