一、参考文章
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_EDITOR
protected 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);
}
#endif
protected 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;
else
deg += (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;
}
#endregion
public 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_EDITOR
if (!Application.isPlaying)
UpdateCachedReferences();
#endif
var 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;
}
}