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

三、使用方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/635893.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式Linux开发

(17 封私信 / 1 条消息) 嵌入式Linux应用 - 搜索结果 - 知乎 (zhihu.com)

css层叠性,继承性,优先级

前言 本文概要&#xff1a;讲述css的三大特性&#xff0c;层叠&#xff0c;继承和优先级。 层叠性 描述&#xff1a;我们试想以下这种情况&#xff1a;我们定义了同一个选择器&#xff0c;但是定义的属性不同。属性有相同的也有不同的&#xff0c;那么最后我们这个页面会听谁的…

小图标还不会设计!

ICON图标设计 hello&#xff0c;我是小索奇 image-20230805225451447 你有好奇过这样的图标如何设计的吗&#xff1f; 其实非常简单&#xff0c;仅需要一行代码即可完成&#xff0c;本篇文章就带伙伴们使用&#xff0c;每天看一篇&#xff0c;简单易懂&#xff0c;日久技长~…

设计模式学习笔记 - 开源实战三(中):剖析Google Guava中用到的设计模式

概述 上篇文章&#xff0c;我通过 Google Guava 这样一个优秀的开源类库&#xff0c;讲解了如何在业务开发中&#xff0c;发现跟业务无关、可以复用的通用功能模块&#xff0c;并将它们抽离出来&#xff0c;设计成独立的类库、框架或功能组件。 本章再来学习下&#xff0c;Go…

稀碎从零算法笔记Day54-LeetCode:39. 组合总和

题型&#xff1a;数组、树、DFS、回溯 链接&#xff1a;39. 组合总和 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数…

卷王问卷考试系统/SurveyKing调查系统源码

SurveyKing是一个功能强大的开源调查问卷和考试系统&#xff0c;它能够快速部署并适用于各个行业。 这个系统提供了在线表单设计、数据收集、统计和分析等功能&#xff0c;支持20多种题型&#xff0c;提供多种创建问卷的方式和设置。 项 目 地 址 &#xff1a; runruncode.c…

软件设计师软考中项学习(二)之计算机系统基础知识

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 学习目标学习内容学习笔记学习总结 学习目标 计算机系统硬件基本组成 中央处理…

React【Day4】

路由快速上手 1. 什么是前端路由 一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候&#xff0c;path 对应的组件会在页面中进行渲染 2. 创建路由开发环境 # 使用CRA创建项目 npm create-react-app react-router-pro# 安装最新的ReactRouter包 …

【6】mysql查询性能优化-关联子查询

【README】 0. 先说结论&#xff1a;一般用inner join来改写in和exist&#xff0c;用left join来改写not in&#xff0c;not exist&#xff1b;&#xff08;本文会比较内连接&#xff0c;包含in子句的子查询&#xff0c;exist的性能 &#xff09; 1. 本文总结自高性能mysql 6…

Spark-机器学习(3)回归学习之线性回归

在之前的文章中&#xff0c;我们了解我们的机器学习&#xff0c;了解我们spark机器学习中的特征提取和我们的tf-idf&#xff0c;word2vec算法。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你…

异常检测 | SVDD支持向量数据描述异常数据检测(Matlab)

异常检测 | SVDD支持向量数据描述异常数据检测&#xff08;Matlab&#xff09; 目录 异常检测 | SVDD支持向量数据描述异常数据检测&#xff08;Matlab&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 用于一类或二元分类的 SVDD 模型 多种核函数&#xff08;…

栈与堆的比较

栈与堆的比较 栈与堆的比较申请后系统的响应申请效率的比较申请大小的限制堆和栈中的存储内容总结参考 栈与堆的比较 内存布局&#xff1a; 申请后系统的响应 栈&#xff1a;只要栈的剩余空间大于所申请空间&#xff0c;系统将为程序提供内存&#xff0c;否则将报异 常提示…