Unity中的UGUI源码解析之图形对象(Graphic)(2)-ICanvasElement

在上一篇文章中, 我们对整个Graphic部分做了概述, 这篇文章我们介绍ICanvasElementCanvasUpdateRegistry.

ICanvasElement是一个接口(Interface). 抽象了能够在画布上显示的元素行为. 文件所在为: UnityEngine.UI/UI/Core/CanvasUpateRegistry.cs.

相关的辅助类有: enum CanvasUpdateclass CanvasUpdateRegistry.

在Unity中, 有以下元素实现了该接口:

即:

  • Toggle
  • LayoutRebuilder
  • ScrollRect
  • Graphic
  • InputField
  • Scrollbar
  • MaskableGraphic
  • Image
  • Slider
  • RawImage
  • Text

我们会在后面的文章慢慢介绍这些元素, 在本文中, 主要关注接口本身和其辅助类.

enum CanvasUpdate

CanvasUpdate是一个枚举, 本身很简单, 用于表明元素的刷新时机, 主要在重建(rebuild)和刷新(update)中使用.

/// <summary>
/// 在Canvas更新时调用的更新时机
/// </summary>
public enum CanvasUpdate
{/// <summary>/// 在布局之前调用/// </summary>Prelayout = 0,/// <summary>/// 在布局时调用/// </summary>Layout = 1,/// <summary>/// 在布局之后调用/// </summary>PostLayout = 2,/// <summary>/// 在渲染之前调用/// </summary>PreRender = 3,/// <summary>/// 稍后再渲染之前调用/// </summary>LatePreRender = 4,/// <summary>/// 最大值, 用于数组遍历上界/// </summary>MaxUpdateValue = 5
}

interface ICanvasElement

ICanvasElement抽象了在画布上显示的元素的基本行为, 用于元素在重建(rebuild)和刷新(update)的各个时机定义和触发特定的行为.

/// <summary>
/// 可以在画布上的元素行为接口
/// </summary>
public interface ICanvasElement
{/// <summary>/// 重新构建给定阶段的元素/// 可以将元素的更新延迟到画布刷新之前, 降低更新的消耗/// </summary>/// <param name="executing"></param>void Rebuild(CanvasUpdate executing);/// <summary>/// 相关联的transform/// </summary>Transform transform { get; }/// <summary>/// 此元素完成布局时的回调/// </summary>void LayoutComplete();/// <summary>/// 此元素完成Graphic重新构建时的回调/// </summary>void GraphicUpdateComplete();/// <summary>/// due to unity overriding null check/// we need this as something may not be null/// but may be destroyed/// 返回元素是否被销毁/// </summary>/// <returns></returns>bool IsDestroyed();
}

class CanvasUpdateRegistry

CanvasUpdateRegistry类是一个单例类, 主要负责驱动UGUI元素的刷新和重建, 是底层的Native代码(Canvas)和UGUI元素之间沟通的桥梁.

CanvasUpdateRegistry十分简单, 主要是维护了两个队列, 一个存放需要在布局更新时(比如位置发生变化时)刷新和重建的UGUI元素, 一个存放需要在图形更新时(也就是渲染内容发生变化时, 比如材质和顶点变化), 然后通过注册一个在画布渲染之前发送的事件, 照着布局更新到图形更新的顺序依次对两个队列进行刷新和重建.

刷新队列是各个UGUI元素自己在合适的时机, 调用CanvasUpdateRegistry单例进行注册和加入的, 在每次刷新之后会清空队列.

主要流程

