凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Oculus Rift で頭の動き + タップで簡単に Unity 4.6 UI を選択できるやつを作ってみた

はじめに

HMD での UI 操作は色々と議論がなされていると思いますが、中でも頭の向きを利用した UI は結構安定して操作できると個人的に感じています。頭は思ったよりも低カロリーで正確に動かすことが可能で、実際ブラウザ操作(リンククリック)もできるくらいです。

他にも Leap Motion を使った指の空中タッチ等も色々試したり、色んな人が利用できるようにしてフィードバックを頂いて色々考察できればと思い、まずは手始めに Unity 4.6 UI頭の向きで選択 + HMD タップで決定できる VR 用 Input Module およびカスタム Raycaster を作ってみました。

デモ

頭の向きで UI を選択、コツンと HMD を叩くと決定します。

ダウンロード

Unity 4.6 新 UI システムのイベントシステム

前回、Unity 4.6 GUI の EventSystem を拡張する方法について書きました。

が、文字ばっかりでわかりにくかったので、もう少しざっくりとした概念を絵を交えて書きます(ただしドキュメントやコードからの推測が混ざっているため正しい情報とは限らないのでご留意頂ければと思います)。

Unity 4.6 の新 GUI でのイベントの取り扱いは、Event System を基点として、どのオブジェクトが現在選択されているかを判断する Raycaster とマウスやキーボードのイベントを解釈・ディスパッチする Input Module からなります。色々なルールを複数持てるように Raycaster は複数存在可能です。具体的には CanvasGUI の当たり判定を担当する GraphicRaycaster やコライダのついたオブジェクトの当たり判定を担当する PhysicsRaycaster 等です。一方、イベントの解釈を行う Input Module はただ 1 つしか持てません。

f:id:hecomi:20140930223856p:plain

具体的な流れとしては、EventSystem コンポーネントを基点として、選択された Input Module の Process() が毎フレーム呼ばれ、その中で EventSystem.RaycastAll() を通じて当たり判定を行い、該当のオブジェクトに対して適切なイベント(ホバーやドラッグ、クリック等)を ExecuteEvents.Execute() で送信してあげます。

f:id:hecomi:20140930224959p:plain

これらをよしなに自作してあげれば、既存の Event System の上に乗っかってイベントハンドリングが出来るので、GUI をよしなに利用してあげたり出来るわけです。ひな形としては、Input Module は UnityEngine.EventSystems.BaseInputModule を、Raycaster は UnityEngine.EventSystems.BaseRaycaster を継承する形になります。

CustomInputModule

using UnityEngine;
using UnityEngine.EventSystems;

public class MyInputModule : BaseInputModule
{
    /*!
     * 毎フレーム呼ばれるイベント解釈処理
     */
    public override void Process()
    {
        // モリモリする
    }
}

CustomRaycaster

using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;

public class CustomRaycaster : BaseRaycaster
{
    /*!
     * Raycast に使用するカメラ
     *
     * eventCamera は override 必須
     * Raycast でしか使われないと思われるので別に実装しなくても良い(はず)
     * ここではメインカメラを割り当てている
     */
    public override Camera eventCamera
    {
        get { return Camera.main; }
    }

    /*!
     * 当たり判定を行う処理
     * Raycast は現在の InputModule から呼ばれる
     *
     * @param eventData
     *     マウス座標が入っている
     *     このモジュールでは無視
     * @param resultAppendList
     *     他の Raycaster も含めた全ヒットデータが入っている
     *     この Raycaster によって追加される要素を詰めていく
     */
    public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
    {
        // resultAppendList.Add( hogehoge );
    }
}

あとは、このあたりのコードを参考にモリモリする形です。

タップ決定の仕組み

ザバイオーネさんのコードを利用させていただきました。

タップ決定の瞬間、叩くとカーソルがずれるので、そのズレを吸収するために照準の動きはフィルタを掛けて滑らかにしています。

設計の反省点

  • GraphicRaycaster はそのままに、Input Module 側だけで Raycast() する際に引き渡すマウスの座標情報を照準の位置に差し替えてやれば、Input Module の変更だけで済んだかもしれません...。

考察

思ったより小突く操作は高カロリーだし、操作してる感がないなぁ、という感じがしました。以前試してみた時間が経つと決定する操作は楽だったのですが、勝手にどこか決定されてしまってうーん、という感じでした。が、その時の UI がブラウザだったのでコンテンツを見るのが主になり、それで誤決定されてしまったので相性が悪かったのかもしれません。今回のようなメニューを表示して選択、閉じる、みたいな流れだと、誤操作も少なくなりそうな気がするので、楽な方、という意味では以前の方式のほうが良い気もします。ただ、もう少し複雑な UI や誤操作をなるべく避けたい場合は、今回のような明示的に決定を行う方式のほうが優れていると思います。

明示的に、という意味では USB ポートを利用して側面にスイットを設けてクリック、とかのほうが良さそうです。が、今回の方式だとスイッチなくても Oculus Rift さえあればできるのでその分利点はあると思います。

操作してる感、という点では横に叩いてるのに UI は押し込まれているので、そこら辺の見せ方を工夫すると良くなる気もします。

あと、メニューの表示に関しては、ダブルタップもハンドル出来るようにして、これに割り当てると良いかな、とか思いました。

おわりに

次は Leap Motion 用モジュールを作って頭の向きによる操作と比較してみたいと思います。