尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Unity UGUI动态弧形滑动条:从数学原理到自定义Mesh实现

Unity UGUI动态弧形滑动条:从数学原理到自定义Mesh实现 1. 项目概述为什么我们需要一个动态弧形滑动条在Unity的UGUI世界里标准滑动条Slider组件几乎是每个项目都离不开的基础控件。它横平竖直逻辑清晰处理音量调节、进度显示这类需求得心应手。但当你接到一个需求比如要做一个技能冷却的环形进度条、一个模拟仪表盘的弧形刻度盘或者一个更具视觉冲击力的弧形音量控制器时那个方方正正的矩形滑动条就显得力不从心了。这正是“动态弧形滑动条”要解决的问题——它不仅仅是把滑动轨迹从直线变成曲线更涉及到坐标转换、值域映射、动态填充与交互反馈等一系列UGUI进阶知识。我最近在一个科幻主题的HUD界面项目中就遇到了这个需求。客户想要一个环绕角色头像的弧形护盾值指示器滑动时要有流光效果跟随。市面上虽然能找到一些现成的插件或资源但要么功能过于庞杂要么定制性不够无法完美融入项目的美术风格和交互逻辑。于是我决定从零开始自己动手实现一个轻量、高效且高度可定制的动态弧形滑动条。这个过程不仅解决了实际问题更是一次对UGUI底层绘制、事件交互和数学应用的深度探索。如果你也厌倦了千篇一律的矩形UI想为你的项目注入一些独特的弧形动感那么这篇实战指南正是为你准备的。无论你是UGUI的熟练使用者还是希望深入理解UI原理的开发者都能从中获得可以直接“抄作业”的代码和思路。2. 核心设计思路与数学原理拆解2.1 从直线到曲线核心思路的转变一个标准的UGUI Slider其核心逻辑可以简化为在一个矩形区域内根据一个归一化的值0到1计算出一个填充图像的矩形范围fillAmount或者一个滑块手柄的锚点位置。它的坐标系是笛卡尔坐标系计算是线性的。而弧形滑动条我们需要将这套逻辑映射到一个圆形或扇形区域上。核心思路转变如下定义弧形轨迹我们需要一个圆心、半径、起始角度和结束角度来定义一段弧。值到角度的映射将归一化的值0到1线性映射到起始角度与结束角度之间的一个角度值。currentAngle Mathf.Lerp(startAngle, endAngle, normalizedValue)角度到位置的映射根据当前角度和半径利用三角函数计算出该角度在屏幕或局部坐标系下的位置。x center.x radius * Mathf.Cos(currentAngle * Mathf.Deg2Rad)y center.y radius * Mathf.Sin(currentAngle * Mathf.Deg2Rad)视觉呈现这个位置可以用来放置滑块手柄或者作为计算弧形填充如Image的fillAmount但需设置为Radial 360等类型或Mesh顶点生成的依据。注意Unity中很多数学函数如Mathf.Sin,Mathf.Cos使用的是弧度制而我们在Inspector面板设置角度时习惯用角度制。因此代码中频繁的Mathf.Deg2Rad角度转弧度和Mathf.Rad2Deg弧度转角度转换是必不可少的这也是新手最容易忽略导致显示错乱的坑点之一。2.2 方案选型Image填充 vs. 自定义Mesh实现弧形视觉表现主要有两种主流方案各有优劣方案一使用UGUI Image组件的Radial填充这是最快捷的方案。将Image的Image Type设置为FilledFill Method选择Radial 360、Radial 180等。然后通过脚本控制fillAmount属性并动态设置fillOrigin填充起点来匹配我们的起始角度。优点实现简单性能好合批友好直接利用UGUI的绘制。缺点灵活性极差。只能实现标准的360度、180度、90度等几种固定扇形无法实现任意角度的弧形例如从30度到270度。且难以实现复杂的边缘效果如渐变色、描边、多重光环。方案二自定义生成Mesh这是功能最强大的方案。通过脚本动态计算弧线上的一系列点生成一个扇形的网格Mesh然后使用CanvasRenderer或自定义Graphic组件进行绘制。优点绝对自由。可以定义任意角度、任意半径、任意宽度的弧形甚至可以生成非均匀厚度的环状体。可以方便地添加UV动画来实现流光效果。缺点实现复杂需要一定的图形学基础。如果每帧动态更新Mesh可能带来额外的CPU开销。方案三使用Sprite Mask与填充Sprite组合这是一种取巧的方案。准备一个完整的环形或扇形Sprite作为“底”再准备一个从圆心开始、可旋转的矩形长条Sprite作为“填充条”。将填充条作为底图的子物体并为其添加Sprite Mask组件底图设置Mask Interaction为Visible Inside Mask。通过旋转填充条来控制显示的区域。优点美术可控性强可以实现非常复杂的纹理效果。缺点需要额外的美术资源性能上多了一次Mask操作且旋转中心点的对齐需要精细调整。我的选择与理由 对于追求极致灵活性和动态效果的项目如我遇到的科幻HUD方案二自定义Mesh是首选。它虽然上手门槛高但一次开发终身受用可以衍生出各种复杂的弧形UI控件。本实战也将以方案二作为主线进行深度讲解因为它最能体现“从零到一”和“进阶”的内涵。同时我也会在关键节点指出如何适配方案一供不同需求的读者选择。3. 基础架构搭建创建弧形滑动条组件3.1 组件设计与继承关系我们不直接继承Slider因为它与矩形轨道的耦合太深。更好的方法是创建一个全新的组件比如叫ArcSlider它继承自Selectable以获得基本的可选择、可交互状态并实现IDragHandler、IPointerDownHandler等接口来处理拖拽和点击事件。using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; [RequireComponent(typeof(RectTransform))] public class ArcSlider : Selectable, IDragHandler, IPointerDownHandler { // 核心参数 [SerializeField] private float radius 100f; // 弧形半径 [SerializeField] private float startAngle -90f; // 起始角度度 [SerializeField] private float endAngle 90f; // 结束角度度 [SerializeField] private float currentValue 0.5f; // 当前值 [SerializeField] private float minValue 0f; [SerializeField] private float maxValue 1f; [SerializeField] private bool clockwise true; // 是否顺时针增长 // 手柄引用 [SerializeField] private RectTransform handleRect; // 填充图形引用可能是Image也可能是我们自定义的MeshRenderer [SerializeField] private Graphic fillGraphic; // 事件 public event System.Actionfloat OnValueChanged; protected override void Start() { base.Start(); UpdateVisuals(); } public void OnDrag(PointerEventData eventData) { // 处理拖拽逻辑 } public void OnPointerDown(PointerEventData eventData) { // 处理点击逻辑 } private void UpdateVisuals() { // 更新手柄位置和填充显示 } }3.2 关键参数解析与Inspector面板美化为了让组件在Inspector里更友好我们使用Range、Header、Tooltip等属性。[Header(Arc Settings)] [Tooltip(弧形的半径以像素为单位)] [SerializeField] private float radius 100f; [Range(-360, 360)] [SerializeField] private float startAngle -90f; [Range(-360, 360)] [SerializeField] private float endAngle 90f; [Header(Value Settings)] [Min(0)] [SerializeField] private float minValue 0f; [SerializeField] private float maxValue 1f; [Range(0, 1)] [SerializeField] private float currentValue 0.5f; [Header(References)] [SerializeField] private RectTransform handleRect; [SerializeField] private Graphic fillGraphic;这里有一个重要细节startAngle和endAngle的范围。在Unity的UI坐标系中0度通常指向正右方X轴正方向角度增加的方向是逆时针。这与我们常见的数学极坐标系一致。所以startAngle -90指向正下方endAngle 90指向正上方会得到一个从左下角到右下角的半圆。如果你想要一个从正右方开始、顺时针增长的弧形可以设置startAngle0endAngle-360并勾选clockwisetrue。理解这一点是避免弧形“画反了”的关键。4. 核心交互逻辑将屏幕点击映射为弧形上的值这是整个组件的“大脑”。当用户点击或拖拽UI区域时我们需要将屏幕坐标eventData.position转换到弧形所在的局部坐标系然后计算该点对应的角度最后将角度映射回一个归一化的值。4.1 坐标转换与角度计算private bool TryCalculateValueFromScreenPoint(Vector2 screenPoint, out float value) { value minValue; RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out Vector2 localPoint); // 计算相对于圆心的向量 Vector2 dir localPoint; // 假设圆心在RectTransform的中心 (pivot 0.5,0.5) // 如果圆心不在中心需要减去圆心偏移量 // dir localPoint - centerLocalPosition; // 计算角度弧度Mathf.Atan2(y, x) 返回 [-π, π] 之间的值 float angleRad Mathf.Atan2(dir.y, dir.x); float angleDeg angleRad * Mathf.Rad2Deg; // 转换为度 // 将角度规范到 [0, 360) 区间方便计算 if (angleDeg 0) angleDeg 360f; // 考虑弧形方向顺时针/逆时针和起始结束角将角度映射到 [0,1] 区间 // 这是最复杂的一步需要处理跨0度的情况 float normalizedAngle ConvertAngleToNormalized(angleDeg); value Mathf.Lerp(minValue, maxValue, normalizedAngle); return true; }4.2 处理弧形区间与方向ConvertAngleToNormalized函数是核心中的核心它需要处理四种主要情况顺时针/逆时针以及起始角是否大于结束角即弧形跨越0度/360度边界。private float ConvertAngleToNormalized(float worldAngle) { float sAngle startAngle; float eAngle endAngle; // 规范化角度到[0,360) sAngle (sAngle % 360 360) % 360; eAngle (eAngle % 360 360) % 360; worldAngle (worldAngle % 360 360) % 360; if (!clockwise) { // 逆时针交换起始和结束角逻辑上等同于反向处理 float temp sAngle; sAngle eAngle; eAngle temp; // 注意交换后增长方向的概念需要重新考虑这里简化处理为始终计算从sAngle到eAngle的顺时针夹角 // 更健壮的做法是统一转换到顺时针逻辑来计算。 } // 计算从sAngle到worldAngle的顺时针夹角 float clockwiseAngleFromStart 0; if (worldAngle sAngle) { clockwiseAngleFromStart worldAngle - sAngle; } else { clockwiseAngleFromStart 360 - sAngle worldAngle; } // 计算整个弧形的顺时针角度跨度 float totalClockwiseSpan 0; if (eAngle sAngle) { totalClockwiseSpan eAngle - sAngle; } else { totalClockwiseSpan 360 - sAngle eAngle; } // 避免除零 if (Mathf.Approximately(totalClockwiseSpan, 0)) return 0; float normalized clockwiseAngleFromStart / totalClockwiseSpan; return Mathf.Clamp01(normalized); }实操心得角度计算逻辑非常容易出错尤其是在处理360度边界和方向时。我的建议是先在纸上画出你的弧形标出startAngle, endAngle, 和增长方向然后推导出计算公式。编写完成后一定要用多个测试用例如点击弧形的起点、中点、终点、外侧进行验证并在UpdateVisuals中打印出计算出的角度和归一化值进行调试。4.3 整合到事件处理中在OnDrag和OnPointerDown中调用这个计算函数并设置值。public void OnDrag(PointerEventData eventData) { if (!IsActive() || !IsInteractable()) return; if (TryCalculateValueFromScreenPoint(eventData.position, out float newValue)) { SetValue(newValue); } } public void OnPointerDown(PointerEventData eventData) { if (!IsActive() || !IsInteractable()) return; // 点击时也直接跳转 if (TryCalculateValueFromScreenPoint(eventData.position, out float newValue)) { SetValue(newValue); } // 同时需要调用Select()来确保Selectable的状态正确并可能触发手柄的拖拽 base.OnPointerDown(eventData); } private void SetValue(float value, bool sendCallback true) { float oldValue currentValue; currentValue Mathf.Clamp(value, minValue, maxValue); UpdateVisuals(); if (sendCallback !Mathf.Approximately(oldValue, currentValue)) { OnValueChanged?.Invoke(currentValue); // 如果需要也可以触发UnityEvent } }5. 视觉呈现实现自定义弧形Mesh生成现在我们来解决“如何画出来”的问题。我们将创建一个ArcGraphic类继承自MaskableGraphic它负责根据ArcSlider的参数动态生成并显示弧形Mesh。5.1 ArcGraphic 基础结构using UnityEngine; using UnityEngine.UI; public class ArcGraphic : MaskableGraphic { public ArcSlider slider; // 关联的Slider [Min(2)] public int segments 60; // 弧形的细分段数越多越平滑 public float thickness 10f; // 弧形的厚度宽度 protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); if (slider null) return; // 获取弧形的参数 float normalizedValue Mathf.InverseLerp(slider.MinValue, slider.MaxValue, slider.CurrentValue); float startAngle slider.StartAngle; float endAngle slider.EndAngle; float radius slider.Radius; bool clockwise slider.Clockwise; // 计算当前值对应的角度 float currentAngle Mathf.Lerp(startAngle, endAngle, normalizedValue); // 生成从startAngle到currentAngle的弧形Mesh GenerateArcMesh(vh, startAngle, currentAngle, radius, thickness, segments, clockwise); } // 当Slider的值改变时需要刷新显示 public void Refresh() { SetVerticesDirty(); // 标记顶点数据脏了会触发OnPopulateMesh } private void GenerateArcMesh(VertexHelper vh, float fromAngle, float toAngle, float radius, float width, int seg, bool cw) { // 具体的网格生成逻辑 } }5.2 网格生成算法详解生成一个弧形带有厚度的弧形本质上是生成两个同心圆弧内弧和外弧然后用三角形将它们连接起来。我们按角度步进依次生成顶点。private void GenerateArcMesh(VertexHelper vh, float fromAngle, float toAngle, float radius, float width, int seg, bool cw) { // 确保角度和方向正确 if (!cw) { float temp fromAngle; fromAngle toAngle; toAngle temp; } // 确保fromAngle toAngle (顺时针方向) if (fromAngle toAngle) toAngle 360f; float angleStep (toAngle - fromAngle) / seg; int vertexCount seg * 2 2; // 内外弧各 seg1 个点 UIVertex vertex UIVertex.simpleVert; vertex.color color; // 使用Graphic的颜色 // 生成顶点 for (int i 0; i seg; i) { float currentAngle fromAngle angleStep * i; float rad currentAngle * Mathf.Deg2Rad; // 外弧顶点 Vector2 outerDir new Vector2(Mathf.Cos(rad), Mathf.Sin(rad)); vertex.position outerDir * radius; vh.AddVert(vertex); // 内弧顶点 Vector2 innerDir outerDir; // 方向相同 vertex.position innerDir * (radius - width); vh.AddVert(vertex); } // 生成三角形 for (int i 0; i seg; i) { int index i * 2; // 两个矩形四个顶点组成一个梯形带 vh.AddTriangle(index, index 1, index 2); // 三角形1 vh.AddTriangle(index 1, index 3, index 2); // 三角形2 } }关键点解析顶点顺序AddTriangle的三个顶点索引必须是顺时针顺序从屏幕正面看否则三角形背面剔除会导致不显示。我们这里按顺序添加保证了正确的朝向。局部坐标系我们生成的顶点位置是基于ArcGraphic游戏对象自身的局部坐标系通常是中心点为原点。ArcSlider的radius参数需要和这个坐标系匹配。性能考量segments分段数直接影响顶点和三角形数量。对于一个平滑的弧形60段通常足够。在移动端可以酌情减少到24或36段。5.3 与ArcSlider联动在ArcSlider的UpdateVisuals方法中除了更新手柄位置还要调用ArcGraphic的刷新。private void UpdateVisuals() { // 更新手柄位置 if (handleRect ! null) { float normalizedValue Mathf.InverseLerp(minValue, maxValue, currentValue); float angle Mathf.Lerp(startAngle, endAngle, normalizedValue) * Mathf.Deg2Rad; Vector2 dir new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)); handleRect.anchoredPosition dir * radius; } // 更新填充图形 if (fillGraphic ! null) { if (fillGraphic is ArcGraphic arcGraphic) { arcGraphic.Refresh(); } else if (fillGraphic is Image image image.type Image.Type.Filled) { // 如果是Radial Image则设置fillAmount和fillOrigin image.fillAmount normalizedValue; // 需要将startAngle转换为Image的FillOrigin枚举这里逻辑较复杂通常建议用自定义Mesh } } }6. 高级功能与效果扩展基础功能完成后我们可以为弧形滑动条添加更多“动态”和“高级”特性。6.1 动态插值与平滑移动直接让手柄和填充“跳”到目标位置会很生硬。我们可以添加一个平滑过渡的效果。[Header(Animation)] [SerializeField] private bool smoothTransition true; [SerializeField] private float transitionSpeed 10f; private float displayedValue; // 当前显示的值用于插值 private void Update() { if (!Application.isPlaying) return; if (smoothTransition !Mathf.Approximately(displayedValue, currentValue)) { displayedValue Mathf.Lerp(displayedValue, currentValue, Time.deltaTime * transitionSpeed); // 使用displayedValue来更新视觉而不是currentValue UpdateVisualsWithValue(displayedValue); } } private void SetValue(float value, bool sendCallback true) { // ... 原有的clamp和回调逻辑 ... if (!smoothTransition) { displayedValue currentValue; UpdateVisuals(); } // 如果smoothTransition为truedisplayedValue会在Update中逐渐逼近currentValue }6.2 添加刻度与标签在弧形上等间距地绘制刻度线和数值标签常用于仪表盘。[System.Serializable] public class TickMark { public RectTransform tickRectPrefab; // 刻度线的预制体 public RectTransform labelRectPrefab; // 标签的预制体 public int count 10; // 刻度数量 public float tickLength 5f; } public TickMark tickMarkSettings; private void GenerateTickMarks() { if (tickMarkSettings null || tickMarkSettings.tickRectPrefab null) return; // 清理旧的刻度 foreach (Transform child in tickMarkContainer) { Destroy(child.gameObject); } for (int i 0; i tickMarkSettings.count; i) { float t i / (float)tickMarkSettings.count; float angle Mathf.Lerp(startAngle, endAngle, t); float value Mathf.Lerp(minValue, maxValue, t); // 实例化刻度 RectTransform tick Instantiate(tickMarkSettings.tickRectPrefab, tickMarkContainer); tick.anchoredPosition CalculatePositionOnArc(angle, radius); // 让刻度指向圆心或切线方向需要旋转 tick.localEulerAngles new Vector3(0, 0, angle - 90); // -90度调整使图标指向圆心 // 实例化标签 if (tickMarkSettings.labelRectPrefab ! null) { RectTransform label Instantiate(tickMarkSettings.labelRectPrefab, tickMarkContainer); label.anchoredPosition CalculatePositionOnArc(angle, radius tickMarkSettings.tickLength 10); // 放在刻度外侧 // 设置标签文本 Text labelText label.GetComponentInChildrenText(); if (labelText ! null) labelText.text value.ToString(F1); } } }6.3 着色器与材质特效要让弧形“炫”起来离不开着色器。我们可以为ArcGraphic创建一个简单的Unlit Shader并添加一些属性。// 示例一个简单的弧形渐变着色器 Properties { _Color(Color, Color) (1,1,1,1) _MainTex(Main Texture, 2D) white {} _FillAmount(Fill Amount, Range(0,1)) 1.0 _StartAngle(Start Angle, Float) 0.0 _EndAngle(End Angle, Float) 360.0 [Toggle] _Clockwise(Clockwise, Float) 1 }在片元着色器中我们可以根据像素的角度和_FillAmount来决定是否裁剪clip或混合颜色从而实现动态填充效果。更进一步可以添加_FlowTex和_FlowSpeed来实现沿着弧形流动的流光效果这需要将顶点的UV坐标设置为沿着弧形的长度方向即V坐标与角度成正比。实操技巧对于复杂的动态效果如渐变、流光、扭曲建议将计算放在Shader中而不是在CPU端每帧修改顶点颜色或位置。这样性能开销最小效果也最流畅。ArcGraphic只需要将_FillAmount等参数通过MaterialPropertyBlock传递给材质即可。7. 性能优化与合批考量在UGUI中Draw Call的数量是影响UI性能的关键。我们的自定义ArcGraphic会如何影响合批呢Canvas层级确保ArcSlider及其所有子元素手柄、填充图、刻度都在同一个Canvas下并且中间没有其他Canvas组件隔开。不同Canvas下的元素无法合批。材质与纹理ArcGraphic使用的材质和纹理应尽可能与其他UI元素特别是同一Canvas下的其他ArcSlider保持一致。如果每个弧形滑动条都使用不同的材质实例就会打断合批。最佳实践创建一个共享的材质球所有ArcGraphic都引用它。通过MaterialPropertyBlock来设置每个实例独有的属性如_FillAmount,_Color。MaterialPropertyBlock不会打断合批在UGUI的默认渲染器下情况可能更复杂但这是通用图形优化原则。MaskableGraphic我们继承了MaskableGraphic这意味着它天然支持RectMask2D和Mask组件。但如果被Mask裁剪可能会产生额外的Draw Call。顶点数量控制segments参数在视觉平滑和性能之间取得平衡。对于小尺寸的弧形可以减少分段数。重建频率只有在值改变时才调用SetVerticesDirty()来重建网格。避免在Update中每帧都重建。踩坑记录在一次性能测试中我发现当有多个动态变化的ArcGraphic时Draw Call飙升。原因是每个ArcGraphic在OnPopulateMesh中直接修改了其material的属性如_FillAmount这导致Unity为每个修改了材质属性的Graphic创建了独立的材质实例从而破坏了合批。解决方案是改用CanvasRenderer.SetMaterial配合MaterialPropertyBlock或者确保所有需要动态修改的属性都通过顶点颜色或UV通道传递而不依赖材质属性。8. 常见问题与调试技巧实录在开发过程中我遇到了各种各样奇怪的问题这里记录下最典型的几个及其解决方法。问题1弧形显示不全或位置不对。排查首先检查ArcGraphic的RectTransform尺寸是否足够大能否容纳下半径为radius的弧形。其次检查ArcGraphic生成的顶点坐标是否在局部坐标系下而ArcSlider计算手柄位置时是否使用了相同的坐标系原点通常是RectTransform的中心。技巧在OnPopulateMesh中临时添加调试代码将计算出的顶点位置用Debug.DrawLine在场景视图中画出来可以直观看到生成的网格形状和位置。问题2点击交互区域不准特别是弧形很细的时候很难点中。原因UGUI的点击检测默认基于矩形区域RectTransform的矩形。我们的弧形可能只占这个矩形的一小部分。解决方案扩大点击区域使用一个透明的Image作为背景覆盖整个弧形可能的活动范围。自定义点击检测重写IsRaycastLocationValid方法。在ArcSlider或一个专门的ArcRaycastFilter组件中实现这个方法根据输入点是否在弧形“轨道”的某个距离阈值内来返回true或false。public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera) { // 复用TryCalculateValueFromScreenPoint的逻辑但只判断点是否在弧形附近 RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out Vector2 localPoint); float distanceToCenter localPoint.magnitude; // 判断距离是否在 [radius - thickness/2 - tolerance, radius thickness/2 tolerance] 范围内 return Mathf.Abs(distanceToCenter - radius) (thickness / 2 clickTolerance); }问题3手柄滑动时“跳”到错误位置或者弧形填充方向反了。原因几乎100%是角度计算逻辑有误尤其是没有处理好0/360度边界和顺时针/逆时针标志。调试方法在TryCalculateValueFromScreenPoint和ConvertAngleToNormalized函数中添加详细的Debug.Log打印出每一步计算出的原始角度、规范化后的角度、起始角、结束角、归一化值。然后用手柄在弧形上缓慢拖动观察控制台输出是否符合预期。问题4在ScrollRect或其它可拖动容器内弧形滑动条无法拖动。原因EventSystem的事件传递被父级的ScrollRect等组件拦截了。解决方案在ArcSlider的OnBeginDrag方法中需要实现IBeginDragHandler可以调用EventSystem.current.SetSelectedGameObject(gameObject)来强制获取焦点。更优雅的做法是确保ArcSlider的Drag Threshold设置得比ScrollRect的启动拖拽阈值稍小一点让ArcSlider能优先响应。问题5打包后尤其是WebGL弧形显示为粉色Missing Shader。原因自定义的Shader没有包含在打包的资源中。解决确保Shader所在的.shader文件或包含该Shader的材质球被项目中的某个Resources文件夹引用或者被添加到某个场景中或者通过Graphics Settings的Always Included Shaders列表包含进去。最后将这个组件投入实际项目前务必进行全面的测试包括极端值minmax、反向角度startAngle endAngle、快速拖拽、在屏幕不同位置、在不同分辨率下的表现等。一个健壮的UI组件必须能优雅地处理各种边界情况。
返回列表