搜索Unity

一些可充分优化Unity UI的技巧

上次更新时间:2019年1月

本页内容简介:有关如何为内容创建优化UI元素的技巧,包括划分画布、避免使用Camera.main和布局组、UI对象的智能池等。

在Unity工程师Ian Dundore主讲的深入挖掘Unity:提高性能的技巧这一场精彩会议中,您会找到更多技巧(Unity UI的一部分,从23:38开始)。

划分画布

问题:当UI Canvas上的一个或多个元素发生更改时,整个画布会脏化。

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

生成这些网格的成本很高。您需要将UI元素收集到批处理中,从而以尽可能少的绘制调用对它们进行绘制。由于批处理生成的成本很高,所以我们只在必要时才会想要重新生成它们。问题是当画布上的一个或多个元素发生更改时,必须重新分析整个画布才能确定如何对其元素进行最佳绘制。

许多用户在单个包含数千个元素的画布中构建整个游戏的UI。所以,当他们更改一个元素时,他们的CPU可能会出现几毫秒的开销飙升(要了解有关重新构建为何成本很高的更多详细信息,请转到Ian的谈话的24:55处)。

解决方案:划分画布。

每个画布都是一个孤岛,它可以将自己的元素与其他画布上的元素分开。所以,请在可用于解决Unity UI中的批处理问题的主流工具中分割画布。

您也可以嵌套画布,从而让设计师创建大层级UI,而不必考虑在许多画布上,不同的元素具体位于屏幕上的何处。子画布的元素也会与其父画布和同级画布的元素分开。它们会维护自己的几何体并执行自己的批处理。

使用子画布分割画布时,请尝试根据元素的更新时间进行分组。例如,将动态元素与静态元素分开(在29:36左右,Ian提供了一个巧妙分割画布的好例子)。

Graphic Raycaster的优化应用

问题:Graphic Raycaster的优化应用:

Graphic Raycaster是将输入转化成UI事件的组件。它将屏幕/触控输入转化为事件,然后将其发送到相关的UI元素。要求输入的每个画布(包括子画布)上都需要Graphic Raycaster。

尽管它的名称是Graphic Raycaster,但它并不是真正意义上的射线投射器:默认情况下,它只会测试UI图形。它将获取需要接收指定画布上的输入的UI元素组,并执行交叉检查:当Graphic Raycaster的画布上的每个UI元素的RectTransform标记为交互时,它会检查输入事件发生在哪一个点。

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

解决方案:关闭静态或非交互式元素的Raycast Target。

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

Unity UI优化技巧

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

如果将画布的渲染模式设置为Worldspace Camera或Screen Space Camera,也可以设置阻挡遮罩。阻挡遮罩确定Raycaster是通过2D还是3D物理投射光线,从而查看某些物理对象是否阻止用户与UI交互的能力。

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

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

避免使用Camera.main

问题:World Space画布需要知道它们的交互事件应来自哪个摄像机。

在World Space或摄像机的屏幕空间上设置要渲染的画布时,可以指定将用于为UI的Graphic Raycaster生成交互事件的具体摄像机。此设置是“Screen Space - Camera”画布所必需的,称为“渲染摄像机”。

Unity UI优化技巧屏幕空间摄像机

但是,对于“World Space”画布,此设置是可选的,称为“Event Camera”。

Unity UI优化技巧世界空间

如果在World Space Canvas上将Event Camera字段留空,这并不意味着画布不会收到事件。相反,它会使用游戏的主摄像机。为了确定哪一个摄像机是主摄像机,它会访问Camera.main属性。

Unity UI优化技巧摄像机主属性

根据Unity使用的代码路径,对于每个Graphic Raycaster的每个World Space Canvas,它每帧会访问Camera.main 7-10次。每次访问Camera.main时,它会调用Object.FindObjectWithTag!显然,这在运行时中并非高效行为。

解决方案:避免使用Camera.main。

缓存摄像机的引用,并创建一个系统来跟踪主摄像机。如果您使用World Space画布,请始终分配一个Event Camera。不要将此设置留空!如果Event Camera需要更改,请编写一些更新Event Camera属性的代码。

尽可能避免布局组

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

当布局系统上的一个或多个子元素发生更改时,它会脏化。发生更改的子元素会导致拥有它的布局系统无效。

关于布局系统的一点补充:布局系统是直接位于Layout Element上的一组连续的布局组。Layout Element不仅仅是Layout Element组件:UI图像、文本和滚动矩形(它们也是Layout Element)。滚动矩形也是布局组。

回到问题:每个将其布局标记为脏化的UI元素至少会执行一次GetComponents调用。该调用会在Layout Element的父级中查找有效的布局组。如果找到,它会继续向上查找Transform层级,直到停止查找布局组或达到根层级(以先到为准)。因此,每个布局组会将一个GetComponents调用添加到每个子Layout Element的脏化过程,这导致嵌套不具足的性能极其低下。

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

为比例布局使用锚点。在包含动态数量元素的活跃UI上,请考虑自己编写计算布局的代码,并且确保仅在必要时使用,而不是每次发生一个更改时都使用。

以智能方式集中UI对象

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

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

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

您会脏化旧层级一次,但随后当您重定其父级时,可以避免第二次脏化旧层级,而且绝对不会脏化新层级。如果您从池中移除某个对象,请先重定期父级,然后更新数据,然后再将其启用。

如何隐藏画布

问题:如何隐藏画布

有时,您希望隐藏某些UI元素和画布。怎样做效率最高?

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

禁用画布组件将阻止画布向GPU发出绘制调用,所以画布不再可见。但是,画布不会丢弃它的顶点缓冲区;它会保留所有网格和顶点,当您重新启用它时,它不会触发重新构建,它会直接开始再次绘制。

此外,禁用画布组件不会触发在画布层级上执行成本高昂的OnDisable/OnEnable回调。只不过,要小心禁用运行成本高昂的每帧代码的子组件。

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

问你题:在UI上使用动画器

动画器每一帧都会脏化其元素,即使动画中的值不发生更改。动画器没有无操作检查。

解决方案:

只应将动画器放置在随时发生更改的动态元素上。对于很少发生更改或在响应事件时才会短时间发生更改的元素,请自行编写代码或补间系统(有很多现成的,请查看Asset Store中的数字)。

更多资源

您喜欢本文吗?请告诉我们!

喜欢。继续发送 还行。有待改进
明白了

我们使用cookies来确保为您提供网站的最佳体验。点击这里了解更多信息。