UGUI源码试探究 (一)
1. 前言
1.1 UGUI是什么
UGUI是Unity官方推出的UI系统, 集成了所见即所得的UI解决方案, 其功能丰富并且使用简单, 同时其部分源代码也是开放的
下载地址:https://bitbucket.org/Unity-Technologies/ui/src
2. 图形绘制
2.1 绘制基础
Mesh:
- 通俗的讲, Mesh是指模型的网格, 3D模型是由多边形拼接而成, 而一个复杂的多边形, 实际上是由多个三角形拼接而成. 所以一个3D模型的表面是由多个彼此相连的三角形构成. 三维空间中, 构成这些三角形的点以及三角形的边的集合就是Mesh. 如下图所示:
Vertex:
- 顶点, 组成Mesh的元素. 一个三角面有3个顶点, 如下图:
Triangle:
三角形, 通过指定顶点索引顺序来构成三角形
三角形的顶点顺序为顺时针
在下图中, 构成M和N的顶点顺序可以为: (A, B, C), (C, D A)
UV:
纹理贴图坐标
顶点属性中储存UV坐标, 指的是该点对应纹理的位置
UV的坐标轴值范围为(0, 1), 与纹理的大小比例无关
VertexHelper:
一个Unity提供的帮助类, 用于帮助生成Mesh
主要方法说明:
AddVert
: 向顶点缓冲流中加入一个顶点, 包括顶点的属性(位置, 颜色, UV坐标等)AddTriangle
: 向索引缓冲流中加入一个三角形, 参数为构成三角形的顶点的索引(注意顺序为顺时针)FillMesh
: 将VertexHelper中的缓存数据填入到指定的Mesh网格中
2.2 绘制基础实例
下面通过使用VertexHelper
和射线检测来实现可动态变化的简易Image
最终效果如图:
2.2.0 组件需求
如果我们在场景中创建一个Cube, 会发现Cube带了三个组件
MeshFilter
: 储存资源的Mesh, 可以由MeshRenderer在屏幕上渲染出来BoxCollider
: 碰撞器, 可以接受射线的检测MeshRenderer
: 从MeshFilter中提取中几何体并渲染出来
因此, 我们新建一个空的GameObject(在Canvas子目录中), 添加三个组件MeshFilter
, MeshRenderer
, MeshCollider
2.2.1 新建MyImage.cs
并挂载到GameObject上
主要步骤为:
脚本获得挂载的组件
mMyMeshFilter = GetComponent<MeshFilter>(); mMyMeshRenderer = GetComponent<MeshRenderer>(); mMyMeshCollider = GetComponent<MeshCollider>();
添加顶点信息
mVertexHelpers[0].AddVert(Vector3.down * imageSize, Color.white, new Vector2(0, 0)); mVertexHelpers[0].AddVert(Vector3.left * imageSize, Color.white, new Vector2(0, 1)); mVertexHelpers[0].AddVert(Vector3.right * imageSize, Color.white, new Vector2(1, 0));
添加三角形信息
mVertexHelpers[0].AddTriangle(0, 1, 2);
指定
MeshFilter.mesh
mMyMeshFilter.mesh = mMyMesh;
指定
MeshRenderer.material.shader
mMyMeshRenderer.material.shader = Shader.Find("Unlit/Texture");
指定
MeshRenderer.material.mainTexture
mMyMeshRenderer.material.mainTexture = texture2D;
指定
MeshCollider.sharedMesh
mMyMeshCollider.sharedMesh = mMyMesh;
添加射线检测及其响应方法
void Update() { var ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hitInfo; Physics.Raycast(ray, out hitInfo); if (hitInfo.collider != null) { if (Input.GetMouseButtonDown(0)) { Debug.Log("Click"); ChangeMyImage(); } } Debug.DrawRay(ray.origin, ray.direction * 50, Color.red, 0.0f, false); }
2.2.3 关于Shader.Find
的几个坑:
Shader.Find
可以在脚本中动态切换着色器,而不必保留对着色器的引用。name
是可以在任何材质的着色器弹出窗口中看到的名称,例如 “Standard”, “Unlit/Texture”, “Legacy Shaders/Diffuse” 等。如果没有引用它, Shader可能会出现找不到的情况! 因为在导出工程文件时, Unity默认情况下不会把工程里没有使用过的shader导出( unity不会分析你代码里使用了哪个内置shader. 为了防止出现”粉红色”材质, 可以采用以下几种方法之一:
从场景中使用的一些Material中引用它
将其添加到Edit->ProjectSettings-> Graphics->Always Included Shaders列表中
将shader或其引用( 例如Material )添加到“Resources”文件夹中
2.2.4 资源下载
2.3 绘制过程
利用调试来推导图形绘制过程
2.3.1 绘制过程实例
动态修改图片颜色, 通过点击按钮, 实现图片颜色的叠加, 效果如图
在自定义方法SetColor
设置断点, 点击Button时, 即可触发
F11
按下之后, 来到了Graphic
的Color属性, 如果满足SetColor
条件, 则设置顶点”dirty”
SetPropertyUtility.SetColor
方法的作用是判断是否是否需要刷新颜色, 可以防止重复绘制同一种颜色
public static bool SetColor(ref Color currentValue, Color newValue)
{
if (currentValue.r == newValue.r && currentValue.g == newValue.g && currentValue.b == newValue.b && currentValue.a == newValue.a)
return false;
currentValue = newValue;
return true;
}
将当前的组件注册到Graphic
的Canvas渲染队列当中
在CanvasUpdateRegistry.PerformUpdate
方法中使用该队列
那谁来调用PerformUpdate
方法呢?
Unity手册中关于Canvas.willRenderCanvases
的描述为:
- Event that is called just before Canvas rendering happens.
- This allows you to delay processing / updating of canvas based elements until just before they are rendered.
说明在Canvas被渲染之前, 会调用PerformUpdate
方法
即调用实现了ICanvasElememt
接口的类中的Rebuild
方法
在此处加上断点, 然后F11
,
来到了Graphic
的Rebuild
方法
UpdateGeometry
更新几何体
DoMeshGeneration
网格生成
OnPopulateMesh
利用VertexHelper
填充网格, 判断图片的类型
刷新了顶点的颜色!!!
结果出现啦!!
2.4 总结
图形绘制过程图为:
将PerformUpdate
注册到Canvas.willRenderCanvases
中, 如果UI发生变动, 该事件触发
PerformUpdate
事件从渲染队列中获取元素,分别调用Rebuild
方法Rebuild
方法中完成网格, 材质等的更新
当UI发生变动时, 将需要修改的元素添加到渲染队列中, 从而实现UI的更新