主要的流程为:

  1. 在CanvasUpdateRegistry构造函数中注册画布渲染前事件: Canvas.willRenderCanvases += PerformUpdate;

  2. 各个UGUI元素在合适的时机注册布局更新队列(RegisterCanvasElementForLayoutRebuild)或者图形更新队列(RegisterCanvasElementForGraphicRebuild)

  3. 触发画布渲染前事件: PerformUpdate

  4. UI系统分析启动: UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);

  5. 清理无效的队列元素(比如那些不再存活的元素): CleanInvalidItems

  6. 标记布局更新中: m_PerformingLayoutUpdate = true;

  7. 布局队列元素排序: m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);

  8. 对布局队列中的有效元素(可能在某个时机中变得无效)分别进行各个时机的重建:

    • Prelayout(0), Layout(1), PostLayout(2);
    • int i = 0; i <= (int)CanvasUpdate.PostLayout; i++
    • ObjectValidForUpdate(rebuild)
    • rebuild.Rebuild((CanvasUpdate)i)
  9. 对布局队列中的元素分别进行布局完成的回调: m_LayoutRebuildQueue[i].LayoutComplete()

  10. 清空布局队列: m_LayoutRebuildQueue.Clear();

  11. 清理布局更新中标记: m_PerformingLayoutUpdate = false;

  12. 进行剔除(cull): ClipperRegistry.instance.Cull();

  13. 标记图形更新中: m_PerformingGraphicUpdate = true;

  14. 对图形队列中的有效元素(可能在某个时机中变得无效)分别进行各个时机的重建:

    • PreRender(3), LatePreRender(4)
    • var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++
    • ObjectValidForUpdate(element)
    • element.Rebuild((CanvasUpdate)i)
  15. 对图形队列中的元素分别进行图形更新完成的回调: m_GraphicRebuildQueue[i].GraphicUpdateComplete()

  16. 清空图形队列: m_GraphicRebuildQueue.Clear();

  17. 清理图形更新中标记: m_PerformingGraphicUpdate= false;

  18. UI系统分析结束: UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);

基本属性和字段

public class CanvasUpdateRegistry
{/// <summary>/// 单例/// </summary>private static CanvasUpdateRegistry s_Instance;/// <summary>/// 是否在更新布局(位置)/// </summary>private bool m_PerformingLayoutUpdate;/// <summary>/// 是否在更新图形(渲染)/// </summary>private bool m_PerformingGraphicUpdate;/// <summary>/// 需要在布局更新时重建的元素(位置)/// </summary>private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();/// <summary>/// 需要在图形更新时重建的元素(渲染)/// </summary>private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();/// <summary>/// 单例/// </summary>public static CanvasUpdateRegistry instance{get{if (s_Instance == null)s_Instance = new CanvasUpdateRegistry();return s_Instance;}}
}

注册元素和回调

// 构造函数中注册即将开始渲染画布时的回调
protected CanvasUpdateRegistry()
{Canvas.willRenderCanvases += PerformUpdate;
}/// <summary>
/// 注册图形元素重建
/// </summary>
/// <param name="element"></param>
public static void RegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{instance.InternalRegisterCanvasElementForGraphicRebuild(element);
}/// <summary>
/// 注册布局元素重建
/// </summary>
/// <param name="element"></param>
public static void RegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{instance.InternalRegisterCanvasElementForLayoutRebuild(element);
}public static void UnRegisterCanvasElementForRebuild(ICanvasElement element)
{instance.InternalUnRegisterCanvasElementForLayoutRebuild(element);instance.InternalUnRegisterCanvasElementForGraphicRebuild(element);
}

工具方法

/// <summary>
/// 是否正在进行布局更新
/// </summary>
/// <returns></returns>
public static bool IsRebuildingLayout()
{return instance.m_PerformingLayoutUpdate;
}/// <summary>
/// 是否正在进行图形更新
/// </summary>
/// <returns></returns>
public static bool IsRebuildingGraphics()
{return instance.m_PerformingGraphicUpdate;
}/// <summary>
/// 清理无效的元素
/// </summary>
private void CleanInvalidItems()
{// So MB's override the == operator for null equality, which checks// if they are destroyed. This is fine if you are looking at a concrete// mb, but in this case we are looking at a list of ICanvasElement// this won't forward the == operator to the MB, but just check if the// interface is null. IsDestroyed will return if the backend is destroyed.for (int i = m_LayoutRebuildQueue.Count - 1; i >= 0; --i){var item = m_LayoutRebuildQueue[i];if (item == null){m_LayoutRebuildQueue.RemoveAt(i);continue;}if (item.IsDestroyed()){m_LayoutRebuildQueue.RemoveAt(i);item.LayoutComplete();}}for (int i = m_GraphicRebuildQueue.Count - 1; i >= 0; --i){var item = m_GraphicRebuildQueue[i];if (item == null){m_GraphicRebuildQueue.RemoveAt(i);continue;}if (item.IsDestroyed()){m_GraphicRebuildQueue.RemoveAt(i);item.GraphicUpdateComplete();}}
}/// <summary>
/// 判断元素是否有效
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
private bool ObjectValidForUpdate(ICanvasElement element)
{var valid = element != null;var isUnityObject = element is Object;if (isUnityObject)valid = (element as Object) != null; //Here we make use of the overloaded UnityEngine.Object == null, that checks if the native object is alive.return valid;
}/// <summary>
/// 布局元素排序委托, 做成静态成员可以减少函数到委托的转换
/// </summary>
private static readonly Comparison<ICanvasElement> s_SortLayoutFunction = SortLayoutList;/// <summary>
/// 排序节点
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
private static int SortLayoutList(ICanvasElement x, ICanvasElement y)
{Transform t1 = x.transform;Transform t2 = y.transform;return ParentCount(t1) - ParentCount(t2);
}

