読者です 読者をやめる 読者になる 読者になる

しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

HoloLens で Unity の Button へのタップを検出してみる

※ これは 2016/05/31 Unity HoloLens Technical Preview ver.5.4.0beta14(Windows版) 時点の情報です

最新版では動作が異なる可能性がありますのでご注意ください

前回 は Button へのホールド操作を検出したのでさらにタップ操作を検出してみたいと思います

HoloLens 用のジェスチャ認識クラス UnityEngine.VR.WSA.Input.GestureRecognizer には TappedEvent というイベントが用意されているのでこちらを利用します

using System.Collections;
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.VR.WSA.Input;

public class VRUI : MonoBehaviour
{
    /// <summary>
    /// ボタンのラベルテキスト
    /// </summary>
    [SerializeField]
    private UnityEngine.UI.Text buttonLabel;

    /// <summary>
    /// ターゲッテイング用カーソル
    /// </summary>
    [SerializeField]
    private GameObject cursor;

    /// <summary>
    /// ジェスチャ検知
    /// </summary>
    private GestureRecognizer gesture;

    /// <summary>
    /// 視線オーバーフラグ
    /// </summary>
    private bool isOver = false;

    /// <summary>
    /// ホールドジェスチャフラグ
    /// </summary>
    private bool isHold = false;

    /// <summary>
    /// タップカウント
    /// </summary>
    private int count = 0;

    /// <summary>
    /// 初期処理
    /// </summary>
    public void Awake()
    {
        this.gesture = new GestureRecognizer();
        this.gesture.SetRecognizableGestures(GestureSettings.Hold | GestureSettings.Tap);
        this.gesture.HoldStartedEvent += this.OnButtonHold;
        this.gesture.HoldCompletedEvent += this.OnButtonHoldEnd;
        this.gesture.HoldCanceledEvent += this.OnButtonHoldEnd;
        this.gesture.TappedEvent += this.OnButtonClick;
        this.gesture.StartCapturingGestures();
    }

    /// <summary>
    /// 破棄処理
    /// </summary>
    public void OnDestroy()
    {
        if (this.gesture != null && this.gesture.IsCapturingGestures())
        {
            this.gesture.StopCapturingGestures();
            this.gesture.HoldStartedEvent -= this.OnButtonHold;
            this.gesture.HoldCompletedEvent -= this.OnButtonHoldEnd;
            this.gesture.HoldCanceledEvent -= this.OnButtonHoldEnd;
            this.gesture.TappedEvent -= this.OnButtonClick;
        }
    }

    /// <summary>
    /// 定期的な処理
    /// </summary>
    public void FixedUpdate()
    {
        RaycastHit hit;
        if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out hit))
        {
            this.cursor.transform.position = hit.point;
            this.cursor.transform.rotation = Quaternion.Euler(hit.normal);
            this.cursor.SetActive(true);

            var target = hit.collider.gameObject.GetComponent<Button>();
            if (target != null)
            {
                this.isOver = true;
            }
        }
        else
        {
            this.isOver = false;
            this.cursor.SetActive(false);
        }
    }

    /// <summary>
    /// 毎フレームごとの処理
    /// </summary>
    public void Update()
    {
        if (this.isOver)
        {
            if (this.isHold)
            {
                this.buttonLabel.text = string.Format("Pressed({0})", count);
                return;
            }

            this.buttonLabel.text = string.Format("Over({0})", count);
            return;
        }

        if (!this.isHold)
        {
            this.buttonLabel.text = string.Format("Button({0})", count);
        }
        //ホールド中に視線を外した場合は何もしない
    }

    /// <summary>
    /// 視線ホールドイベントハンドラ
    /// </summary>
    /// <param name="source">入力ソース</param>
    /// <param name="headRay">頭部の方向</param>
    private void OnButtonHold(InteractionSourceKind source, Ray headRay)
    {
        this.isHold = true;
    }

    /// <summary>
    /// 視線ホールド終了イベントハンドラ
    /// </summary>
    /// <param name="source">入力ソース</param>
    /// <param name="headRay">頭部の方向</param>
    private void OnButtonHoldEnd(InteractionSourceKind source, Ray headRay)
    {
        this.isHold = false;
    }

    /// <summary>
    /// 視線クリックイベントハンドラ
    /// </summary>
    /// <param name="source">入力ソース</param>
    /// <param name="tapCount">タップ回数</param>
    /// <param name="headRay">頭部の方向</param>
    private void OnButtonClick(InteractionSourceKind source, int tapCount, Ray headRay)
    {
        if (this.isOver)
        {
            this.count += tapCount;
        }
    }
}

最初に SetRecognizableGestures の引数に GestureSettings.Tap を追加する以外はいたって普通の実装ですね

タップ回数をカウントして Button のラベルの最後尾にカッコつきで表示するようにしています

これをさっそくエミュレーター実行!

f:id:matatabi_ux:20160709105314g:plain

ちょっと思っていた動作と違い、短く人差し指を折り戻すとタップになりますが、ホールドしてはなした場合はタップ扱いにならないようですね

いったんホールドした場合もタップ扱いする場合は HoldCompletedEvent で検出する必要がありそうです