Unity UI 核心类解析之Graphic

🧱 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_EDITORprotected override void OnValidate()
#endif

📋 总结表格

方法名调用时机是否运行时调用是否编辑器专用主要用途
OnTransformParentChangedGameObject 的父级发生变化时✅ 是❌ 否处理层级变化,重新注册 Canvas
OnEnableGameObject 被激活时✅ 是❌ 否初始化资源、注册 UI、触发重建
Reset首次挂载脚本或点击 Reset 时❌ 否✅ 是设置默认值,重置组件状态
OnDidApplyAnimationProperties动画属性应用后✅ 是❌ 否处理动画驱动的 UI 更新
OnValidateInspector 中字段修改后失去焦点时❌ 否✅ 是数据验证、实时预览 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();elseDoMeshGeneration();
}
1. 新版网格生成(DoMeshGeneration)

使用 VertexHelper 构建顶点数据,并支持 IMeshModifier 修改网格。

private void DoMeshGeneration()
{if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)OnPopulateMesh(s_VertexHelper);elses_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 618OnPopulateMesh(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_EDITORGraphicRebuildTracker.TrackGraphic(this);
#endifif (s_WhiteTexture == null)s_WhiteTexture = Texture2D.whiteTexture;SetAllDirty();
}protected override void OnDisable()
{
#if UNITY_EDITORGraphicRebuildTracker.UnTrackGraphic(this);
#endifGraphicRegistry.DisableGraphicForCanvas(canvas, this);CanvasUpdateRegistry.DisableCanvasElementForRebuild(this);if (canvasRenderer != null)canvasRenderer.Clear();LayoutRebuilder.MarkLayoutForRebuild(rectTransform);base.OnDisable();
}
  • 启用时注册到 Canvas 和重建系统。
  • 禁用时取消注册并清除缓存。

2. 销毁

