当前位置: 首页 > news >正文

南通网站建打造自己的网站

南通网站建,打造自己的网站,无锡网站建设818gx,汽车音响网站建设UGUI源码剖析(第十五章):Slider的运行时逻辑与编辑器实现 在之前的章节中,我们已经深入了UGUI众多核心组件的运行时源码。然而,一个完整的Unity组件,通常由两部分构成:定义其在游戏世界中行为的…

UGUI源码剖析(第十五章):Slider的运行时逻辑与编辑器实现

在之前的章节中,我们已经深入了UGUI众多核心组件的运行时源码。然而,一个完整的Unity组件,通常由两部分构成:定义其在游戏世界中行为的运行时代码,以及定义其在Inspector面板中如何被配置和显示的编辑器代码。Slider组件,正是这两者精妙结合的典范。

本章,我们将同时解剖Slider.cs和SliderEditor.cs,来看一个滑块是如何实现的。

1. 数值的设定与约束

Slider的核心,是围绕一个浮点数m_Value展开的。源码中设计了一套严谨的机制,来确保这个值的有效性变更通知

1.1 核心属性与值范围

  • m_MinValue & m_MaxValue:定义了value的合法范围。
  • m_WholeNumbers:一个布尔开关,用于决定value是否应该被强制约束为整数。

1.2 核心方法:Set(float input, bool sendCallback = true)
这是Slider内部所有值变更的唯一入口。无论是用户通过value属性赋值,还是通过拖拽操作,最终都会调用这个方法。

protected virtual void Set(float input, bool sendCallback = true)
{// 1. 约束输入值float newValue = ClampValue(input);// 2. 检查值是否真正发生变化if (m_Value == newValue)return;m_Value = newValue;// 3. 更新视觉表现UpdateVisuals();if (sendCallback){// 4. 触发回调事件m_OnValueChanged.Invoke(newValue);}
}
  • ClampValue(input): 在这个辅助方法中,input会被Mathf.Clamp(input, minValue, maxValue)约束在最大最小值之间,并且如果wholeNumbers为true,还会被Mathf.Round()取整。这保证了m_Value永远不会超出合法范围。
  • 变更检查: if (m_Value == newValue) return; 这一行是至关重要的性能优化。它避免了在值未发生实际变化时,执行不必要的视觉更新和事件回调。
  • 职责分离: Set方法清晰地定义了值变更后的三大后续操作:约束(Clamp)、更新视觉(UpdateVisuals)、和通知逻辑(Invoke)

1.3 normalizedValue:归一化的“翻译官”
Slider还提供了一个normalizedValue属性,它的值永远在0到1之间。

public float normalizedValue
{get { return Mathf.InverseLerp(minValue, maxValue, value); }set { this.value = Mathf.Lerp(minValue, maxValue, value); }
}

normalizedValue扮演了一个转换的角色。get访问器使用Mathf.InverseLerp将value从[minValue, maxValue]的范围,转换到[0, 1]的范围。set访问器则使用Mathf.Lerp进行反向翻译。这为开发者提供了一个不关心具体最大最小值,只关心百分比的、更便捷的控制方式。

2. UpdateVisuals的布局

当Slider的值发生变化后,其Fill(填充区域)和Handle(滑块)的位置或尺寸也必须随之更新。这个过程,由核心方法UpdateVisuals()负责。

private void UpdateVisuals()
{// ...m_Tracker.Clear(); // 清空之前的驱动记录// --- 更新填充区域 (Fill Rect) ---if (m_FillContainerRect != null){m_Tracker.Add(this, m_FillRect, DrivenTransformProperties.Anchors);Vector2 anchorMin = Vector2.zero;Vector2 anchorMax = Vector2.one;if (m_FillImage != null && m_FillImage.type == Image.Type.Filled){// 方式一:如果Fill Image是Filled类型,则直接驱动其fillAmountm_FillImage.fillAmount = normalizedValue;}else{// 方式二:驱动Fill Rect的锚点,实现拉伸效果if (reverseValue)anchorMin[(int)axis] = 1 - normalizedValue;elseanchorMax[(int)axis] = normalizedValue;}m_FillRect.anchorMin = anchorMin;m_FillRect.anchorMax = anchorMax;}// --- 更新滑块 (Handle Rect) ---if (m_HandleContainerRect != null){m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors);Vector2 anchorMin = Vector2.zero;Vector2 anchorMax = Vector2.one;// 驱动Handle Rect的锚点,使其锚点重合于一个点,并定位到对应位置anchorMin[(int)axis] = anchorMax[(int)axis] = (reverseValue ? (1 - normalizedValue) : normalizedValue);m_HandleRect.anchorMin = anchorMin;m_HandleRect.anchorMax = anchorMax;}
}

DrivenRectTransformTracker的应用:Slider组件通过m_Tracker.Add,将自己注册为m_FillRect和m_HandleRect这两个子对象RectTransform属性的驱动者(Driver)。这使得Fill和Handle的锚点在Inspector中会变为灰色不可编辑,确保了它们的布局完全由Slider的value来控制。

