UGUI中与显示图像相关的功能的承载者,由其衍生出的MaskableGraphic
类是Image
、Text
等类的父类。
Attribute
1 2 3 4
| [DisallowMultipleComponent] [RequireComponent(typeof(CanvasRenderer))] [RequireComponent(typeof(RectTransform))] [ExecuteInEditMode]
|
不支持同一个对象上挂载多个此组件,需要组件CanvasRenderer
和RectTransform
,会在编辑器模式下执行。
基类和实现的接口
1 2 3 4 5 6
| public abstract class Graphic : UIBehaviour, ICanvasElement { //... }
|
继承自UIBehaviour
和ICanvasElement
:
UIBehaviour
UIBehaviour
是所有UI组件的抽象基类,提供了接收UnityEngine或UnityEditor的事件的接口,部分接口如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| //...
protected virtual void Awake() {}
protected virtual void OnEnable() {}
protected virtual void Start() {}
protected virtual void OnDisable() {}
protected virtual void OnDestroy() {}
//...
protected virtual void OnRectTransformDimensionsChange() {}
protected virtual void OnBeforeTransformParentChanged() {}
protected virtual void OnTransformParentChanged() {}
protected virtual void OnDidApplyAnimationProperties() {}
protected virtual void OnCanvasGroupChanged() {}
protected virtual void OnCanvasHierarchyChanged() {}
//...
|
在OnEnable()
和OnDisable()
以及涉及到父节点或Canvas变化的方法时,会调用GraphicRegistry
注册或注销的方法,将自己注册至对应的Canvas
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| protected override void OnEnable() { //... GraphicRegistry.RegisterGraphicForCanvas(canvas, this); //... }
protected override void OnBeforeTransformParentChanged() { GraphicRegistry.UnregisterGraphicForCanvas(canvas, this); //... }
protected override void OnTransformParentChanged() { //... GraphicRegistry.RegisterGraphicForCanvas(canvas, this); //... }
protected override void OnDisable() { //... LayoutRebuilder.MarkLayoutForRebuild(rectTransform); //... }
protected override void OnCanvasHierarchyChanged() { //... if (currentCanvas != m_Canvas) { GraphicRegistry.UnregisterGraphicForCanvas(currentCanvas, this);
if (IsActive()) GraphicRegistry.RegisterGraphicForCanvas(canvas, this); } }
|
ICanvasElement
ICanvasElement
提供了Canvas
对其管理的元素的更新事件的接口,Canvas
对于UI组件的更新是由CanvasUpdateRegistry
来管理的,稍后介绍。只有实现了ICanvasElement
接口的类才可以通过CanvasUpdateRegistry
注册更新事件。ICanvasElement
的部分接口如下:
1 2 3 4 5 6 7 8 9 10
| //...
void Rebuild(CanvasUpdate executing);
//...
void LayoutComplete(); void GraphicUpdateComplete();
//...
|
其中Rebuild
是当Canvas上的元素需要更新时,由CanvasUpdateRegistry
调用的用于重建UI组件的方法,而LayoutComplete
和GraphicUpdateComplete
会在重建完成时按需调用。
GraphicRegistry
单例类,用于保存Canvas
和与其关联的Graphic
。并可以通过其获取到某个Canvas
关联的所有Graphic
。在其内部每个Canvas
对应的所有Graphic
是通过一种名为IndexedSet
的数据结构来保存的。IndexedSet<T>
,在UGUI其它的地方也会遇到,是一个有序集合,它支持以下特性:
- 元素唯一
- 快速随机删除
- 快速在尾部插入(唯一)
- 顺序访问
m_xxxDirty
Graphic
通过脏标记(Dirty flag)来判断是否需要重建,以及哪一种信息需要重新绘制。脏标记m_VertsDirty
和m_MaterialDirty
(其实还有一个布局相关的脏标记)会在响应指定的事件时置为true
,例如以下的事件回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| protected override void OnRectTransformDimensionsChange() { //... if (CanvasUpdateRegistry.IsRebuildingLayout()) SetVerticesDirty(); else { SetVerticesDirty(); SetLayoutDirty(); } }
protected override void OnTransformParentChanged() { //... SetAllDirty(); }
protected override void OnEnable() { //... SetAllDirty(); }
|
CanvasUpdateRegistry
CanvasUpdateRegistry
是一个单例,Canvas
和各UI组件沟通的桥梁,UI组件通过其来向Canvas
注册更新事件。
在Graphic
的一些设置脏标记的方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public virtual void SetVerticesDirty() { //... CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this); //... }
public virtual void SetMaterialDirty() { //... CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this); //... }
|
会调用RegisterCanvasElementForGraphicRebuild
来注册更新的回调方法,然后在CanvasUpdateRegistry.PerformUpdate()
中调用ICanvasElement.Rebuild(...)
。
Rebuild()
Graphic
类的重建方法,根据传入的CanvasUpdate
的枚举值来更新,实际上对于Graphic
类,只响应CanvasUpdate.PreRender
序列的重建。并且根据自身的脏标记m_VertsDirty
或m_MaterialDirty
,判断更新几何或者材质。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public virtual void Rebuild(CanvasUpdate update) { //... switch (update) { case CanvasUpdate.PreRender: if (m_VertsDirty) { UpdateGeometry(); m_VertsDirty = false; } if (m_MaterialDirty) { UpdateMaterial(); m_MaterialDirty = false; } break; } }
|
更新几何和更新材质的逻辑是Graphic
类承担显示图像职责最核心的内容,UpdateGeometry()
用于处理网格和顶点,UpdateMaterial()
用于更新材质和纹理。
UpdateGeometry()
1 2 3 4 5 6 7
| protected virtual void UpdateGeometry() { if (useLegacyMeshGeneration) DoLegacyMeshGeneration(); else DoMeshGeneration(); }
|
其中DoLegacyMeshGeneration
和DoMeshGeneration
都是重新生成网格的方法,所执行的逻辑相似,DoLegacyMeshGeneration
中直接操作workerMesh
,而后者通过VertexHelper
来完成网格生成与绘制的部分逻辑,最后通过VertexHelper.FillMesh(Mesh mesh)
来完成网格的生成。这里以DoMeshGeneration()
为例:
DoMeshGeneration()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private void DoMeshGeneration() { if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0) OnPopulateMesh(s_VertexHelper); else s_VertexHelper.Clear(); // clear the vertex helper so invalid graphics dont draw.
var components = ListPool<Component>.Get(); GetComponents(typeof(IMeshModifier), components);
for (var i = 0; i < components.Count; i++) ((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper);
ListPool<Component>.Release(components);
s_VertexHelper.FillMesh(workerMesh); canvasRenderer.SetMesh(workerMesh); }
|
在DoMeshGeneration()
中主要完成了以下的工作:
OnPopulateMesh
获取当前的RectTransform
的尺寸构建两个三角形网格(组成一个矩形),临时保存在s_VertexHelper
内。
ModifyMesh
获取所有实现IMeshModifier
接口的组件,调用其ModifyMesh(VertexHelper verts)
方法,来实现对网格的修改变化
SetMesh
将Graphic
此次重建的网格信息提交给canvasRenderer
;
UpdateMaterial()
除了更新几何之外,材质信息通过UpdateMaterial()
来更新,内容很简单,重设材质和纹理:
1 2 3 4 5 6 7 8 9
| protected virtual void UpdateMaterial() { if (!IsActive()) return;
canvasRenderer.materialCount = 1; canvasRenderer.SetMaterial(materialForRendering, 0); canvasRenderer.SetTexture(mainTexture); }
|
以上就是Graphic
实现显示图像功能的核心逻辑代码,除了这些之外,还有两个很重要的方法CrossFadeColor()
和CrossFadeAlpha()
,它使用UGUI中的TweenRunner
和ColorTween
来完成一个基于协程的改变颜色(或透明度)的动画。如CrossFadeColor()
:
CrossFadeColor()
1 2 3 4 5 6 7 8
| public virtual void CrossFadeColor(Color targetColor, float duration, bool ignoreTimeScale, bool useAlpha, bool useRGB) { //... var colorTween = new ColorTween {duration = duration, startColor = canvasRenderer.GetColor(), targetColor = targetColor}; //... m_ColorTweenRunner.StartTween(colorTween);
}
|
本系列其它文章详见Unity3D UGUI 源码学习
REFERENCE
https://bitbucket.org/Unity-Technologies/ui
http://blog.csdn.net/ecidevilin/article/details/52548747