Unity之圆环slider

embedded/2024/10/21 5:33:48/

一、参考文章

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;}
}

三、使用方法


http://www.ppmy.cn/embedded/4727.html

相关文章

Unity UGUI透明区域点击无效

是这样的&#xff0c;我有一张图&#xff0c;客户给的是1920*1080&#xff0c;但只有中间部分是按钮&#xff0c;是有效像素。为了让空白区域点击无效。需要设置如下 并且加上下面这句 this.GetComponent<Image>().alphaHitTestMinimumThreshold 0.1f;

Phpstorm环境配置与应用

PhpStorm是一款专业的PHP集成开发环境&#xff08;IDE&#xff09;&#xff0c;提供了丰富的功能和工具&#xff0c;可以帮助开发者更高效地开发PHP应用程序。下面是PhpStorm环境配置与应用的步骤: 下载和安装PhpStorm&#xff1a;从官方网站&#xff08;https://www.jetbrains…

【系统分析师】系统安全分析与设计

文章目录 1、安全基础技术1.1 密码相关1.1.1对称加密1.1.2非对称加密1.1.3信息摘要1.1.4数字签名1.1.5数字信封 1.2 PKI公钥体系 2、信息系统安全2.1 保障层次2.2 网络安全2.2.1WIFI2.2.2 网络威胁与攻击2.2.3 安全保护等级 2.3计算机病毒与木马2.4安全防范体系 【写在前面】 记…

手把手教你实现贪吃蛇

前言 在实现贪吃蛇前&#xff0c;我们需要熟练地掌握C语言知识&#xff0c;对初阶数据结构中的链表有一定的掌握&#xff0c;并且我们还会使用到Win 32 API 的知识&#xff0c;下面我会对需要使用到的API接口函数进行解释。最终的代码我放在后面&#xff0c;有需要的可以自取。…

红黑树(Red-Black Tree)

红黑树&#xff08;Red-Black Tree&#xff09;是一种自平衡的二叉查找树&#xff0c;它具有以下特性&#xff1a; 1. 每个节点要么是红色&#xff0c;要么是黑色。 2. 根节点是黑色的。 3. 每个叶子节点&#xff08;NIL节点&#xff09;是黑色的。 4. 如果一个节点是红色的&am…

企业数据分析的维度一般有哪些?

​在很多场景下&#xff0c;都会进行企业的一个分析&#xff0c;来反应我们的问题。常见的需要分析企业数据的场景有&#xff1a;业务优化&#xff08;月度季度&#xff09;&#xff0c;需要做投资决策时&#xff0c;有融资需求&#xff0c;或者战略上出现了改变时&#xff0c;…

探索C语言数据结构:利用顺序表完成通讯录的实现

在好久之前我就已经学习过顺序表&#xff0c;但是在前几天再次温习顺序表的时候&#xff0c;我惊奇的发现顺序编表可以完成我们日常使用的通讯录的功能&#xff0c;那么今天就来好好通过博客总结一下通讯录如何完成吧。 常常会回顾努力的自己&#xff0c;所以要给自己的努力留…

CSS3 立体 3D 变换

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍CSS3 立体 3D 变换&#x1f48e;1 坐标轴&#x1f48e;2 perspective 透视视…