Hero image

Unity UI优化技巧

了解如何全面优化UI,以及划分画布和布局组、池化UI对象等技巧。

为方便起见,此网页已进行机器翻译。我们无法保证翻译内容的准确性或可靠性。如果您对翻译内容的准确性有疑问,请参阅此网页的官方英文版本。

这是提供有关如何优化 PC 和游戏主机游戏的深入指导的几个页面之一。你可以在免费的电子书《Optimize your console and PC game performance》中找到完整集锦,书中有80多个优化性能的可操作技巧和最佳做法。

拆分画布

问题:当UI画布上的单个元素发生更改时,它会脏化整个画布。

画布是Unity UI的基本组成部分。它可以生成代表放置在其上的UI元素的网格,当UI元素发生更改时重新生成网格,并向GPU发出绘制调用,以便实际显示UI。

生成这些网格的成本很高。UI元素需要批量收集,以便尽可能减少绘制调用次数。由于批量生成的成本很高,我们只在必要时进行重新生成。问题是,当画布上的一个或多个元素发生变化时,必须再次分析整个画布,以找出如何以最佳方式绘制其元素。

许多用户在单个包含数千个元素的画布上构建整个游戏的UI。当他们更改一个元素时,可能会出现多毫秒的 CPU 峰值。要了解重建为何如此昂贵,请观看Unite会话级别/会话次数的24:55。

解决方案:把画布拆开

每个画布都是一个孤岛,与其他画布的元素相互独立。利用 UGUI 支持多个画布的功能,将画布切片以解决 Unity UI 的批处理问题。

您还可以嵌套画布,让设计师能够创建大型分层 UI,而不必考虑不同元素在屏幕上的不同位置。子画布还会将内容与其父画布和兄弟画布分开。它们维护自己的几何体并执行自己的批处理。一种根据更新频率来划分方法。将静态UI元素放在单独的画布上,将动态元素放在较小的子画布上同时更新。另外,画布上的所有UI元素必须具有相同的Z值、材质和纹理。

图形射线投射器界面

限制Graphic Raycasters并禁用Raycast Target

问题:Graphic Raycaster(图形射线投射器)的使用不足

Graphic Raycaster是将输入转换为UI Events的组件。具体来说,就是将屏幕点击或触摸输入转换成UI事件,再发送给相关的UI元素。每个需要输入的画布(包括子画布)都需要Graphic Raycaster。但是,它也循环遍历屏幕上的每个输入点,检查它们是否在 UI 的 RectTransform 中,从而产生潜在的开销。

尽管Graphic Raycaster有这么个名字,但它并不是真的射线投射器。默认情况下,它仅测试 UI 图形。它获取对接收给定画布上的输入感兴趣的UI元素集并执行交叉检查。分层检测Graphic Raycaster画布上每个UI元素的RectTransform是否被标记为可互动。

问题是并非所有UI元素都需要接收更新。

解决方案:从非交互式UI画布上移除Graphic Raycaster,并关闭静态或非交互式元素的Raycast Target。

特别是,关闭Raycast Target按钮上的文本将直接减少Graphic Raycaster每一帧必须执行的交叉检查数量。

问题:Graphic Raycaster在某些方面的行为确实表现为射线投射器。

如果将画布上的Render模式设置为Worldspace Camera或Screen Space Camera,则可以添加遮挡遮罩。遮挡遮罩决定了射线投射器是通过2D还是3D物理投射光线,以确定是否有物理对象遮挡了用户与UI交互的能力。

解决方案:通过2D或3D物理投射光线的成本很高,所以要谨慎使用此功能。

不要将Graphic Raycaster添加到非交互式UI画布,从而尽可能减少它的数量。因为在这种情况下,没有理由检查交互事件。

请在这篇文档中详细了解Graphic Raycaster。

网格界面

避免代价高昂的UI元素

问题:大型列表、网格视图和大量重叠的UI元素会消耗大量性能。

大型List和Grid视图非常昂贵,将大量UI元素(即卡牌游戏中堆叠的卡牌)分层会造成过度绘制。

