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

# 概要
## やりたいこと
World Spaceで配置したuGuiのSliderをRaycastで操作したい。

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

## 使用バージョン
Unity 5.5.0f3

## 最終的な操作イメージ
![slider.gif](https://qiita-image-store.s3.amazonaws.com/0/119425/c318d77e-3955-366d-0ce4-d37497dcd4c5.gif)
※Debug.DrawRay以外にも実はビーム（LineRenderer）も出してますが、それは特に解説しませんのであしからず。

# 実装方法
## ソース
```csharp: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;
		}

	}		
}
```
```csharp: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](https://gist.github.com/atsushishi/7c5bb0819aedb2edd07f8dacbe0d8d6f)

## 手順
- 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](http://qiita.com/amano-kiyoyuki/items/71f18fb710197624996e)

## おまけ（Cameraの向きを動かすScript）
- MainCameraにアタッチする。altを押した状態でCameraの向きを動かせると思います。

```csharp: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
```