Unity之圆环slider

2024-04-21 00:36
文章标签 unity slider 圆环

本文主要是介绍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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921739

相关文章

Unity Post Process Unity后处理学习日志

Unity Post Process Unity后处理学习日志 在现代游戏开发中,后处理(Post Processing)技术已经成为提升游戏画面质量的关键工具。Unity的后处理栈(Post Processing Stack)是一个强大的插件,它允许开发者为游戏场景添加各种视觉效果,如景深、色彩校正、辉光、模糊等。这些效果不仅能够增强游戏的视觉吸引力,还能帮助传达特定的情感和氛围。 文档

Unity协程搭配队列开发Tips弹窗模块

概述 在Unity游戏开发过程中,提示系统是提升用户体验的重要组成部分。一个设计良好的提示窗口不仅能及时传达信息给玩家,还应当做到不干扰游戏流程。本文将探讨如何使用Unity的协程(Coroutine)配合队列(Queue)数据结构来构建一个高效且可扩展的Tips弹窗模块。 技术模块介绍 1. Unity协程(Coroutines) 协程是Unity中的一种特殊函数类型,允许异步操作的实现

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光 一,前言二,资源包内容三,免费获取资源包 一,前言 在创意的世界里,每一个细节都能决定一个项目的独特魅力。今天,要向大家介绍一款令人惊艳的粒子效果包 ——Super Confetti FX。 二,资源包内容 💥充满活力与动态,是 Super Confetti FX 最显著的标签。它宛如一位

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(4)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)-CSDN博客  这节就是真正的存储数据了   理清一下思路: 1.存储路径并检查 //2进制文件类存储private static string Data_Binary_Pa

Unity Adressables 使用说明(一)概述

使用 Adressables 组织管理 Asset Addressables 包基于 Unity 的 AssetBundles 系统,并提供了一个用户界面来管理您的 AssetBundles。当您使一个资源可寻址(Addressable)时,您可以使用该资源的地址从任何地方加载它。无论资源是在本地应用程序中可用还是存储在远程内容分发网络上,Addressable 系统都会定位并返回该资源。 您

Unity Adressables 使用说明(六)加载(Load) Addressable Assets

【概述】Load Addressable Assets Addressables类提供了加载 Addressable assets 的方法。你可以一次加载一个资源或批量加载资源。为了识别要加载的资源,你需要向加载方法传递一个键或键列表。键可以是以下对象之一: Address:包含你分配给资源的地址的字符串。Label:包含分配给一个或多个资源的标签的字符串。AssetReference Obj

在Unity环境中使用UTF-8编码

为什么要讨论这个问题         为了避免乱码和更好的跨平台         我刚开始开发时是使用VS开发,Unity自身默认使用UTF-8 without BOM格式,但是在Unity中创建一个脚本,使用VS打开,VS自身默认使用GB2312(它应该是对应了你电脑的window版本默认选取了国标编码,或者是因为一些其他的原因)读取脚本,默认是看不到在VS中的编码格式,下面我介绍一种简单快

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(2) (*****生成数据结构类的方式特别有趣****)-CSDN博客 做完了数据结构类,该做一个存储类了,也就是生成一个字典类(只是声明)  实现和上一节的数据结构类的方式大同小异,所

【Unity小技巧】URP管线遮挡高亮效果

前言 在URP渲染管线环境下实现物体遮挡高亮显示效果,效果如下: Unity URP遮挡高亮 实现步骤 创建层级,为需要显示高亮效果的物体添加层级,比如Player 创建一个材质球,也就是高亮效果显示的材质球找到Universal Renderer Data Assets 4.在Assets上添加两个Render Objects组件 第一个做如下三处设置 指定遮挡层级指

【Unity面经】实习篇:面试官常问的一百个面试题

👨‍💻个人主页:@元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 专栏交流🧧🟥Unity100个实战基础✨🎁🟦 Unity100个精华一记✨🎁🟩 Unity50个demo案例教程✨🎁🟨 Unity100个精华细节BUG✨🎁🟨 Unity100个面试题✨🎁 文章