VRシーン内で手を用いてスライダを操作する[unity][steamVR]

やりたいこと

・SteamVRシーン中で手を用いてスライダの値を操作(キャリブレーションに用いる)

youtu.be

 

背景

VRシーン中でゲームプレイヤーに音響などの設定を操作させたかった。よくあるレーザーポインタ方式はほかのシーンと一貫していなかったので使いたくなかった。SteamVR中でスライダをつかんで操作する実装が載っているサイトはなく、寧ろいくつか質問トピックが回答なしの状態で放置されていたため、書くことにした。

 

実装

指先に付けた当たり判定がスライダ上の当たり判定に当たったときにスライダの両端の座標を求め、指でスライダを握っているときには指先の座標のスライダ上での位置を求めてslider.valueに代入する。指を開いたらスライダを離す。

 

PointerController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;
using UnityEngine;
using UnityEngine.UI;
public class PointerController : MonoBehaviour
{
    StimulusPoint_config stimulusPoint;
    Slider slider;

    int slider_Max;
    int slider_min;
    bool preGrabValue = false;

    Vector3 slider_left;
    Vector3 slider_right;

    void Start()
    {
        //指先位置につけたgameobjectにつけたスクリプト 握っている状態ではstimulusPoint.Grabbingがtrueになる
        stimulusPoint = gameObject.GetComponent<StimulusPoint_config>();
    }
   
    void OnTriggerStay(Collider col)
    {
        //スライダ上にsphereコライダを置いておく
        if (col.gameObject.CompareTag("Slider") && stimulusPoint.Grabbing && slider == null)
        {
            Debug.Log("Grab");
            //スライダを得る
            slider = col.gameObject.GetComponentInParent<Slider>();
            Debug.Log(slider);
            slider_Max = (int)slider.maxValue;
            slider_min = (int)slider.minValue;
            Vector3[] v = new Vector3[4];
            slider.gameObject.GetComponent<RectTransform>().GetWorldCorners(v);
            //スライダの左端、右端のworld座標を得る
            slider_left = (v[0] + v[1])/2;
            slider_right = (v[2] + v[3])/2;
        }
    }

    void OnTriggerExit(Collider col){
        //slider = null;
    }
   
    void Update()
    {
        //指を開いたときにスライダを離す
        if (slider != null)
        {
            //指を開いた瞬間にのみ以下を実行
            if(preGrabValue != stimulusPoint.Grabbing && !stimulusPoint.Grabbing){
                slider = null;
                Debug.Log("slider ungrab");
            }
        }

        if (slider != null)
        {
            //自分の握っている指の先端位置がスライダ上のどの位置にあるかを割合で出す
            float ratio = (stimulusPoint.transform.position.x - slider_left.x)/(slider_right.x - slider_left.x);
            Debug.Log(ratio);
            //上の割合を用いてスライダのvalueを定める 今回はスライダは整数値なので適当に丸める
            slider.value = slider_min + Mathf.Round( (slider_Max - slider_min) * ratio);
        }

        //指の開閉を記録
        preGrabValue = stimulusPoint.Grabbing;
    }
}
StimulusPoint_config.cs
 
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class StimulusPoint_config : MonoBehaviour
{
    Material mat;
    public bool Grabbing;
    public bool Drawing;
    [SerializeField] GameObject IndexTip;
    [SerializeField] GameObject ThumbTip;
    [SerializeField] float Threshold = 0.04f;

    void Start()
    {
        mat = gameObject.GetComponent<Renderer>().material;
    }

    // Update is called once per frame
    void Update()
    {
       // 人差し指と親指の距離で開閉を判断
        if ((IndexTip.transform.position - ThumbTip.transform.position).magnitude < Threshold)
        {
            //Debug.Log((IndexTip.transform.position - ThumbTip.transform.position).magnitude);
            //Debug.Log("Grab");
            Grabbing = true;
            mat.color = Color.red;
        }else{
             Grabbing = false;
             mat.color = Color.white;
        }
    }
}

 

スライダの当たり判定の為にsphereをここにおく