Unity 补完计划(四):UGUI-9:RectMask2D & MaskableGraphic
前言
在上一节的分析中,我们由 IMaterialModifier 这个接口引出了 UGUI 中的一块重要功能 —— 遮罩。也提到了 UGUI 中提供了两种遮罩的实现方式:Mask 和 RectMask2D。通过对 Mask 的分析我们知道,Mask 是在模板测试阶段通过修改 Stencil Buffer 的值来实现遮罩的。而 RectMask2D 在渲染管线中的位置更靠前,在裁剪测试阶段即可完成遮罩的效果。本节我们就来看看 RectMask2D 以及 MaskableGraphic 的实现。
宇宙万法的源头 —— CanvasUpdateRegistry
CanvasUpdateRegistry 在 UGUI 系统中的重要作用这个系列文章已经提到多次,正是该类监听了 Canvas.willRenderCanvases 事件,并调用 m_LayoutRebuildQueue 以及 m_GraphicRebuildQueue 中 ICanvasElement 的 Rebuild 方法来完成布局以及渲染的整个过程。
让我们重温一下 CanvasUpdateRegistry.Perform ...
Unity 补完计划(四):UGUI-8:IMeshModifier & IMaterialModifier
前言
在 上一篇 中我们提到了:Graphic 在渲染流程中分别使用到了 IMeshModifier 和 IMaterialModifier 接口来实现对于网格以及材质的修改,本节我们就来分析一下 UGUI 中实现了这些接口的组件分别都起到了什么作用。
IMeshModifier
在 UGUI 中,实现了 IMeshModifier 接口的组件间的关系如下图所示:
在 Graphic 中的使用
首先来看看 IMeshModifier 在 Graphic 中是如何使用的:
123456789101112131415161718private void DoMeshGeneration(){ if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0) OnPopulateMesh(s_VertexHelper); else s_VertexHelper.Clear(); ...
Unity 补完计划(四):UGUI-7:Graphic
Refs:
https://zhuanlan.zhihu.com/p/643275125
https://www.cnblogs.com/cancantrbl/p/16076748.html
https://docs.unity.cn/cn/2018.4/Manual/class-Canvas.html
前言
Graphic 算是 UGUI 中最重要的组件之一了,它是所有 UI 组件的基类与基础,负责实现 UI 的绘制功能。在本篇中,我们将详细讲解 Graphic 的实现。
前置知识
在学习 Graphic 源码之前,需要对 Canvas 及渲染流程有一定的了解。
Canvas
画布(canvas)是 UI 组件的容器,所有 UI 元素都必须放置在画布中。这就好比是电影幕布,所有的影像都在幕布上呈现。Canvas 组件通过渲染器将UI元素绘制到屏幕上。它使用层级结构来管理UI元素的显示顺序,可以通过设置UI元素的层级来控制它们的显示顺序。Canvas组件还可以设置渲染模式,包括屏幕空间、世界空间和摄像机空间等。
若场景中没有画布,在创建控件时会自动创建画布。一个默认的 Canvas ...
Unity 补完计划(四):UGUI-6:LayoutElement & LayoutGroup
前言
在上一篇中我们分析了 LayoutRebuilder 的实现,了解了一个 UI 元素是如何将自身注册到 LayoutSystem 中的。但 LayoutRebuilder 所做的事情仅仅是在 Rebuild 函数中调用 ILayoutElement 及 ILayoutController 接口来实现布局的更新,而真正的布局计算是由 LayoutElement 和 LayoutGroup 来完成的。
本篇将介绍 LayoutElement 和 LayoutGroup 的实现,关于这些布局组件的使用请参考 LayoutSystem
LayoutElement
LayoutElement 脚本用于控制 UI 元素的布局属性,包括最小宽高、首选宽高、弹性宽高等。通过添加该脚本,可以覆盖已有的布局属性,使 UI 的布局更加灵活。接下来让我们逐行分析 LayoutElement 的源码。
12345678910111213public class LayoutElement : UIBehaviour, ILayoutElement, ILayoutIgnorer{ [Ser ...
Unity 补完计划(四):UGUI-5:LayoutRebuilder
前言
在 CanvasUpdateRegistry 中,我们介绍了 UGUI 系统是如何通过 CanvasUpdateRegistry 来控制 UI 元素的更新的,其中提到了 “Canvas 的更新可以分为布局与渲染两个阶段”。在监听到 Canvas.willRenderCanvases 事件后,CanvasUpdateRegistry 会遍历所有需要更新的 UI 组件,并根据 CanvasUpdate 所代表的阶段来调用对应的 Rebuild 方法。
接下来就让我们来看看 Layout 阶段是如何更新的。
ILayoutElement
在 UnityEngine.UI\UI\Core\Layout\ILayoutElement.cs 中定义了一些 LayoutSystem 所使用到的接口,包括 ILayoutElement、ILayoutController、ILayoutGroup 等。它们分别定义了 LayoutElements 及 LayoutControllers 的基本属性与方法。
ILayoutElement
ILayoutElement 定义了一个可以被 LayoutC ...
Unity 补完计划(四):UGUI-4:LayoutSystem
Refs:
https://gwb.tencent.com/community/detail/117690
前言
原本是想继续上一篇的内容梳理 LayoutRebuilder 的源码,但发现布局系统的内容非常多且抽象,如果不结合实际的使用来分析很难理解透彻;且大部分的功能在日常使用 Unity 的过程中出现的频率都非常高,例如 HorizontalLayoutGroup、VerticalLayoutGroup、GridLayoutGroup 等等。在我们使用 UGUI 的过程中,经常会被这些布局组件的繁杂属性所困扰,因此这里打算先系统性地介绍一下布局系统的使用,之后再继续 LayoutRebuilder 的源码解析。
LayoutSystem 介绍
Auto Layout System 是基于 Rect Transform Layout System 之上的系统(关于 RectTransform 可以参考 Unity3D RectTransform使用详解:布局、属性、方法、设计),自动调整一个或多个的元素大小、位置、间格,又分为 Layout Controllers(父物件) 与 ...
Unity 补完计划(四):UGUI-3:CanvasUpdateRegistry
CanvasUpdateRegistry
CanvasUpdateRegistry是一个单例,它是 UGUI 与 Canvas 之间的中介,继承了 ICanvasElement 接口的组件都可以注册到它,它监听了 Canvas 即将渲染的事件,并调用已注册组件的 Rebuild 等方法。
因此,UGUI 中元素(CanvasElements)的渲染并不是各组件单独控制而是统一交由 CanvasUpdateRegistry 处理。
CanvasUpdate
在 CanvasUpdateRegistry.cs 文件的首部定义了一个枚举类 CanvasUpdate 用于标识 Canvas 更新过程的各个阶段。
总体而言,Canvas 的更新可以分为布局与渲染两个阶段。其中,Prelayout、Layout 与 PostLayout 皆为布局阶段中的子阶段;而 PreRender 与 LatePreRender 则是渲染阶段中的子阶段;一个 UI 元素的刷新过程就依照枚举类中标识的流程进行,后续也会通过该枚举值依次调用各阶段的处理函数。
123456789101112131415161718 ...
Unity 补完计划(四):UGUI-2:IndexedSet
IndexedSet
UGUI 中定义了一种特殊的容器,被称为 IndexedSet。该容器结合了 List 与 Dictionary
使得它具有以下的优点:
保证容器中元素的唯一性(类似于 Set)
快速的随机元素删除(使用了 swap 的技巧)
连续访问(由 List 保证的缓存友好特性)
但同时,这种容器也有一些缺点:
使用更多的内存(同时维护了一个 List 与一个 Dictionary)
顺序并不能一直保持(Swap 技巧的弊端)
序列化不友好(同时维护两种数据结构)
下面就来看一下该类的实现。
首先,该类被标记为 internal。这说明该容器仅有 UGUI 程序集内部能够使用;其次,该类是一个泛型类并继承了 IList<T>。
此外可以看出,类中同时维护了一个 List 与一个 Dictionary。
123456789101112131415161718192021internal class IndexedSet<T> : IList<T>{ //This is a container ...
Unity 补完计划(四):UGUI-1:EventSystem
EventSystem
Unity 的 UGUI 系统中的 EventSystem 是一个核心组件,用于管理和处理用户输入事件。它是所有 UI 交互的基础,负责检测和分发输入事件,如鼠标点击、触摸、键盘输入等。EventSystem 的主要功能包括:
管理所有的输入检测模块(InputModule)。 EventSystem 使用输入模块(Input Modules)来处理不同类型的输入并逐帧调用 Module 的执行函数 Process(),常见的输入模块包括 StandaloneInputModule(用于鼠标和键盘输入)和 TouchInputModule(用于触摸输入)。
调动射线捕捉模块(Raycasters),为 InputModule 提供结果(具体的触点所穿透的对象信息)。 EventSystem 使用射线检测(Raycasting)来确定用户输入的位置和目标。它会发射一条射线,从输入设备(如鼠标或触摸点)的位置出发,检测与之相交的 UI 元素,并将事件传递给 UI 元素。
事件监听及处理(ExecuteEvent)。 EventSystem 会捕获用户的输 ...
C++ 补完计划(七):RTTI
RTTI 简介
RTTI(Runtime Type Identification)是“运行时类型识别”的意思。C++引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型。但是现在RTTI的类型识别已经不限于此了,它还能通过typeid操作符识别出所有的基本类型的变量对应的类型。为什么会出现RTTI这一机制呢?这和C++语言本身有关系,C++是一门静态类型语言,其数据类型是在编译期就确定的,不能在运行时更改。然而由于面向对象程序设计中多态性的要求,C++中的指针或引用本身的类型,可能与它实际代表的类型并不一致,有时我们需要将一个多态指针转换为其实际指向对象的类型,就需要知道运行时的类型信息,这就有了运行时类型识别需求。和Java、C#等拥有反射机制的语言相比,C++要想获得运行时类型信息,只能通过RTTI机制,并且C++最终生成的代码是直接与机器相关的。
C++ 通过以下两个关键字提供 RTTI 功能:
typeid:该运算符返回其表达式或类型名的实际类型
dynamic_cast:该运算符将基类的指针或引用安全地转换为派生类类型的指针或引用(也 ...
程序的编译与运行(三):动态链接
参考:
《程序员的自我修养—链接、装载与库》
《深入理解计算机系统》
动态链接
在动态链接出现之前,可执行文件的生成都是使用静态链接的方式来生成。静态链接方式需要在程序运行前将所有的可重定位文件全部链接到一起,形成一个整体后才能加载到内存中运行,这种机制存在一些弊端:
空间浪费:程序在使用静态库的时候需要将使用到的公用库函数的目标文件全部链接到程序中,这就导致不同程序会存在相同库文件的多个副本,造成空间浪费;
模块更新困难:对于程序中任何参与链接的可重定位文件发生更新,整个程序都需要重新进行编译和发布。
为了解决静态链接的问题,动态链接不再从一开始就将程序的所有模块静态链接在一起,而是等到程序运行时才进行链接。
动态链接的实现
动态链接的基本思想是将程序中的部分独立模块作为动态共享库实现,在链接生成可执行文件时,动态库只提供重定位和符号信息,而不实际参与链接;等到程序运行时,由动态链接器将程序依赖的动态共享库加载到进程的虚拟地址空间中,形成一个完整的程序后执行。动态链接的基本实现过程示意如下:
动态链接的核心工作由动态链接器完成,在Linux平台下,使用的动态链接器一般为ld ...
程序的编译与运行(二):静态链接
参考:
《程序员的自我修养—链接、装载与库》
《深入理解计算机系统》
前言
现代编译器支持以分离编译的方式单独地编译所有的源文件,然后通过链接将所有模块组装成一个完整的应用。这种方式使得我们可以将一个复杂的应用程序分解成更小、更好管理的模块,并且可以独立地对这些模块进行修改和编译。当我们改变其中一个模块时,只需要对修改的模块重新编译,然后重新进行链接,而不必重新编译其它的模块。
静态链接
程序的编译可分为预处理、编译、汇编以及链接这四个主要阶段(见 C++ 编译过程 ),编译器在完成汇编阶段之后,所有的代码源文件已经都翻译成了二进制的目标文件,但是目标文件相互之间仍然是独立的,很多依赖信息还没有解决,包括:
对于每个目标文件,它可能会依赖其它目标文件或者库中的符号信息;
对于每个目标文件,其在内存中的运行地址信息此时也是不确定的,因此还无法直接加载到程序中运行。
这些问题都将由静态链接解决。静态链接的核心作用就是将编译生成的多个目标文件及其依赖的库进行链接,以生成最终的可执行文件。静态链接的基本示意如下:
静态链接过程
在 Linux 平台下,使用的静态链接器通常为 ld 工 ...
程序的编译与运行(一):ELF 文件格式
参考:
《程序员的自我修养—链接、装载与库》
《Linux二进制分析》
ELF 文件
ELF,全称 Executable and Linking Format,旨在为不同操作环境下提供一组通用的ABI(二进制接口),也是目前Unix和类Unix操作系统使用的标准二进制格式。ELF文件格式定义了可执行程序的静态文件格式,包括文件信息头、段及节等结构,并约定了程序在运行时,程序文件的内容是如何动态加载到内存中以及起始运行地址。
ELF 文件类型
ELF文件从字面的意义上来讲,描述的是可执行和链接格式,因此包括可执行文件、目标文件以及动态库都可以采用ELF文件格式进行存储。ELF标准中定义的ELF文件类型主要有以下四种:
ELF文件类型
类型标记
说明
可重定位文件
ET_REL
可重定位目标文件通常是还未被链接到可执行程序的一段位置独立的代码,如.o文件
可执行文件
ET_EXEC
即可运行的程序,是一个进程开始执行的入口,平时使用的shell、find等工具都属于此类
共享目标文件
ET_DYN
动态可链接目标文件,即共享库,会在程序运行时被装载并连接到程序的进 ...
C# 补完计划(三):string
参考:
C# 中的 String 究竟是个怎样的类型
浅谈C#字符串构建利器StringBuilder
string
.Net框架程序设计(修订版)中有这样一段描述:
String类型直接继承自 Object,这使得它成为一个引用类型,也就是说线程上的栈上不会驻留有任何字符串。
String类型对象直接派生自Object,所以String是引用类型,因此,String对象总是存在于堆上,永远不会跑到线程栈。
但String类型却又有值类型的特性,所以问到String究竟是值类型还是引用类型时,我们一般称为特殊的引用类型。
具体请看以下例子:
123456string str1 = "str1";string str2 = str1;str1 = "str3";Console.WriteLine(str1); // str3Console.WriteLine(str2); // str1
按普通引用类型的特性来理解以上代码,str2 应该指向 str1 的同一个内存地址,若此时修改 str1 的值,str2 应该也会发生变化,但修改 ...
内存管理杂谈
内核
什么是内核呢?
计算机是由各种外部硬件设备组成的,比如内存、cpu、硬盘等,如果每个应用都要和这些硬件设备对接通信协议,那这样太累了,所以这个中间人就由内核来负责,让内核作为应用连接硬件设备的桥梁,应用程序只需关心与内核交互,不用关心硬件的细节。
内核有哪些能力呢?
现代操作系统,内核一般会提供 4 个基本能力:
管理进程、线程,决定哪个进程、线程使用 CPU,也就是进程调度的能力;
管理内存,决定内存的分配和回收,也就是内存管理的能力;
管理硬件设备,为进程与硬件设备之间提供通信能力,也就是硬件通信能力;
提供系统调用,如果应用程序要运行更高权限运行的服务,那么就需要有系统调用,它是用户程序与操作系统之间的接口。
内核是怎么工作的?
内核具有很高的权限,可以控制 cpu、内存、硬盘等硬件,而应用程序具有的权限很小,因此大多数操作系统,把内存分成了两个区域:
内核空间,这个内存空间只有内核程序可以访问;
用户空间,这个内存空间专门给应用程序使用;
用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有内存空间。因此,当程序使用用户空间时,我们常 ...