执行更新主流程

/// <summary>
/// 执行更新主流程
/// </summary>
private void PerformUpdate()
{// UI系统分析启动UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);// 清理无效元素CleanInvalidItems();// 标记布局更新中m_PerformingLayoutUpdate = true;// 排序布局元素(根据父节点数量从小到大的排序)m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);// 在各个时机分别触发各个布局元素的重建(Prelayout, Layout, PostLayout)for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++){for (int j = 0; j < m_LayoutRebuildQueue.Count; j++){var rebuild = instance.m_LayoutRebuildQueue[j];try{// 只对有效的元素进行触发(可能在某个时机中会改变有效性)if (ObjectValidForUpdate(rebuild))rebuild.Rebuild((CanvasUpdate)i);}catch (Exception e){Debug.LogException(e, rebuild.transform);}}}// 触发所有布局元素布局完成的回调for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)m_LayoutRebuildQueue[i].LayoutComplete();// 清理布局元素队列instance.m_LayoutRebuildQueue.Clear();// 清理布局元素更新中的标记m_PerformingLayoutUpdate = false;// 布局元素完成更新后, 进行剔除操作(后面的文章单独介绍)ClipperRegistry.instance.Cull();// 标记图形元素更新中m_PerformingGraphicUpdate = true;// 在各个时机分别触发各个图形元素的重建(PreRender, LatePreRender)for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++){for (var k = 0; k < instance.m_GraphicRebuildQueue.Count; k++){try{var element = instance.m_GraphicRebuildQueue[k];// 只对有效的元素进行触发(可能在某个时机中会改变有效性)if (ObjectValidForUpdate(element))element.Rebuild((CanvasUpdate)i);}catch (Exception e){Debug.LogException(e, instance.m_GraphicRebuildQueue[k].transform);}}}// 触发所有图形元素更新完成的回调for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)m_GraphicRebuildQueue[i].GraphicUpdateComplete();// 清空图形队列instance.m_GraphicRebuildQueue.Clear();// 清理图形更新中标记m_PerformingGraphicUpdate = false;// UI系统分析结束UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);
}

举例子: Graphic的实现

/// <summary>
/// 重建接口的实现
/// </summary>
/// <param name="update"></param>
public virtual void Rebuild(CanvasUpdate update)
{if (canvasRenderer.cull)   return;// 在渲染之前更新所需内容(顶点或者材质)switch (update){case CanvasUpdate.PreRender:if (m_VertsDirty){UpdateGeometry();m_VertsDirty = false;}if (m_MaterialDirty){UpdateMaterial();m_MaterialDirty = false;}break;}
}

总结

今天介绍了ICanvasElement和其相关的工具类, ICanvasElement抽象了可以在画布上显示的元素的行为, 主要是重建和一些回调, 而CanvasUpdateRegistry则是使用ICanvasElement的主体, 根据注册画布渲染前事件来触发那些需要进行重建的元素(比如元素自己知道某些情况下需要进行重建, 就要把自己注册到CanvasUpdateRegistry)的接口.

本质上CanvasUpdateRegistry的存在是为了统一UGUI元素的刷新, 一方面避免刷新时机的不统一, 一方面也能降低刷新频率. Unity在很多地方都使用了类似的机制, 值得我们借鉴.

好了, 今天的内容就是这些, 希望对大家有所帮助.

