Unity3D UGUI 源码学习 Graphic

UGUI中与显示图像相关的功能的承载者,由其衍生出的MaskableGraphic类是ImageText等类的父类。

Attribute

1
2
3
4
[DisallowMultipleComponent]
[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
[ExecuteInEditMode]

不支持同一个对象上挂载多个此组件,需要组件CanvasRendererRectTransform,会在编辑器模式下执行。

基类和实现的接口

1
2
3
4
5
6
public abstract class Graphic
: UIBehaviour,
ICanvasElement
{
//...
}

继承自UIBehaviourICanvasElement

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组件的方法,而LayoutCompleteGraphicUpdateComplete会在重建完成时按需调用。

GraphicRegistry

单例类,用于保存Canvas和与其关联的Graphic。并可以通过其获取到某个Canvas关联的所有Graphic。在其内部每个Canvas对应的所有Graphic是通过一种名为IndexedSet的数据结构来保存的。IndexedSet<T>,在UGUI其它的地方也会遇到,是一个有序集合,它支持以下特性:

  • 元素唯一
  • 快速随机删除
  • 快速在尾部插入(唯一)
  • 顺序访问

m_xxxDirty

Graphic通过脏标记(Dirty flag)来判断是否需要重建,以及哪一种信息需要重新绘制。脏标记m_VertsDirtym_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_VertsDirtym_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();
}

其中DoLegacyMeshGenerationDoMeshGeneration都是重新生成网格的方法,所执行的逻辑相似,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中的TweenRunnerColorTween来完成一个基于协程的改变颜色(或透明度)的动画。如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);

}

REFERENCE

https://bitbucket.org/Unity-Technologies/ui

http://blog.csdn.net/ecidevilin/article/details/52548747