🧱 Unity UI 核心类解析:Graphic
类详解
一、什么是 Graphic?
在 Unity 的 UI 系统中,Graphic
是一个抽象基类,继承自 UIBehaviour
并实现了 ICanvasElement
接口。它是所有可以被绘制到屏幕上的 UI 元素的基础类。
✅ UI 控件(如按钮、文字、图片等),都直接或间接继承自
Graphic
。
二、核心作用与职责
Graphic
类主要负责以下几件事:
职责 | 功能 |
---|---|
🎨 视觉渲染 | 控制颜色、材质、纹理、网格生成等 |
📐 布局更新 | 响应 RectTransform 变化,参与布局系统 |
🔦 射线检测 | 控制是否响应点击/触摸事件 |
🔄 脏标记机制 | 优化性能,只在必要时才重新绘制 |
⚙️ 生命周期管理 | OnEnable / OnDisable / OnDestroy 等回调处理 |
三、关键属性详解
1. 默认材质与纹理
protected static Material s_DefaultUI = null;
protected static Texture2D s_WhiteTexture = null;
public static Material defaultGraphicMaterial
{
get
{
if (s_DefaultUI == null)
s_DefaultUI = Canvas.GetDefaultCanvasMaterial();
return s_DefaultUI;
}
}
public virtual Texture mainTexture => s_WhiteTexture;
defaultGraphicMaterial
:获取默认的 UI 材质。mainTexture
:默认使用白色纹理,用于绘制基础图形。
2. 颜色控制
[SerializeField] private Color m_Color = Color.white;
public virtual Color color {
get => m_Color;
set {
SetPropertyUtility.SetColor(ref m_Color, value);
SetVerticesDirty();
}
}
- 支持序列化,方便在 Inspector 中修改。
- 修改颜色会触发顶点数据“脏”状态,表示需要重新绘制。
3. 材质控制
protected Material m_Material;
public virtual Material material
{
get
{
return (m_Material != null) ? m_Material : defaultMaterial;
}
set
{
if (m_Material == value)
return;
m_Material = value;
SetMaterialDirty();
}
}
- 支持设置自定义材质,否则使用默认材质。
- 修改材质也会触发“脏”状态。
4. 射线检测开关
private bool m_RaycastTarget = true;
public virtual bool raycastTarget
{
get
{
return m_RaycastTarget;
}
set
{
if (value != m_RaycastTarget)
{
if (m_RaycastTarget)
GraphicRegistry.UnregisterRaycastGraphicForCanvas(canvas, this);
m_RaycastTarget = value;
if (m_RaycastTarget && isActiveAndEnabled)
GraphicRegistry.RegisterRaycastGraphicForCanvas(canvas, this);
}
m_RaycastTargetCache = value;
}
}
- 控制该 UI 是否能接收点击事件。
- 修改此值时会注册或注销射线检测图层。
四、重建方法(Rebuild)
Rebuild
方法来自接口ICanvasElement
public interface ICanvasElement
{
/// <summary>
/// Rebuild the element for the given stage.
/// </summary>
/// <param name="executing">The current CanvasUpdate stage being rebuild.</param>
void Rebuild(CanvasUpdate executing);
}
Graphic
实现了ICanvasElement
接口
public abstract class Graphic : UIBehaviour, ICanvasElement
Rebuild
方法如下
/// <summary>
/// 在 PreRender(预渲染)阶段重建图形的几何体及其材质。
/// </summary>
/// <param name="update">当前 CanvasUpdate 渲染循环的阶段。</param>
/// <remarks>
/// 有关画布更新循环的更多详细信息,请参阅 CanvasUpdateRegistry。
/// </remarks>
public virtual void Rebuild(CanvasUpdate update)
{
if (canvasRenderer == null || canvasRenderer.cull)
return;
switch (update)
{
case CanvasUpdate.PreRender:
if (m_VertsDirty)
{
UpdateGeometry();
m_VertsDirty = false;
}
if (m_MaterialDirty)
{
UpdateMaterial();
m_MaterialDirty = false;
}
break;
}
}
CanvasUpdateRegistry
的部分源码
public class CanvasUpdateRegistry
{
private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();
protected CanvasUpdateRegistry()
{
//willRenderCanvases是一个Unity内置的静态事件,它在每一帧中:
//在所有 Canvas 被渲染之前立即触发。
Canvas.willRenderCanvases += PerformUpdate;
}
private void PerformUpdate()
{
CleanInvalidItems();
m_PerformingLayoutUpdate = true;
m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);
for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++)
{
for (int j = 0; j < m_LayoutRebuildQueue.Count; j++)
{
var rebuild = m_LayoutRebuildQueue[j];
try
{
if (ObjectValidForUpdate(rebuild))
//关键函数
rebuild.Rebuild((CanvasUpdate)i);
}
catch (Exception e)
{
Debug.LogException(e, rebuild.transform);
}
}
}
}
}
五、脏标记机制(SetAllDirty)
为了提高性能,Unity 使用了“脏标记”机制,只有当某些数据发生改变时才会触发重绘或重新计算。
1.SetAllDirty
public virtual void SetAllDirty()
{
if (m_SkipLayoutUpdate)
{
m_SkipLayoutUpdate = false;
}
else
{
SetLayoutDirty();
}
if (m_SkipMaterialUpdate)
{
m_SkipMaterialUpdate = false;
}
else
{
SetMaterialDirty();
}
SetVerticesDirty();
SetRaycastDirty();
}
该方法在下面几处会调用
protected override void OnTransformParentChanged()
protected override void OnEnable()
protected override void Reset()
protected override void OnDidApplyAnimationProperties()
#if UNITY_EDITOR
protected override void OnValidate()
#endif
📋 总结表格
方法名 | 调用时机 | 是否运行时调用 | 是否编辑器专用 | 主要用途 |
---|---|---|---|---|
OnTransformParentChanged |
GameObject 的父级发生变化时 | ✅ 是 | ❌ 否 | 处理层级变化,重新注册 Canvas |
OnEnable |
GameObject 被激活时 | ✅ 是 | ❌ 否 | 初始化资源、注册 UI、触发重建 |
Reset |
首次挂载脚本或点击 Reset 时 | ❌ 否 | ✅ 是 | 设置默认值,重置组件状态 |
OnDidApplyAnimationProperties |
动画属性应用后 | ✅ 是 | ❌ 否 | 处理动画驱动的 UI 更新 |
OnValidate |
Inspector 中字段修改后失去焦点时 | ❌ 否 | ✅ 是 | 数据验证、实时预览 UI 效果 |
2.SetLayoutDirty
public virtual void SetLayoutDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
if (m_OnDirtyLayoutCallback != null)
m_OnDirtyLayoutCallback();
}
以下方法会调用
public virtual void SetAllDirty()
protected override void OnRectTransformDimensionsChange()
作用:
- 告诉布局系统该元素的布局信息已改变,需要重新计算布局。
触发时机:
SetAllDirty()
OnRectTransformDimensionsChange()
:尺寸发生变化时
行为:
- 调用
LayoutRebuilder.MarkLayoutForRebuild(rectTransform)
,加入布局重建队列 - 触发回调
m_OnDirtyLayoutCallback
- 调用
3.SetMaterialDirty
public virtual void SetMaterialDirty()
{
if (!IsActive())
return;
m_MaterialDirty = true;
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
if (m_OnDirtyMaterialCallback != null)
m_OnDirtyMaterialCallback();
}
以下方法会调用
public virtual void SetAllDirty()
public virtual Material material
{
get
{
return (m_Material != null) ? m_Material : defaultMaterial;
}
set
{
if (m_Material == value)
return;
m_Material = value;
SetMaterialDirty();
}
}
作用:
- 表示材质发生了变化,需要重新提交材质到 Canvas 渲染系统。
触发时机:
SetAllDirty()
- 材质属性被修改(例如
material = new Material(...)
)
行为:
- 设置标志位
m_MaterialDirty = true
- 注册到
CanvasUpdateRegistry
,参与图形重建流程 - 触发回调
m_OnDirtyMaterialCallback
- 设置标志位
4.SetVerticesDirty
public virtual void SetVerticesDirty()
{
if (!IsActive())
return;
m_VertsDirty = true;
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
if (m_OnDirtyVertsCallback != null)
m_OnDirtyVertsCallback();
}
以下方法会调用
public virtual Color color {
get { return m_Color; } set {
if (SetPropertyUtility.SetColor(ref m_Color, value))
SetVerticesDirty();
}
}
public virtual void SetAllDirty()
protected override void OnRectTransformDimensionsChange()
作用:
- 表示顶点数据(网格)发生了变化,需要重新生成 UI 网格。
触发时机:
SetAllDirty()
- 颜色属性变更(如
color = Color.red
) - 尺寸变化(通过
OnRectTransformDimensionsChange()
) - 自定义顶点修改(如自定义 Graphic)
行为:
- 设置标志位
m_VertsDirty = true
- 注册到
CanvasUpdateRegistry
,等待重建 - 触发回调
m_OnDirtyVertsCallback
- 设置标志位
5.SetRaycastDirty
public void SetRaycastDirty()
{
if (m_RaycastTargetCache != m_RaycastTarget)
{
if (m_RaycastTarget && isActiveAndEnabled)
GraphicRegistry.RegisterRaycastGraphicForCanvas(canvas, this);
else if (!m_RaycastTarget)
GraphicRegistry.UnregisterRaycastGraphicForCanvas(canvas, this);
}
m_RaycastTargetCache = m_RaycastTarget;
}
以下方法会调用
public virtual void SetAllDirty()
作用:
- 表示是否允许响应射线检测的状态发生改变。
触发时机:
SetAllDirty()
- 修改
raycastTarget
属性时
行为:
- 如果启用射线检测,则注册到
GraphicRegistry
- 如果禁用,则从
GraphicRegistry
移除 - 更新缓存值
m_RaycastTargetCache
- 如果启用射线检测,则注册到
6.总结
生命周期方法 | 是否调用 SetAllDirty() |
是否调用其他 Dirty 方法 |
---|---|---|
OnEnable() |
✅ 是 | ❌ 否(但会触发 SetAllDirty) |
Reset() |
✅ 是 | ❌ 否 |
OnDidApplyAnimationProperties() |
✅ 是 | ❌ 否 |
OnTransformParentChanged() |
✅ 是 | ❌ 否 |
OnValidate() (编辑器) |
✅ 是 | ❌ 否 |
OnRectTransformDimensionsChange() |
❌ 否 | ✅ 是(调用 SetVerticesDirty() 和 SetLayoutDirty() ) |
六、几何体生成(UpdateGeometry)
这是 Graphic
类最核心的部分之一,它决定了 UI 如何绘制到屏幕上。
关键方法:
protected virtual void UpdateGeometry()
{
if (useLegacyMeshGeneration)
DoLegacyMeshGeneration();
else
DoMeshGeneration();
}
1. 新版网格生成(DoMeshGeneration)
使用 VertexHelper
构建顶点数据,并支持 IMeshModifier
修改网格。
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);
//获取当前对象是否有IMeshModifier接口,
//Text的描边和阴影都是通过它的ModifyMesh方法实现的
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);
}
2. 旧版网格生成(DoLegacyMeshGeneration)
直接操作 Mesh 对象,兼容旧版本逻辑。
private void DoLegacyMeshGeneration()
{
if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
{
#pragma warning disable 618
OnPopulateMesh(workerMesh);
#pragma warning restore 618
}
else
{
workerMesh.Clear();
}
var components = ListPool<Component>.Get();
GetComponents(typeof(IMeshModifier), components);
for (var i = 0; i < components.Count; i++)
{
#pragma warning disable 618
((IMeshModifier)components[i]).ModifyMesh(workerMesh);
#pragma warning restore 618
}
ListPool<Component>.Release(components);
canvasRenderer.SetMesh(workerMesh);
}
实现细节:OnPopulateMesh(VertexHelper vh)
protected virtual void OnPopulateMesh(VertexHelper vh)
{
var r = GetPixelAdjustedRect();
var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height);
Color32 color32 = color;
vh.Clear();
vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(0f, 0f));
vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(0f, 1f));
vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(1f, 1f));
vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(1f, 0f));
vh.AddTriangle(0, 1, 2);
vh.AddTriangle(2, 3, 0);
}
这是一个虚方法,提供了默认实现,子类可以重写实现自己的图形形状。
七、生命周期与事件响应
1. 启用与禁用
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();
}
protected override void OnDisable()
{
#if UNITY_EDITOR
GraphicRebuildTracker.UnTrackGraphic(this);
#endif
GraphicRegistry.DisableGraphicForCanvas(canvas, this);
CanvasUpdateRegistry.DisableCanvasElementForRebuild(this);
if (canvasRenderer != null)
canvasRenderer.Clear();
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
base.OnDisable();
}
- 启用时注册到 Canvas 和重建系统。
- 禁用时取消注册并清除缓存。
2. 销毁
protected override void OnDestroy()
{
#if UNITY_EDITOR
GraphicRebuildTracker.UnTrackGraphic(this);
#endif
GraphicRegistry.UnregisterGraphicForCanvas(canvas, this);
CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
if (m_CachedMesh)
Destroy(m_CachedMesh);
m_CachedMesh = null;
base.OnDestroy();
}
- 清除资源,防止内存泄漏。
3. RectTransform 变化
protected override void OnRectTransformDimensionsChange()
{
if (gameObject.activeInHierarchy)
{
// prevent double dirtying...
if (CanvasUpdateRegistry.IsRebuildingLayout())
SetVerticesDirty();
else
{
SetVerticesDirty();
SetLayoutDirty();
}
}
}
- 当尺寸变化时触发顶点或布局更新。
4. Canvas 层级变化
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())
{
GraphicRegistry.UnregisterGraphicForCanvas(currentCanvas, this);
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);
}
}
- 当父级 Canvas 发生变化时,重新注册自己。
八、性能优化技巧
避免频繁调用
SetAllDirty()
- 只在真正需要更新时才调用。
- 多个属性变更后统一调用一次即可。
利用
IMeshModifier
和IMaterialModifier
- 可以扩展图形外观,而无需继承
Graphic
。
- 可以扩展图形外观,而无需继承
合理使用
raycastTarget
- 不需要交互的 UI 应关闭射线检测,减少性能开销。
使用对象池(ListPool)
- 比如
ListPool<Component>.Get()
和ListPool<Component>.Release()
,避免频繁 GC。
- 比如
九、总结
Graphic
类是 Unity UI 系统中最底层、最重要的类之一。它不仅提供了基础的视觉表现能力,还通过高效的脏标记机制和灵活的扩展接口,使得能够轻松构建各种复杂的 UI 控件。