protected override void OnDestroy()
{
#if UNITY_EDITORGraphicRebuildTracker.UnTrackGraphic(this);
#endifGraphicRegistry.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 CacheCanvasCanvas 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 发生变化时,重新注册自己。

八、性能优化技巧

  1. 避免频繁调用 SetAllDirty()

    • 只在真正需要更新时才调用。
    • 多个属性变更后统一调用一次即可。
  2. 利用 IMeshModifierIMaterialModifier

    • 可以扩展图形外观,而无需继承 Graphic
  3. 合理使用 raycastTarget

    • 不需要交互的 UI 应关闭射线检测,减少性能开销。
  4. 使用对象池(ListPool)

    • 比如 ListPool<Component>.Get()ListPool<Component>.Release(),避免频繁 GC。

九、总结

Graphic 类是 Unity UI 系统中最底层、最重要的类之一。它不仅提供了基础的视觉表现能力,还通过高效的脏标记机制和灵活的扩展接口,使得能够轻松构建各种复杂的 UI 控件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/84290.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/84290.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Elasticsearch】文档迁移(Reindex)

文档迁移 1.为什么要进行 reindex 操作2.Reindex 操作的本质3.实际案例3.1 同集群索引之间的全量数据迁移3.2 同集群索引之间基于特定条件的数据迁移3.2.1 源索引设置检索条件3.2.2 基于 script 脚本的索引迁移3.2.3 基于预处理管道的数据迁移 3.3 不同集群之间的索引迁移3.4 查…

WordPress 区块版面配置指南

WordPress 的区块编辑器(Gutenberg)提供了灵活的版面配置选项&#xff0c;以下是主要配置方法&#xff1a; 基本区块布局 添加区块&#xff1a;点击””按钮或按”/”键快速插入区块 常用内容区块&#xff1a; 段落(Paragraph) 标题(Heading) 图像(Image) 画廊(Gallery)…

TensorFlow基础之理解张量

2.理解张量 张量&#xff08;Tensors&#xff09;介绍 张量是物理和工程领域的基础数学结构。但是过去张量很少在计算机科学里使用。它与离散数学和逻辑学有更多的联系。随着机器学习的出现&#xff0c;这种状态开始显著的改变&#xff0c;成为连续向量的计算基础。现代机器学…

Flume 安装与配置步骤

1.解压 tar -zxvf apache-flume-1.9.0-bin.tar.gz 2.配置环境变量 vim /etc/profile export FLUME_HOME/home/wang/soft/flume/apache-flume-1.9.0-bin export PATH$PATH:$FLUME_HOME/bin source /etc/profile 3.创建必要的目录 mkdir -p $FLUME_HOME/conf 4.创建 Flume 配置文…

还原线上 WebView 异常:手机端APP远程调试

前端调试总被理解为开发阶段的事&#xff0c;但在实际项目中&#xff0c;真正困难的调试往往发生在产品上线之后。用户反馈“看不到内容”、“一直转圈”、“点了没反应”&#xff0c;而开发环境无法复现&#xff0c;测试机也正常运行&#xff0c;这时怎么定位、验证和解决问题…

102页满分PPT | 汽车设备制造业企业信息化业务解决方案智能制造汽车黑灯工厂解决方案

这份文档是一份汽车设备制造业企业信息化业务解决方案&#xff0c;详细阐述了企业从生产到销售的全流程信息化建设。针对企业目前手工管理为主、信息化程度低、数据追溯困难等问题&#xff0c;提出了建立统一信息化平台的目标&#xff0c;涵盖财务、业务、流程和数据的整合。方…

SQLite 表达式详解

SQLite 表达式详解 引言 SQLite 是一个轻量级的数据库,广泛用于移动设备和桌面应用程序。SQLite 的表达式是 SQL 语句的核心,它们用于查询、更新和删除数据库中的数据。本文将详细解释 SQLite 的各种表达式,并探讨它们在数据库操作中的重要性。 表达式概述 在 SQLite 中…

沉浸式AI交互数字人技术解析

360智汇云沉浸式AI交互数字人支持开发者灵活接入和私有化部署大模型服务&#xff0c;构建面向业务场景的实时音视频交互能力。系统集成了360智汇云自研的沉浸式AI交互数字人引擎与高性能 RTC 模块&#xff0c;保障音视频传输过程中的低延迟、高稳定性和高并发承载能力&#xff…

HarmonyOS 评论回复弹窗最佳实践

HarmonyOS 评论回复弹窗最佳实践 前言 在移动应用开发中&#xff0c;评论回复功能是一个常见且重要的交互场景。本文将详细介绍如何在 HarmonyOS 中实现一个功能完善的评论回复弹窗&#xff0c;包括弹窗选型、富文本编辑、软键盘适配等关键技术点。 功能概述 我们要实现的评…

Git 回退操作详解:带示例的“小白”指南

前言 在日常开发中&#xff0c;我们难免会遇到&#xff1a; 改错代码&#xff1a;推送之前才发现某些行根本就不该动提交错误&#xff1a;commit 信息打错、提交到错误分支想回到之前版本&#xff1a;测试时发现之前版本是好的&#xff0c;需要回去查看 这就需要用到 Git 的…

redux以及react-redux

1.redux案例完整版 上一篇文章我们是没有action文件&#xff0c;直接在countre组件与store以及reducer直接进行状态的改变以及展示。 下面我们加上action文件&#xff0c;我们就不能直接通过dispatch传&#xff0c;而是通过调用action里面的函数讲我们传入的参数变成action这种…

idea中配置svn及提交提示“未检测到更改”提示

首先要安装TortoiseSVN&#xff0c;选command line client tools&#xff1b; 在idea中&#xff0c;文件->设置->Subversion->如下图 文件->设置->目录映射->如下图 初次导入到svn&#xff0c; 输入服务器上的svn地址&#xff0c;等待成功即可&#xff1b;…

vue 实现dot-dropdown

<template><div class"app-container"><div class"mt30"></div><el-row :gutter"20"><!-- title --><!-- <div class"modt-box">导航管理</div> --><el-col :span"2&q…

使用 mysql2/promise 模块返回以后,使用 await 返回数据总结

SELECT 返回结构 const [rows, fields] await db.query(SELECT * FROM folders);返回&#xff1a; rows: 是一个数组&#xff0c;包含所有查到的记录。fields: 是字段的结构定义&#xff08;列信息&#xff09;&#xff0c;一般不用。 rows 是一个数组&#xff0c;包含所有…

Manus Metagloves pro高精度+无漂移+低延迟 ,重构VR/XR手部交互方式

manus metagloves pro是一款专为动画制作、虚拟现实及游戏开发打造的高精度无线动作捕捉手套。采用先进的Quantum追踪技术&#xff0c;实现毫米级动作捕捉&#xff0c;精准还原手指细节&#xff0c;显著提升创作效率与交互真实感。 MANUS Metagloves Pro解锁动捕 / 机器人 / XR…

Uniapp插件改造指南:如何让vue-plugin支持HarmonyOS5原生能力?

一、分层架构设计 采用通用逻辑与平台实现分离的三层结构&#xff1a; uni-plugin-harmony ├── common # 跨平台通用层 │ ├── interfaces # 能力接口抽象&#xff08;如Scanner.ets&#xff09; │ └── utils # 工具类 ├── harmony …

P1040 [NOIP 2003 提高组] 加分二叉树 题解

题目描述 设一个 n n n 个节点的二叉树 tree \text{tree} tree 的中序遍历为 ( 1 , 2 , 3 , … , n ) (1,2,3,\ldots,n) (1,2,3,…,n)&#xff0c;每个节点都有一个分数&#xff08;均为正整数&#xff09;。任一棵子树 subtree \text{subtree} subtree&#xff08;包含 tr…

【Golang面试题】Data Race 问题怎么检测?

Go Race Detector 深度指南&#xff1a;原理、用法与实战技巧 一、什么是数据竞争&#xff1f; 在并发编程中&#xff0c;数据竞争发生在两个或多个 goroutine 同时访问同一内存位置&#xff0c;且至少有一个是写操作时。这种竞争会导致不可预测的行为和极其难以调试的问题。…

257. 二叉树的所有路径(js)

257. 二叉树的所有路径——DFS 回溯&#xff08;js&#xff09; 题目描述解题思路完整代码时间复杂度分析 题目描述 257. 二叉树的所有路径 解题思路 题意理解 给定一棵二叉树&#xff0c;要求返回所有从根节点到叶子节点的路径&#xff0c;路径以字符串形式表示&#xff0c…

自动化文档生成工具(亲测可运行)

本文介绍了一个用Java编写的自动化文档生成工具&#xff0c;通过读取开发清单文本自动生成格式规范的Word文档。该工具的主要特点包括&#xff1a; 采用Apache POI库处理Word文档&#xff0c;支持多级标题和段落自动生成实现中文数字转换功能&#xff0c;将编号转换为"一、…