LoginSignup
5
7

More than 5 years have passed since last update.

[Unity] World Spaceで配置したSliderをRaycastで操作する

Last updated at Posted at 2017-02-02

概要

やりたいこと

World Spaceで配置したuGuiのSliderをRaycastで操作したい。

何でやりたいのか?

(特にVR上だと)マウスを使ってのSliderの操作が出来ないので、視線(raycastで代替)と首振りでSliderの操作が出来ないかなと思ってやりました。

使用バージョン

Unity 5.5.0f3

最終的な操作イメージ

slider.gif
※Debug.DrawRay以外にも実はビーム(LineRenderer)も出してますが、それは特に解説しませんのであしからず。

実装方法

ソース

SliderManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SliderManager : MonoBehaviour {

    [SerializeField]
    GameObject sliderObj;

    [SerializeField]
    GameObject cameraRig;

    GameObject sliderObjParent;
    Slider slider;

    float adjacentDistance;

    float width;
    float scaleX;
    float parentScaleX;
    float oppositeDistance;

    float radTan;
    float angle;

    float defaultRotateY;
    public float DefaultRotateY {
        set { defaultRotateY = value;}
    }
    float nowRotateY;

    float magValue;
    float defaultValue;
    float resetValue;

    bool isSlider = false;
    public bool IsSlider {
        set { isSlider = value;}
    }

    void Start () {

        sliderObjParent = sliderObj.transform.parent.gameObject;
        slider = sliderObj.GetComponent<Slider> ();

        isSlider = false;

        adjacentDistance = Vector3.Distance (cameraRig.transform.position, sliderObj.transform.position);

        width = sliderObj.GetComponent<RectTransform> ().sizeDelta.x;
        scaleX = sliderObj.GetComponent<RectTransform> ().localScale.x;
        parentScaleX = sliderObjParent.GetComponent<RectTransform> ().localScale.x;
        oppositeDistance = width * scaleX * parentScaleX;

        radTan = oppositeDistance / adjacentDistance;
        Debug.Log ("atanRad = " + Mathf.Atan (radTan));
        Debug.Log ("atanDeg = " + (Mathf.Atan (radTan) * Mathf.Rad2Deg));
        angle = Mathf.Atan (radTan) * Mathf.Rad2Deg;

        magValue = (slider.maxValue - slider.minValue);
        angle *= magValue;
        defaultValue = slider.value;

    }

    void Update () {

        if (isSlider && Input.GetMouseButton (0)) {

            nowRotateY = cameraRig.transform.rotation.eulerAngles.y;
            nowRotateY = (nowRotateY >= 180f) ? (nowRotateY - 360f) : nowRotateY; 
            resetValue = (((nowRotateY - defaultRotateY) * (magValue / angle)) * magValue) + defaultValue;

            if (resetValue < slider.minValue) {
                slider.value = slider.minValue;
            } else if (resetValue > slider.maxValue) {
                slider.value = slider.maxValue;
            } else {
                slider.value = resetValue;
            }
        } else {
            isSlider = false;
        }

    }       
}
SimpleRay.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SimpleRay : MonoBehaviour {

    [SerializeField]
    Camera cameraRig;

    [SerializeField]
    SliderManager sliderManager;

    float distance = 10f;
    bool isFirst = true;

    void Update () {

        Ray ray = new Ray (transform.position, transform.forward);
        RaycastHit hit;

        if (Physics.Raycast (ray, out hit)) {
            if (isFirst) {
                float rotateY = cameraRig.transform.eulerAngles.y;
                sliderManager.DefaultRotateY = (rotateY >= 180f) ? (rotateY - 360f) : rotateY;
                isFirst = !isFirst;
            }
            sliderManager.IsSlider = true;
            Debug.DrawRay (ray.origin, ray.direction * distance, Color.red, 0.1f, false);

        } else {
            Debug.DrawRay (ray.origin, ray.direction * distance, Color.blue, 0.1f, false);
        }

    }
}

Source - Gist

手順

  • uGuiのSliderをWorld Spaceで配置する
  • SliderのHandle部分にColliderを付ける
    • ColliderはHandle部分を覆うくらいにリサイズ
  • MainCameraの下に空GameObjectを配置する
    • SimpleRay.csをアタッチする
    • SimpleRay.csにCameraとSlider(の親)を設定
    • position.yを-0.25くらいに設定
    • (↑Cameraから直線上に飛ばすとGameViewで確認できない模様)
  • 適当なObjectにSliderManager.csをアタッチする
    • SimpleRay.csにCameraとSlider(の親)を設定
  • Sceneを再生して、RayをHandleに当てて左クリックを押した状態で動かせることを確認
    • Cameraの向きを動かすためのScriptは以下を参照ください。

仕様

SliderManager.cs

  • 初期処理でSlider.valueの計算に必要な値を算出する
    • CameraとSliderとの距離を求める(隣辺)
    • Sliderの長さを求める(対辺)
      • RectTransformのWidthとScale、および親のScaleから実際の長さを計算する。
      • (↑1つ上の親ではないときは個別に調整ください)
    • 隣辺と対辺から、tanθを求めてradian値から角度を算出する
    • 初期状態のSlider.valueを保有しておく
  • カメラの首振り(Rotation.yの移動量)から、Slider.valueを算出して設定する
    • 初期状態と現在のRotation.yの差分からminValueとmaxValueの間になるように計算する
    • ※maxValueは1以上も対応できますが、minValueは0以下は対応してません。
    • ※右から左、上下の動きは対応してません。

SimpleRay.cs

  • アタッチされたGameObjectから直線上にRayを飛ばす
  • Hitしたら、SliderManagerで使用する初期Rotation.yを設定し、フラグをONにする

参考

三角関数を使う必要があったので、下記が参考になりました。
Unityで三角関数#1

おまけ(Cameraの向きを動かすScript)

  • MainCameraにアタッチする。altを押した状態でCameraの向きを動かせると思います。
VREditorViewer.cs
#if UNITY_EDITOR

using UnityEngine;
using System;
using System.Collections.Generic;
using UnityEditor;

public class VREditorViewer : MonoBehaviour
{

    public bool trackRotation = true;
    public Transform target;

    // Use mouse to emulate head in the editor.
    private float mouseX = 0;
    private float mouseY = 0;
    private float mouseZ = 0;

    void Awake()
    {
        Screen.sleepTimeout = SleepTimeout.NeverSleep;
        Input.gyro.enabled = true;
    }

    public void Update()
    {
        Quaternion rot;

        bool rolled = false;
        if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))
        {
            mouseX += Input.GetAxis("Mouse X") * 5;
            if (mouseX <= -180)
            {
                mouseX += 360;
            }
            else if (mouseX > 180)
            {
                mouseX -= 360;
            }
            mouseY -= Input.GetAxis("Mouse Y") * 2.4f;
            mouseY = Mathf.Clamp(mouseY, -85, 85);
        }
        else if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
        {
            rolled = true;
            mouseZ += Input.GetAxis("Mouse X") * 5;
            mouseZ = Mathf.Clamp(mouseZ, -85, 85);
        }
        if (!rolled)
        {
            mouseZ = Mathf.Lerp(mouseZ, 0, Time.deltaTime / (Time.deltaTime + 0.1f));
        }
        rot = Quaternion.Euler(mouseY, mouseX, mouseZ);

        if (trackRotation)
        {
            if (target == null)
            {
                transform.localRotation = rot;
            }
            else {
                transform.rotation = target.rotation * rot;
            }
        }
    }
}
#endif
5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7