本文主要是介绍Unity之圆环slider,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、参考文章
Unity_圆环滑动条(圆形、弧形滑动条)_unity弧形滑动条-CSDN博客
此滑动条拖动超过360后继续往前滑动值会从0开始,正常我们超过360度时不可在滑动。
二、 超过360度不可滑动问题解决
参考HTML文章制作: https://www.cnblogs.com/pangys/p/13201808.html
下载链接
修改后的脚本:
using OfficeOpenXml.FormulaParsing.Excel.Functions;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;[RequireComponent(typeof(RectTransform)), ExecuteInEditMode]
public class AnnularSlider : Selectable, IDragHandler, ICanvasElement
{[Serializable]public class DragEvent : UnityEvent{}[Serializable]public class DragValueEvent : UnityEvent<float>{}[SerializeField] private Image _fillImage;[SerializeField] private Image.Origin360 _fillOrigin;[SerializeField] private bool _clockwise;[SerializeField] private bool _wholeNumbers;[SerializeField] private float _minValue;[SerializeField] private float _maxValue = 1f;[SerializeField] private float _maxAngle = 360f;[SerializeField] private float _value;[SerializeField] private RectTransform _handleRect;[SerializeField] private float _radius = 10f;[SerializeField] private bool _towardCenter;[SerializeField] private DragValueEvent _onValueChanged = new DragValueEvent();[SerializeField] private DragEvent _onBeginDragged = new DragEvent();[SerializeField] private DragEvent _onDragging = new DragEvent();[SerializeField] private DragEvent _onEndDragged = new DragEvent();private bool _delayedUpdateVisuals;public Image FillImage{get { return _fillImage; }set{if (SetClass(ref _fillImage, value)){UpdateCachedReferences();UpdateVisuals();}}}public Image.Origin360 FillOrigin{get { return _fillOrigin; }set{if (SetStruct(ref _fillOrigin, value)){UpdateVisuals();}}}public bool Clockwise{get { return _clockwise; }set{if (SetStruct(ref _clockwise, value)){UpdateVisuals();}}}public bool WholeNumbers{get { return _wholeNumbers; }set{if (SetStruct(ref _wholeNumbers, value)){UpdateValue(_value);UpdateVisuals();}}}public float MinValue{get { return _minValue; }set{if (SetStruct(ref _minValue, value)){UpdateValue(_value);UpdateVisuals();}}}public float MaxValue{get { return _maxValue; }set{if (SetStruct(ref _maxValue, value)){UpdateValue(_value);UpdateVisuals();}}}public float MaxAngle{get { return _maxAngle; }set{if (SetStruct(ref _maxAngle, value)){UpdateVisuals();}}}public float Value{get{if (_wholeNumbers) return Mathf.Round(_value);return _value;}set { UpdateValue(value); }}public RectTransform HandleRect{get { return _handleRect; }set{if (SetClass(ref _handleRect, value)){UpdateVisuals();}}}public float Radius{get { return _radius; }set{if (SetStruct(ref _radius, value)){UpdateVisuals();}}}public bool TowardCenter{get { return _towardCenter; }set{if (SetStruct(ref _towardCenter, value)){UpdateVisuals();}}}public DragValueEvent OnValueChanged{get { return _onValueChanged; }set { _onValueChanged = value; }}public DragEvent OnBeginDragged{get { return _onBeginDragged; }set { _onBeginDragged = value; }}public DragEvent OnDragging{get { return _onDragging; }set { _onDragging = value; }}public DragEvent OnEndDragged{get { return _onEndDragged; }set { _onEndDragged = value; }}public float NormalizedValue{get{if (Mathf.Approximately(_minValue, _maxValue)) return 0;return Mathf.InverseLerp(_minValue, _maxValue, Value);}set { Value = Mathf.Lerp(_minValue, _maxValue, value);}}protected override void OnEnable(){base.OnEnable();UpdateCachedReferences();UpdateValue(_value, false);UpdateVisuals();}#if UNITY_EDITORprotected override void OnValidate(){base.OnValidate();if (WholeNumbers){_minValue = Mathf.Round(_minValue);_maxValue = Mathf.Round(_maxValue);}//Onvalidate is called before OnEnabled. We need to make sure not to touch any other objects before OnEnable is run.if (IsActive()){UpdateCachedReferences();UpdateValue(_value, false);_delayedUpdateVisuals = true;}//if (!UnityEditor.PrefabUtility.IsComponentAddedToPrefabInstance(this) && !Application.isPlaying)// CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);}
#endifprotected virtual void Update(){if (_delayedUpdateVisuals){_delayedUpdateVisuals = false;UpdateVisuals();}}public override void OnPointerDown(PointerEventData eventData){if (MayEvent(eventData)){OnBeginDragged.Invoke();}}public double degValue;//存储public void OnDrag(PointerEventData eventData){if (!MayEvent(eventData)) return;_onDragging.Invoke();Vector2 localPoint;//鼠标在ui中的位置if (RectTransformUtility.ScreenPointToLocalPointInRectangle(_fillImage.rectTransform, eventData.position, eventData.pressEventCamera, out localPoint)){var deg = XYToDeg(localPoint.x, localPoint.y);//获取角度(用弧度制π来表示)double min = 0, max = 0;//滑块起点位置区间//根据起点位置进行换算switch (_fillOrigin){case Image.Origin360.Bottom://第四象限 起点为270°终点为360 即:[3π/2, 2π]min = Math.PI * 1.5;max = Math.PI * 2;break;case Image.Origin360.Right://deg = deg;//在第一象限为起点不换算min =max =0;break;case Image.Origin360.Top://第二象限为起点 区间[π/2,π]=>[90,180]min = Math.PI * 0.5;max = Math.PI * 2;break;case Image.Origin360.Left://第三象限为起点 区间[π,2π]=>[180,360]min = Math.PI;max = Math.PI * 2;break;default:break;}//起点位置差值换算if (deg >= min && deg <= max)deg -= min;elsedeg += (max - min);deg = Clockwise ? Math.PI * 2 - deg : deg; //顺、逆时针方向var constMaxAngle = MaxAngle / 180;//圆的最大弧度常数var radian = deg / Math.PI; //除π得到常数值 [0,2π]/π=[0,2]var ratio = (radian / constMaxAngle) * 100;//鼠标移动的角度在圆的最大角度中的占比//限制value的最大最小值var maxValue = constMaxAngle * 100; //最大value值if (ratio > maxValue || ratio < 0)return; if (ratio >= maxValue) ratio = maxValue;if (ratio <= 0) ratio = 0;/*在圆中360°和0°是首尾相连的,为了防止鼠标滑动到360在往前滑动变成0的问题,需要进行一个计算判断。* 举例当鼠标滑动到360度时ratio和degValue值都为100,此时鼠标再往上滑动ratio值就会从0开始。* 在赋值degValue时使用Math.Abs(ratio - degValue)求两个数的绝对值,然后在设置一个最大阈值10。即可解决问题* Math.Abs(100 - 0)得出结果为100。我们设置的最大阈值为10,当鼠标再往上滑动时超出最大阈值不在赋值*/if (Math.Abs(ratio - degValue) > 10) return;//给value赋值if (degValue != Math.Round(ratio)){degValue = Math.Round(ratio);NormalizedValue = (float)degValue / 100;UpdateVisuals();}}}#region 获取角度(用弧度制π来表示)double XYToDeg(float lx, float ly){/* 这里的lx和ly已经换算过的代表了鼠标以圆中心点为原点的坐标向量,* 连接原点和鼠标坐标位置形成一个直角三角形|y轴|* * | * ** | ** | 。 ** | /| *————————|—————————>* | * x轴* | ** | ** * | * *|| *//* 1.获取角度(用弧度制π来表示)* 利用反三角函数Math.Atan(tanθ)来获取角度* 在三角函数中 lx代表邻边,ly代表对边。根据三角函数可以得出 tanθ=ly/lx (关于直角的绘制看上方例图)* 反三角函数Arctan(ly/lx)可得出角度*/double adeg = Math.Atan(ly / lx);/* 2.将角度限制在[0 , 2π]区间。* 已知Math.Atan函数 返回的数值在[-π/2 , π/2] 换成角度是[-90,90],* 但我们需要获取[0 , 2π]即:[0,360]区间的实际值用于计算* 所以需要通过lx和ly的正负判断所在象限用于换算成[0 , 2π]区间的值*/double deg = 0;if (lx >= 0 && ly >= 0){/*第一象限: * 得到的角度在[0,90]区间,即:[0,π/2]* 不换算*/deg = adeg;}if (lx <= 0 && ly >= 0) {/*第二象限:* 得到的角度在[-90,0]区间,即:[-π/2, 0]* 需要换算为[90,180]区间 所以要+π。(在角度制中π为180)*/deg = adeg + Math.PI;}if (lx <= 0 && ly <= 0){/*第三象限:* 得到的角度在[0,90]区间,即:[0,π/2]* 需要换算为[180,270]区间 所以要+π。(在角度制中π为180)*/deg = adeg + Math.PI;}if (lx > 0 && ly < 0) {/*第四象限:* 得到的角度在[-90,00]区间,即:[-π/2, 0]* 需要换算为[270,360]区间 所以要+2π。(在角度制中π为180)*/deg = adeg + Math.PI * 2;}return deg;}#endregionpublic override void OnPointerUp(PointerEventData eventData){if (MayEvent(eventData)){OnEndDragged.Invoke();//Debug.Log("OnEndDragged");}}public void Rebuild(CanvasUpdate executing){}public void LayoutComplete(){}public void GraphicUpdateComplete(){}/// <summary>/// 返回是否可交互/// </summary>/// <returns></returns>private bool MayEvent(PointerEventData eventData){return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;}/// <summary>/// 更新缓存引用/// </summary>private void UpdateCachedReferences(){if (_fillImage){_fillImage.type = Image.Type.Filled;_fillImage.fillMethod = Image.FillMethod.Radial360;_fillImage.fillOrigin = (int)_fillOrigin;_fillImage.fillClockwise = _clockwise;}}/// <summary>/// 更新视觉效果/// </summary>private void UpdateVisuals(){
#if UNITY_EDITORif (!Application.isPlaying)UpdateCachedReferences();
#endifvar angle = NormalizedValue * _maxAngle;if (_fillImage){_fillImage.fillAmount = angle / 360f;}if (_handleRect){_handleRect.transform.localPosition = GetPointFromFillOrigin(ref angle);if (_towardCenter){_handleRect.transform.localEulerAngles = new Vector3(0f, 0f, angle);}}}/// <summary>/// 更新Value/// </summary>/// <param name="value"></param>/// <param name="sendCallback"></param>private void UpdateValue(float value, bool sendCallback = true){value = Mathf.Clamp(value, _minValue, _maxValue);if (_wholeNumbers) value = Mathf.Round(value);if (_value.Equals(value)) return;_value = value;UpdateVisuals();if (sendCallback){_onValueChanged.Invoke(_value);//Debug.Log("OnValueChanged" + _value);}}/// <summary>/// 返回基于起始点的角度(0°~360°)/// </summary>/// <param name="point"></param>/// <returns></returns>/// <exception cref="ArgumentOutOfRangeException"></exception>private float GetAngleFromFillOrigin(Vector2 point){var angle = Mathf.Atan2(point.y, point.x) * Mathf.Rad2Deg; //相对于X轴右侧(FillOrigin.Right)的角度//转换为相对于起始点的角度switch (_fillOrigin){case Image.Origin360.Bottom:angle = _clockwise ? 270 - angle : 90 + angle;break;case Image.Origin360.Right:angle = _clockwise ? -angle : angle;break;case Image.Origin360.Top:angle = _clockwise ? 90 - angle : 270 + angle;break;case Image.Origin360.Left:angle = _clockwise ? 180 - angle : 180 + angle;break;default:throw new ArgumentOutOfRangeException();}转 360 °表示//if (angle > 360)//{// angle -= 360;//}//if (angle < 0)//{// angle += 360;//}return angle;}/// <summary>/// 返回基于起始点、角度、半径的位置/// </summary>/// <param name="angle"></param>/// <returns></returns>/// <exception cref="ArgumentOutOfRangeException"></exception>private Vector2 GetPointFromFillOrigin(ref float angle){//转化为相对于X轴右侧(FillOrigin.Right)的角度switch (_fillOrigin){case Image.Origin360.Bottom:angle = _clockwise ? 270 - angle : angle - 90;break;case Image.Origin360.Right:angle = _clockwise ? -angle : angle;break;case Image.Origin360.Top:angle = _clockwise ? 90 - angle : 90 + angle;break;case Image.Origin360.Left:angle = _clockwise ? 180 - angle : 180 + angle;break;default:throw new ArgumentOutOfRangeException();}var radian = angle * Mathf.Deg2Rad;return new Vector2(Mathf.Cos(radian) * _radius, Mathf.Sin(radian) * _radius);}//设置结构private static bool SetStruct<T>(ref T setValue, T value) where T : struct{if (EqualityComparer<T>.Default.Equals(setValue, value)) return false;setValue = value;return true;}private static bool SetClass<T>(ref T setValue, T value) where T : class{if (setValue == null && value == null || setValue != null && setValue.Equals(value)) return false;setValue = value;return true;}
}
三、使用方法
这篇关于Unity之圆环slider的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!