Unity中的UGUI源码解析之图形对象(Graphic)(2)-ICanvasElement相关推荐

  1. Unity中的UGUI源码解析之事件系统(2)-EventSystem组件

    Unity中的UGUI源码解析之事件系统(2)-EventSystem组件 今天介绍我们的第一个主角: EventSystem. EventSystem在整个事件系统中处于中心, 相当于事件系统的管理 ...

  2. Unity中的UGUI源码解析之事件系统(8)-输入模块(中)

    Unity中的UGUI源码解析之事件系统(8)-输入模块(中) 接上一篇文章, 继续介绍输入模块. Unity中主要处理的是指针事件, 也就是在2d平面上跟踪指针设备输入坐标的的事件, 这一类事件有鼠 ...

  3. Unity中的UGUI源码解析之事件系统(9)-输入模块(下)

    Unity中的UGUI源码解析之事件系统(9)-输入模块(下) 接上一篇文章, 继续介绍输入模块. StandaloneInputModule类是上一篇文章介绍的抽象类PointerInputModu ...

  4. Unity中的UGUI源码解析之事件系统(6)-RayCaster(下)

    Unity中的UGUI源码解析之事件系统(6)-RayCaster(下) 接上一篇文章, 继续介绍投射器. GraphicRaycaster GraphicRaycaster继承于BaseRaycas ...

  5. Unity中的UGUI源码解析之事件系统(3)-EventData

    Unity中的UGUI源码解析之事件系统(3)-EventData 为了在事件系统中传递数据, Unity提供了EventData相关的类来封装这一类数据. 了解这些结构有助于我们对后面模块的学习. ...

  6. elementui组件_elementui 中 loading 组件源码解析(续)

    上一篇我们说了elementui如何将loading组件添加到 Vue 实例上,具体内容见上期 elementui 中 loading 组件源码解析. 这一篇我们开始讲讲自定义指令 自定义指令 关于自 ...

  7. elementui table某一列是否显示_elementui 中 loading 组件源码解析(续)

    上一篇我们说了elementui如何将loading组件添加到 Vue 实例上,具体内容见上期 elementui 中 loading 组件源码解析. 这一篇我们开始讲讲自定义指令 自定义指令 关于自 ...

  8. UGUI源码解析——ContentSizeFitter

    一:前言 ContentSizeFitter继承自ILayoutSelfController,是调整对象自适应的组件,ContentSizeFitter不改变子物体的大小和位置,而是根据子物体(ILa ...

  9. UGUI源码解析——LayoutElement

    一:前言 继承了ILayoutElement和ILayoutIgnorer接口,作为布局元素组件 挂载了Layout Element组件的对象,布局并不会生效,它是受到实现了布局组的控制(Horizo ...

最新文章

  1. tcp时间戳 引起的网站不能访问
  2. 【错误记录】GitHub 提交代码失败、获取代码失败、连接超时、权限错误、ping 请求连接超时 ( 查找域名对应 IP | 设置 host 文件 )
  3. c语言中一百以内相乘的积,一百以内的加减乘除法游戏....
  4. dokuwiki 的管理和使用
  5. ITK:创建具有相关类ID的样本列表
  6. plsql developer 64位版本
  7. 新媒体视频导演 - 美学基础 todo
  8. wamp5.5.12安装re dis扩展
  9. Linux whoami命令、Linux su命令、Linux w命令
  10. Ubuntu 中的编程语言(中)
  11. 虚拟机备份克隆导致SQL SERVER 出现IO错误案例
  12. Tensorflow学习笔记 (用 tf.data 加载图片)
  13. 单片机74LS164C语言例子,74ls164单片机编程汇总(跑马灯/驱动数码管)
  14. 计算机PS考试都考哪些,计算机专业ps考试题(考查课)(10页)-原创力文档
  15. thinkphp使用163/126邮箱发送
  16. 计算机图形表示的原理
  17. Android中应用分包的方法(Apk Splits)
  18. SIPP测试使用指导
  19. Objective-C 理解之方括号[ ]的使用
  20. 前端框架vue04~~vue.cli脚手架的基本使用

热门文章

  1. a标签点击一次后,就不能再点击了,同时还把它的颜色变成灰色?用js实现
  2. 语义分割的标签在原图像上显示
  3. 面向SecDevOps七种武器 1
  4. get到java7自己不知道的知识
  5. Redis的介绍和使用(NoSQL、Jedis)
  6. 学习记录_DNS域名相关
  7. 【python自动化测试】以pytest为底层的全栈自动化测试框架开发
  8. python测试用例管理模块_Pytest编写测试用例(二)
  9. vue js打印并去掉页眉和页脚
  10. 【笔记】浮动属性float的应用07——浮动可缩放的首字下沉(所有步骤组合在一起)