一、简介
Graphic主要是用来控制图片的显示,设置mesh的顶点和uv数据,提供给VertexHelper和CanvasRenderer进行mesh绘制。所以有时候制作一些角色头顶的血条使用动态创建mesh的方法进行绘制。
Graphic是一个抽象类,是MaskableGraphic(可遮罩图像)的基类,而MaskableGraphic是RawImage、Image和Text的基类。
二、功能
1、添加的特性
[DisallowMultipleComponent]//一个对象只能挂载一个Graphic类组件,即使是继承Graphic也是不行
[RequireComponent(typeof(CanvasRenderer))]//需要CanvasRender组件,用来绘制mesh
[RequireComponent(typeof(RectTransform))]
[ExecuteInEditMode]//编辑器模式下也运行[ExecuteAlways]类似
Unity支持通过[ExecuteInEditMode]
或[ExecuteAlways]
两种参数使脚本在Play Mode以外的状态下被执行,[ExecuteEditMode]
支持脚本在Edit Mode下运行,[ExecuteAlways]
是在Unity2018.3及以后的版本新加入的功能,能够支持脚本一直运行。(ps:由于[ExecuteInEditMode]
并没有考虑Prefab Mode,严格意义上讲Prefab Mode也属于Edit Mode,所以这个功能会逐渐被Unity弃用,最后应该会被[ExecuteAlways]
所替代)
2、重写UIBehaviour的方法:
OnEnable()
1、调用了CacheCanvas方法,从父节点中获取Canvas列表,取得第一个激活并且可用的Canvas赋值给m_Canvas,把m_Canvas注册到GraphicRegistry中。这个队列可以用到raycaster射线点击中获取所有被点击的对象;
2、设置s_WhiteTexture(对应属性MainTexture,用来绘制图形的纹理);
3、SetAllDirty(分别设置Layout布局、Vertices顶点和Material材质为Dirty)。
protected override void OnEnable () {
base.OnEnable ();
CacheCanvas ();
GraphicRegistry.RegisterGraphicForCanvas (canvas, this);
#if UNITY_EDITOR
GraphicRebuildTracker.TrackGraphic (this);
#endif
if (s_WhiteTexture == null)
s_WhiteTexture = Texture2D.whiteTexture;
SetAllDirty ();
}
OnDisable()
从GraphicRegistry和CanvasUpdateRegistry移除注册并清理canvasRenderer,最后调用告知LayoutRebuilder需要重建布局(关于LayoutRebuilder,后续文章会详细讲解)。
///
/// Clear references.
///
protected override void OnDisable () {
#if UNITY_EDITOR
GraphicRebuildTracker.UnTrackGraphic (this);
#endif
GraphicRegistry.UnregisterGraphicForCanvas (canvas, this);
CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild (this);
if (canvasRenderer != null)
canvasRenderer.Clear ();
LayoutRebuilder.MarkLayoutForRebuild (rectTransform);
base.OnDisable ();
}
OnCanvasHierarchyChanged()
当父Canvas的状态发生改变时调用(当父Canvas被启用,禁用或嵌套Canvas的OverrideSorting被改变时,该函数被调用),把m_Canvas设置到currentCanvas,设置m_Canvas为空,调用CacheCanvas方法。从父节点中获取Canvas列表,取得第一个激活并且可用的Canvas赋值给m_Canvas,如果currentCanvas与m_Canvas不同,从GraphicRegistry解除注册,如果Graphic是激活并且可用时,重新注册到GraphicRegistry。
protected override void OnCanvasHierarchyChanged () {
// Use m_Cavas so we dont auto call CacheCanvas
Canvas currentCanvas = m_Canvas;
// Clear the cached canvas. Will be fetched below if active.
m_Canvas = null;
if (!IsActive ())
return;
CacheCanvas ();
if (currentCanvas != m_Canvas) {
GraphicRegistry.UnregisterGraphicForCanvas (currentCanvas, this);
// Only register if we are active and enabled as OnCanvasHierarchyChanged can get called
// during object destruction and we dont want to register ourself and then become null.
if (IsActive ())
GraphicRegistry.RegisterGraphicForCanvas (canvas, this);
}
}
OnRectTransformDimensionsChange方法(当RectTransform尺寸发生变化时),如果在重建中,只设置SetVerticesDirty,否则调用SetVerticesDirty和SetLayoutDirty。【功能简单,不在附带代码介绍】
OnBeforeTransformParentChanged方法,从GraphicRegistry解除注册,并通知LayoutRebuilder重建Layout。
OnTransformParentChanged方法,设置m_Canvas为空,调用CacheCanvas方法。从父节点中获取Canvas列表,取得第一个激活并且可用的Canvas赋值给m_Canvas,把m_Canvas注册到GraphicRegistry中,最后SetAllDirty(分别设置Layout布局、Vertices顶点和Material材质为Dirty)。
OnDidApplyAnimationProperties方法,调用SetAllDirty(分别设置Layout布局、Vertices顶点和Material材质为Dirty)。
3、实现ICanvasElement的方法
Rebuild方法
在Canvas渲染前被调用,在这个方法里会调用UpdateGeometry和UpdateMaterial更新顶点和材质。
UpdateGeometry方法
调用DoMeshGeneration方法,如果rectTransform不为空,且宽高都大于0,调用OnPopulateMesh,实际上只是把顶点和三角形信息保存到了s_VertexHelper里。然后获取所有的IMeshModifier类型的组件,(IMeshModifier是一个接口,需要依据顶点信息的组件继承自它,例如Shadow就间接继承自它),调用IMeshModifier的ModifyMesh方法,修改Mesh信息。最后将s_VertexHelper里修改后的信息赋值给workerMesh,并将workerMesh设置给canvasRenderer。
UpdateMaterial方法
更新canvasRenderer的材质和纹理。在给canvasRenderer设置材质时,会遍历所有IMaterialModifier类型的组件,调用IMaterialModifier.GetModifiedMaterial方法,用于重建图像时,获取修改后的Material,来实现遮罩效果。
OnPopulateMesh方法
在CanvasUpdateRegistry布局重建和图形重建时,会调用重建序列中的Graphic的Rebuild方法,Rebuild方法会调用OnPopulateMesh方法,为CanvasRenderer的Mesh提供了顶点位置、顶点颜色、UV和三角形信息。OnPopulateMesh方法把顶点数据和三角形信息保存到VertexHelper中【这个方法,会在RawImage,Image和Text中被重写,用来绘制图片和文字】。然后获取IMeshModifier类型的组件(IMeshModifier是一个接口,需要依据顶点信息的组件继承自它,例如Shadow就间接继承自它),调用它们的ModifyMesh方法,修改Mesh信息。最后将s_VertexHelper里修改后的信息赋值给workerMesh,并将workerMesh设置给canvasRenderer。
//构建graphic的UI元素数据信息
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.Get ();
GetComponents (typeof (IMeshModifier), components);
for (var i = 0; i < components.Count; i++)
((IMeshModifier) components[i]).ModifyMesh (s_VertexHelper);
ListPool.Release (components);
s_VertexHelper.FillMesh (workerMesh);
canvasRenderer.SetMesh (workerMesh);
}
CrossFadeColor方法
ColorTween是渐变效果的方法,通过m_ColorTweenRunner启动一个协程来运行,m_ColorTweenRunner是在Graphic构造函数里创建的,是TweenRunner
Raycast方法
通过ICanvasRaycastFilter(Image继承了这个接口)的IsRaycastLocationValid方法,判断射线在该组件是否有效。这个方法在GraphicRaycaster的Raycast方法里也被使用到,用于筛选出被射线 照射到的Graphic。GraphicRaycaster继承自BaseRaycaster,输入模块通过Raycast来获取被影响的对象。
///
/// When a GraphicRaycaster is raycasting into the scene it does two things. First it filters the elements using their RectTransform rect. Then it uses this Raycast function to determine the elements hit by the raycast.
///
/// Screen point being tested
/// Camera that is being used for the testing.
/// True if the provided point is a valid location for GraphicRaycaster raycasts.
public virtual bool Raycast (Vector2 sp, Camera eventCamera) {
if (!isActiveAndEnabled)
return false;
var t = transform;
var components = ListPool.Get ();
bool ignoreParentGroups = false;
bool continueTraversal = true;
while (t != null) {
t.GetComponents (components);
for (var i = 0; i < components.Count; i++) {
var canvas = components[i] as Canvas;
if (canvas != null && canvas.overrideSorting)
continueTraversal = false;
var filter = components[i] as ICanvasRaycastFilter;
if (filter == null)
continue;
var raycastValid = true;
var group = components[i] as CanvasGroup;
if (group != null) {
if (ignoreParentGroups == false && group.ignoreParentGroups) {
ignoreParentGroups = true;
raycastValid = filter.IsRaycastLocationValid (sp, eventCamera);
} else if (!ignoreParentGroups)
raycastValid = filter.IsRaycastLocationValid (sp, eventCamera);
} else {
raycastValid = filter.IsRaycastLocationValid (sp, eventCamera);
}
if (!raycastValid) {
ListPool.Release (components);
return false;
}
}
t = continueTraversal ? t.parent : null;
}
ListPool.Release (components);
return true;
}