解决方案:避免大量重叠的UI元素。

自定义代码以在运行时将分层的UI元素合并为更少的元素和批处理。

如果需要创建大型List或Grid视图,例如包含数百个项目的库存屏幕,请考虑重用较小的UI元素池,而不是为每个项目使用单个UI元素。

查看该GitHub项目,了解优化滚动列表的示例。

布局组界面

尽可能避免布局组

问题:每个尝试脏化其布局的UI元素将执行至少一次GetComponents调用。

当布局系统上的一个或多个子 UI 元素发生更改时,布局会变得“脏”。更改后的子元素将使拥有它的布局系统无效。

布局系统是直接位于布局元素上面的一组连续的布局组。布局元素不仅仅是Layout Element组件(UI图像、文本和滚动矩形),它还包括布局元素,就像滚动矩形也是布局组一样。

现在,关于手头的问题:每个将其布局标记为“脏”的UI元素将至少执行一次GetComponent调用。此调用在布局元素的父级上查找有效的布局组。如果找到,它会继续沿着Transform层级向上走,直到停止查找布局组或达到层级根(以先到为准)。因此,每个布局组都会为每个子布局元素的脏化过程添加一次GetComponent调用,使得嵌套布局组的性能极其低下。

解决方案:尽可能避免布局组。

使用锚点进行比例布局。对于具有动态数量UI元素的热点UI,可以考虑编写自己的代码来计算布局。不要只针对一次改动来按广告需求/广告主使用。

请在文档中详细了解布局组。

对象池接口

以智能方式集中UI对象

问题:以错误的方式集中UI对象

用户通常通过重定父级再禁用的方式集中UI对象,但这会导致不必要的脏化。

解决方案:先禁用对象,然后将其父级重定到池中。

您将脏化旧层级一次,但是一旦您重定其父级,就可以避免第二次脏化旧层级,并且绝对不会脏化新层级。如果要从池中删除某个对象,请先重定其父级,更新数据,然后将其启用。

详细了解基本对象池在Unity的概念。

UI画布组件

如何隐藏画布

问题:不知道如何隐藏画布

有时隐藏UI元素和画布也很有用。但如何高效地做到这一点呢?

解决方案:直接禁用画布组件。

禁用画布组件将阻止画布向GPU发出绘制调用。这样一来,画布将不再可见。但是,画布不会丢弃顶点缓冲区,它会保留所有网格和顶点。然后,当您重新启用它时,它不会触发重新构建,而只会再次开始绘制它们。

此外,禁用画布组件不会通过画布层级结构触发代价高昂的OnDisable/OnEnable回调。只要小心禁用那些运行大量每帧代码的子组件即可。

请在此处详细了解画布组件。

在UI元素上优化应用动画器

问题:在UI上使用动画器

动画师在每一帧上都会脏化UI元素,即使动画中的值不变。

解决方案:使用 UI 动画的代码。

只为随时更改的动态UI元素添加动画器。对于很少发生更改或响应事件而临时更改的元素,请自行编写代码或使用调整系统。Asset Store上提供了许多很好的解决方案。

使用全屏UI时,隐藏其他内容

问题:全屏UI性能不佳

如果您的游戏显示暂停或开始屏幕完全覆盖场景,游戏的其余部分仍然在后台渲染,这可能会影响性能。

解决方案:隐藏所有其他内容。

如果您的屏幕覆盖场景中的所有内容,请禁用渲染 3D 场景的摄像机。同样,禁用隐藏在顶层画布后面的画布元素。

在全屏UI时可以降低Application.targetFrameRate,不必再以60 FPS更新。

更多资源

优化游戏性能

获取免费电子书了解更多

为玩家提供最佳的游戏体验。借助 Unity 专家工程师提供的 80 多个可操作的技巧和最佳实践,您可以优化 PC 和主机游戏。

更具体地说,由Unity Success和Unity Studio Productions团队制定的这些详细实践 -- -- 从与顶级工作室的真实合作中收集 -- -- 将有助于提高游戏的整体表现。