两种视觉更新模式

  1. 对于Fill区域:它优先检查Fill上的Image组件是否为Filled类型。如果是,它会选择一种最高效的方式——直接更新fillAmount属性,将顶点计算的压力完全交给Image组件。如果不是,它才会采用第二种方式。
  2. 对于Fill(非Filled模式)和Handle:它通过动态地修改子对象的anchorMin和anchorMax来实现视觉更新。
    • Fill的拉伸:它将Fill的一个锚边(如anchorMax.x)设置为normalizedValue,另一边保持不变(如anchorMin.x=0),从而让Fill的矩形,根据value的百分比,在其父容器(Fill Area)中进行拉伸。
    • Handle的定位:它将Handle的anchorMin和anchorMax都设置为normalizedValue,让其锚点重合为一个点,这个点的位置,正好就是value在父容器(Handle Slide Area)中对应的百分比位置。

3. 从拖拽到数值的转换

Slider通过实现IDragHandler和IInitializePotentialDragHandler等事件接口,来将用户的屏幕空间拖拽操作,“翻译”为Slider逻辑空间中的value变化。

// Slider.cs
public virtual void OnDrag(PointerEventData eventData)
{if (!MayDrag(eventData)) return;UpdateDrag(eventData, eventData.pressEventCamera);
}void UpdateDrag(PointerEventData eventData, Camera cam)
{RectTransform clickRect = m_HandleContainerRect ?? m_FillContainerRect;if (clickRect != null && ...){Vector2 localCursor;// 1. 将屏幕坐标转换为Handle容器的本地坐标if (RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out localCursor)){localCursor -= clickRect.rect.position;// 2. 根据本地坐标,计算出0-1的归一化值float val = Mathf.Clamp01(localCursor[(int)axis] / clickRect.rect.size[(int)axis]);// 3. 将归一化值,设置给normalizedValue属性normalizedValue = (reverseValue ? 1f - val : val);}}
}public virtual void OnPointerDown(PointerEventData eventData)
{// ...// 如果直接点击在滑动条背景上,而非Handle上,则直接跳到该点if (/*... not clicking on handle ...*/){UpdateDrag(eventData, eventData.pressEventCamera);}
}
  • 坐标系转换: UpdateDrag方法的核心,是RectTransformUtility.ScreenPointToLocalPointInRectangle这个“翻译”函数。它负责将屏幕空间的鼠标/触摸坐标,转换为Handle或Fill容器的本地2D坐标
  • 归一化计算: 得到本地坐标后,通过除以容器在对应轴向上的尺寸,就得到了一个0-1之间的归一化值val。
  • 赋值与触发: 最后,将这个归一化值赋给normalizedValue属性。normalizedValue的set访问器,会自动将其转换为value,并调用核心的Set()方法,从而触发视觉更新onValueChanged事件回调,完成整个交互的闭环。

4. 编辑器:SliderEditor.cs的实现剖析

SliderEditor.cs继承自SelectableEditor,它的职责,是为Slider提供一个比默认Inspector更智能、更安全、更友好的配置界面。

4.1 核心职责一:提供更丰富的交互控件

标准的Inspector只会为float类型的m_Value字段,提供一个简单的浮点数输入框。SliderEditor则通过EditorGUILayout.Slider,提供了一个**真正的“滑块”**来编辑这个值。

// SliderEditor.cs
public override void OnInspectorGUI()
{// ...// 使用EditorGUILayout.Slider来绘制m_Value// 它的左右边界,直接取自m_MinValue和m_MaxValue的当前值EditorGUILayout.Slider(m_Value, m_MinValue.floatValue, m_MaxValue.floatValue);// ...
}

这不仅让编辑体验更直观,更重要的是,它将Value的编辑,与其范围MinValue和MaxValue在视觉上直接关联了起来,为开发者提供了即时的上下文。

4.2 核心职责二:保证数据的有效性与联动

SliderEditor花费了大量的代码,来处理各个属性之间的依赖关系和约束,防止开发者设置出无效的数据。

  • Min/Max值的约束:

    // SliderEditor.cs
    float newMin = EditorGUILayout.FloatField("Min Value", m_MinValue.floatValue);
    if (EditorGUI.EndChangeCheck())
    {// 确保新设置的Min值,永远不会大于Max值if (newMin < m_MaxValue.floatValue){m_MinValue.floatValue = newMin;// 如果Min值被抬高,超过了当前的Value,则自动将Value也抬高if (m_Value.floatValue < newMin)m_Value.floatValue = newMin;}
    }
    // (对MaxValue的检查逻辑类似)
    

    编辑器代码在这里扮演了一个**“数据验证器”**的角色。它在用户修改MinValue或MaxValue时,会立刻进行检查,确保MinValue <= Value <= MaxValue这个核心约束永远成立,避免了在运行时可能出现的逻辑错误。

  • wholeNumbers的联动:

    // SliderEditor.cs
    if (m_WholeNumbers.boolValue)m_Value.floatValue = Mathf.Round(m_Value.floatValue);
    

    当Whole Numbers被勾选时,编辑器会立即对m_Value进行取整,为用户提供即时的视觉反馈。

4.3 核心职责三:调用运行时方法,实现复杂行为

Slider的Direction属性,不仅仅是一个简单的枚举值,改变它,还需要对RectTransform进行复杂的翻转操作。这种逻辑,被封装在运行时的Slider.SetDirection方法中。SliderEditor则负责在Inspector中,为这个方法提供一个触发入口。

EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Direction);
if (EditorGUI.EndChangeCheck())
{// 当检测到Direction属性在Inspector中被修改时...Undo.RecordObjects(serializedObject.targetObjects, "Change Slider Direction");Slider.Direction direction = (Slider.Direction)m_Direction.enumValueIndex;foreach (var obj in serializedObject.targetObjects){Slider slider = obj as Slider;// 调用运行时的SetDirection方法,并传入true来触发布局翻转slider.SetDirection(direction, true);}
}

