概要
やりたいこと
World Spaceで配置したuGuiのSliderをRaycastで操作したい。
何でやりたいのか?
(特にVR上だと)マウスを使ってのSliderの操作が出来ないので、視線(raycastで代替)と首振りでSliderの操作が出来ないかなと思ってやりました。
使用バージョン
Unity 5.5.0f3
最終的な操作イメージ
※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);
}
}
}
手順
- 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