EditorGUI.BeginChangeCheck()和EditorGUI.EndChangeCheck()是Editor脚本中检测用户操作的标准模式。通过这个组合,编辑器可以在用户修改了Direction下拉菜单后,立刻获取到这个变化,并遍历所有被选中的Slider对象,调用其SetDirection方法,来执行只有运行时代码才能完成的复杂布局变换。这完美地展示了Editor代码与Runtime代码之间的协同工作。

4.4 核心职责四:提供智能的警告与提示

一个优秀的编辑器,还应该能预见开发者可能犯的错误,并给出提示。

  • EditorGUILayout.HelpBox(“Min Value and Max Value cannot be equal.”, …): 当Min和Max值相等时,给出警告。
  • EditorGUILayout.HelpBox(“The selected slider direction conflicts with navigation…”, …): 当Slider的方向(如水平)与Selectable的自动导航(也是水平)可能冲突时,给出警告。
  • EditorGUILayout.HelpBox(“Specify a RectTransform for the slider fill or …”, …): 当核心的Fill Rect或Handle Rect未被赋值时,给出引导性的提示。

这些极大地提升了组件的易用性,降低了新手的学习成本。

总结:

Slider组件的“内外兼修”,为我们提供了一个关于如何构建高质量Unity组件的最佳实践范例

  1. 运行时 (Slider.cs):负责定义组件的核心数据模型、内部逻辑、以及与引擎其他部分的交互接口。它的代码,追求的是性能、健壮性和逻辑的清晰性
  2. 编辑器时 (SliderEditor.cs):负责为组件的公共属性,提供一个安全、智能、且用户友好的配置界面。它的代码,追求的是易用性、数据验证和对运行时复杂行为的便捷调用

这两部分代码,如同一个硬币的两面,缺一不可。运行时代码是组件的“骨架”,决定了其能力的上限;而编辑器代码则是组件的“皮肤”和“引导员”,决定了这些能力能否被开发者轻松、正确地使用。

通过对Slider及其Editor的深入剖析,我们不仅理解了一个复杂复合组件的实现原理,更重要的是,我们学习到了一套完整的、覆盖了从底层逻辑到上层配置的**“组件工程化”**思想。

http://www.dtcms.com/a/417720.html

相关文章:

  • 南京网站排名优化费用组织建设包括哪些内容
  • 正品海外购网站有哪些网站开发人员分工
  • 小企业网站建设有什么用seo与网站建设
  • 百度服务器建设自己的网站网站开发语言排行
  • 企业网站 优秀在线crm客户管理系统
  • 管庄地区网站建设郑州seo优化外包
  • 网站开发公司深圳网站建设基础资料
  • 建筑网站案例网站建设维护实训总结
  • 眉山建行网站怎么登录百度app
  • 济南seo网站建设网站建设需求分析流程图
  • 哪个网站做译员好济南网站建设 刘彬彬
  • 建立的网站百度搜索不到网络营销的三种方式
  • 网站后台更新缓存失败制作板块的网站
  • 青羊区网站设计wordpress 编辑代码
  • 韩国大型门户网站沟通交流型网站广告如何做
  • 网站开发者ajax数据库网页网站设计
  • 做网站 绍兴手机网站 微信
  • 温州专业营销网站费用国际军事新闻最新消息视频
  • 南宁网站推广自己的网站怎么做淘宝联盟
  • 顺德网站建设淘宝客网站程序购米
  • jquery网站引导插件佛山做外贸网站特色
  • 电商网站用什么做的如何迁移wordpress
  • 网站软件下载app南宁cms建站系统
  • asp网站文章自动更新兼职网站编辑
  • 要想学做网站网站建设制作过程
  • 网站设计制作系统哪个好自做业务网站
  • python网站开发实践江西建设部网站
  • 建立企业网站的详细步骤大连网站策划
  • 专业制作网站哪家好wordpress登录的logo怎么换
  • 烟台市建设工程检测站网站红灰搭配网站模板