英文原文:
OptimizeYourMobileGamePerformance_V6_May2021.pdf

内容

  • 前言
  • Profiling
  • 内存
  • 自适应性能
  • 编程和代码架构
  • 项目配置
  • 资产
  • 图形和 GPU 优化
  • 用户界面
  • 声音的
  • 动画
  • 物理学
  • 工作流程和协作
  • Unity 集成成功
  • 总结

1. 前言

  优化您的 iOS 和 Android 应用程序是支撑整个开发周期的重要过程。 移动硬件不断发展,移动游戏的优化——连同其艺术、游戏设计、音频和货币化策略——在塑造玩家体验方面发挥着关键作用。

  iOS 和 Android 都拥有数十亿的活跃用户群。 如果您的手机游戏经过高度优化,则更有可能通过特定平台商店的认证。 为了最大限度地提高您在发布时及以后取得成功的机会,您的目标始终是双重的:构建最流畅、最身临其境的体验,并使其在最广泛的手持设备上表现出色。

  本指南汇集了 Unity 软件工程师专家团队的知识和建议。 Unity 的 Accelerate Solutions 游戏团队与整个行业的开发人员合作,以帮助推出最好的游戏。 请按照此处列出的步骤,从您的手机游戏中获得最佳性能,同时降低其功耗。

  请注意,此处讨论的许多优化可能会引入额外的复杂性,这可能意味着额外的维护和潜在的错误。 在实施这些最佳实践时,平衡性能增益与时间和劳动力成本。


使用 Unity Profiler 测试应用程序的性能和资源分配。

2. Profiling

  Unity Profiler 可以帮助您检测运行期间任何延迟或冻结的原因,或了解特定帧(时间点)发生的情况。 默认启用 CPU 和内存轨道。 如果您对游戏有特定需求(例如,重物理或基于音乐的游戏玩法),您可以监控其他 Profiler 模块(例如 Renderer、Audio、Physics 等)。

  通过检查 Development Build 和 Autoconnect Profiler 将应用程序构建到您的设备,或手动连接以加快应用程序启动时间。

  选择要分析的目标。 Record 按钮跟踪应用程序播放的几秒钟(默认为 300 帧)。 如果您需要更长的捕获时间,请转到 Unity > Preferences > Analysis > Profiler > Frame Count 以将其增加到 2000。 这意味着 Unity 编辑器必须做更多的 CPU 工作并占用更多内存,但根据您的具体情况,它可能很有用。


  这是一个基于检测的分析器,可以分析显式包装在 ProfileMarkers 中的代码时序(例如 Monobehaviour 的 Start 或 Update 方法或特定的 API 调用)。 此外,当您使用 Deep Profiling 设置时,Unity 可以分析脚本代码中每个函数调用的开始和结束,以准确告诉您应用程序的哪个部分导致速度变慢(但这会带来额外的开销)。

  在分析您的游戏时,我们建议您同时涵盖游戏中的峰值和平均帧成本。 了解和优化每帧发生的昂贵操作对于在目标帧速率以下运行的应用程序可能更有用。 在寻找尖峰时,首先探索昂贵的操作(例如,物理、AI、动画)和垃圾收集。

  在窗口中单击以分析特定帧。 接下来,使用 Timeline 或 Hierarchy 视图:

  • Hierarchy 显示 ProfileMarkers 的层次结构,分组在一起。 这允许您根据以毫秒为单位的时间成本(Time ms 和 Self ms)对样本进行排序。 您还可以计算此帧上对函数和托管堆内存 (GC Alloc) 的调用次数。
  • Timeline 显示特定帧时间的可视化细分。 这使您可以可视化活动如何相互关联以及跨不同线程。 使用它来确定您是受 CPU 限制还是受 GPU 限制。


在此处阅读 Unity Profiler 的完整概述。 那些不熟悉分析的人也可以观看此 Unity Profiling 简介。

  在优化项目中的任何内容之前,请保存 Profiler .data 文件。 实施您的更改并比较修改前后保存的 .data。 依靠这个循环来提高性能:分析、优化和比较。 然后,冲洗并重复。

尽早并经常进行分析

  Unity Profiler 提供有关您的应用程序的性能信息,但如果您不使用它,它就帮不了您。 在开发的早期分析您的项目,而不仅仅是在您接近交付时。 出现故障或尖峰时立即调查。 当您开发项目的“性能签名”时,您将能够更轻松地发现新问题。

不要盲目优化

  不要猜测或假设是什么降低了游戏的性能。 使用 Unity Profiler 和特定于平台的工具来定位导致延迟的具体原因。

  此外,并非此处描述的所有优化都适用于您的应用程序。 在一个项目中运作良好的东西可能无法转化为您的项目。 找出真正的瓶颈并集中精力解决这些问题。

Profile在目标设备上

  虽然编辑器中的分析可以让您对游戏中不同系统的相对性能有一个非常粗略的了解,但没有什么可以替代真实的。 尽可能在目标设备上分析开发版本。 请记住针对您计划支持的最低规格设备进行分析和优化。

  单独的 Unity Profiler 无法查看引擎的每个部分。 幸运的是,iOS 和 Android 都包含原生工具来帮助您测试性能:

  • 在 iOS 上,使用 Xcode 和 Instruments。
  • 在 Android 上,使用 Android Studio 和 Android Profiler。

  某些硬件还可以利用其他分析工具(例如 Arm Mobile Studio、Intel VTune 和 Snapdragon Profiler)。 有关详细信息,请参阅使用 Unity 分析应用程序。

使用Profile Analyzer

  此工具可让您聚合多个 Profiler 数据帧,然后定位感兴趣的帧。 想看看在您对项目进行更改后 Profiler 会发生什么? 比较视图允许您加载和比较两个数据集,这对于测试更改和显示改进至关重要。 Profile Analyzer 可通过 Unity 的包管理器获得。

在每帧的特定时间预算上工作

  每个帧都会有一个基于每秒目标帧数 (fps) 的时间预算。 理想情况下,以 30 fps 运行的应用程序将允许每帧大约 33.33 ms (1000 ms / 30 fps)。 同样,60 fps 的目标每帧留下 16.66 毫秒。

  但是,对于移动设备,您不能始终使用此时间,因为设备会过热并且操作系统会过热限制 CPU 和 GPU。 我们建议您只使用大约 65% 的可用时间来允许帧之间的冷却时间。 典型的帧预算约为 30 fps 时每帧 22 ms 和 60 fps 时每帧 11 ms。

设备可以在短时间内超过此值(例如,过场动画或加载序列),但不会持续很长时间。

确定您是受 GPU 限制还是受 CPU 限制

  Profiler 可以告诉您 CPU 花费的时间是否超过了分配的帧预算,或者罪魁祸首是否是您的 GPU。

  如果您看到 Gfx.WaitForCommands 标记,则表示渲染线程已准备就绪,但您可能正在等待主线程上的瓶颈。

  如果您经常遇到 Gfx.WaitForPresent,这意味着主线程已准备好但正在等待 GPU 呈现帧。

考虑设备温度

  大多数移动设备不像桌面设备那样具有主动冷却功能。 物理热量水平会直接影响性能。

  如果设备运行很热,Profiler 可能会报告性能不佳,即使它可能不会引起关注。 通过在短时间内进行分析来消除分析开销,以保持设备凉爽并模拟真实世界条件。

在最低规格设备上进行测试

  有各种各样的 iOS 和 Android 设备。 在您希望应用程序支持的最低设备规格上测试您的项目。

3. 内存

  Unity 对用户生成的代码和脚本采用自动内存管理。 小块数据,如值类型的局部变量,被分配到堆栈中。 更大的数据和更长期的存储被分配给托管堆。

  垃圾收集器 (GC) 定期识别和释放未使用的堆内存。 虽然这会自动运行,但检查堆中所有对象的过程可能会导致游戏卡顿或运行缓慢。

  优化内存使用意味着要注意何时分配和释放堆内存,并尽量减少垃圾收集的影响。

  有关详细信息,请参阅了解托管堆

使用内存分析器

  这个单独的附加组件(在包管理器中作为实验包或预览包提供)可以拍摄托管堆内存的快照,帮助您发现碎片和内存泄漏等问题。

  在 Tree Map 视图中单击以将变量跟踪到持有内存的本机对象。 在这里,您可以识别常见的内存消耗问题,例如过大的纹理或重复的资源。

  观看如何使用 Unity 中的 Memory Profiler 来提高内存使用率。 您还可以查看官方的 Memory Profiler 文档。

减少垃圾收集 (GC) 的影响

  Unity 使用 Boehm-Demers-Weiser 垃圾收集器,它会停止运行您的程序代码,仅在完成工作后恢复正常执行。

请注意某些不必要的堆分配,这可能会导致 GC 峰值:

  • 字符串:在 C# 中,字符串是引用类型,而不是值类型。 减少不必要的字符串创建或操作。 避免解析 JSON 和 XML 等基于字符串的数据文件; 以 ScriptableObjects 或 MessagePack 或 Protobuf 等格式存储数据。 如果需要在运行时构建字符串,请使用 StringBuilder 类。
  • Unity 函数调用:请注意,某些函数会创建堆分配。 缓存对数组的引用,而不是在循环中间分配它们。 此外,利用某些避免产生垃圾的功能; 例如,使用 GameObject.CompareTag 而不是手动将字符串与 GameObject.tag 进行比较(返回新字符串会产生垃圾)。
  • 装箱:避免传递值类型变量来代替引用类型变量。 这会创建一个临时对象,并且随之而来的潜在垃圾(例如 int i = 123; object o = i)会隐式地将值类型转换为类型对象。
  • 协程:虽然 yield 不会产生垃圾,但创建一个新的 WaitForSeconds 对象会。 缓存并重用 WaitForSeconds 对象,而不是在 yield 行中创建它。
  • LINQ 和正则表达式:这两者都从幕后的装箱中生成垃圾。 如果性能是一个问题,请避免使用 LINQ 和正则表达式。

如果可能,具体的时间点手动垃圾收集

  如果您确定垃圾回收冻结不会影响游戏中的特定点,您可以使用 System.GC.Collect 触发垃圾回收。

  请参阅了解自动内存管理,了解您可以在哪些地方使用它来发挥自己的优势。

使用增量垃圾收集器拆分 GC 工作量

  增量垃圾收集不是使用单个长时间中断程序执行,而是使用多个更短的中断,将工作负载分布在许多帧上。 如果垃圾收集正在影响性能,请尝试启用此选项以查看它是否可以显着减少 GC 峰值问题。 使用 Profile Analyzer 来验证对您的应用程序的好处。

4. 自适应性能

  借助 Unity 和三星的 Adaptive Performance,您可以监控设备的散热和电源状态,以确保您已准备好做出适当的反应。 当用户长时间玩游戏时,您可以动态降低细节级别(或 LOD)偏差,以帮助您的游戏继续流畅运行。 自适应性能允许开发人员以受控方式提高性能,同时保持图形保真度。

  虽然您可以使用自适应性能 API 来微调您的应用程序,但此包还提供自动模式。 在这些模式下,自适应性能根据几个关键指标确定游戏设置:

  • 基于先前帧的所需帧速率
  • 设备温度水平
  • 设备接近热事件
  • 受 CPU 或 GPU 绑定的设备

  这四个指标决定了设备的状态,自适应性能调整调整后的设置以减少瓶颈。 这是通过提供一个整数值(称为索引器)来描述设备的状态来完成的。


  要了解有关 Adaptive Performance 的更多信息,您可以通过选择 Package Manager > Adaptive Performance > Samples 查看我们在 Package Manager 中提供的示例。 每个示例都与特定的缩放器交互,因此您可以看到不同的缩放器如何影响您的游戏。 我们还建议您查看最终用户文档以了解有关自适应性能配置以及如何直接与 API 交互的更多信息。

5. 编程和代码架构

  Unity PlayerLoop 包含与游戏引擎核心交互的函数。 这种树状结构包括许多处理初始化和每帧更新的系统。 您的所有脚本都将依赖此 PlayerLoop 来创建游戏玩法。

  分析时,您将在 PlayerLoop 下看到您项目的所有用户代码(在 EditorLoop 下带有编辑器组件)。

您可以使用这些提示和技巧来优化您的脚本

了解 Unity PlayerLoop

  确保您了解 Unity 帧循环的执行顺序。 每个 Unity 脚本都以预定的顺序运行多个事件函数。 您应该了解 Awake、Start、Update 和其他创建脚本生命周期的函数之间的区别。

有关事件函数的具体执行顺序,请参阅脚本生命周期流程图。

最小化运行每一帧的代码

  考虑代码是否必须运行每一帧。 将不必要的逻辑移出 Update、LateUpdate 和 FixedUpdate。 这些事件函数是放置必须更新每一帧的代码的方便位置,但提取任何不需要以该频率更新的逻辑。 只要有可能,只在事情发生变化时执行逻辑。

  如果确实需要使用Update,请考虑每 n 帧运行一次代码。 这是应用时间切片的一种方式,一种将繁重的工作负载分配到多个帧的常用技术。 在此示例中,我们每三帧运行一次 ExampleExpensiveFunction:

private int interval = 3;
void Update()
{if (Time.frameCount % interval == 0){ExampleExpensiveFunction();}
}
避免 Start/Awake 中的繁重逻辑

  当您的第一个场景加载时,将为每个对象调用这些函数:

  • Awake
  • OnEnable
  • Start

  避免在这些函数中使用昂贵的逻辑,直到您的应用程序呈现其第一帧。 否则,您可能会遇到不必要的加载时间。

  有关第一个场景加载的详细信息,请参阅事件函数的执行顺序。

避免空的 Unity 事件

  即使是空的 MonoBehaviours 也需要资源,因此您应该删除空白的 Update 或 LateUpdate 方法。

  如果您使用这些方法进行测试,请使用预处理器指令:

#if UNITY_EDITORvoid Update(){}
#endif

  在这里,您可以自由地使用编辑器中的 Update 进行测试,而无需将不必要的开销带入您的构建中。

删除调试日志语句

  日志语句(尤其是在 Update、LateUpdate 或 FixedUpdate 中)可能会降低性能。 在进行构建之前禁用您的日志语句。

要更轻松地执行此操作,请考虑将条件属性与预处理指令一起制作。 例如,创建一个这样的自定义类:

public static class Logging
{[System.Diagnostics.Conditional(“ENABLE_LOG”)]static public void Log(object message){UnityEngine.Debug.Log(message);}
}


  使用您的自定义类生成您的日志消息。 如果您在 Player Settings 中禁用 ENABLE_LOG 预处理器,您的所有 Log 语句都会一举消失。

使用哈希值而不是字符串参数

  Unity 在内部不使用字符串名称来处理 Animator、Material 和 Shader 属性。 为了速度,所有属性名称都被散列到属性 ID 中,这些 ID 实际上用于对属性进行寻址。

  每当在 Animator、Material 或 Shader 上使用 Set 或 Get 方法时,请使用整数值方法而不是字符串值方法。 字符串方法只需执行字符串散列,然后将散列后的 ID 转发给整数值方法。

  使用 Animator.StringToHash 作为 Animator 属性名称,使用 Shader.PropertyToID 作为材质和着色器属性名称。

选择正确的数据结构

  当您每帧迭代数千次时,您选择的数据结构可能会对效率或效率低下产生累积影响。 为您的集合使用列表、数组或字典是否更有意义? 按照 MSDN 中的 C# 数据结构指南作为选择正确结构的一般指南。

避免在运行时添加组件

  在运行时调用 AddComponent 需要一些成本。 每当在运行时添加组件时,Unity 必须检查重复的或其他必需的组件。

  使用已设置的所需组件实例化Prefab通常性能更高。

缓存游戏对象和组件

  GameObject.Find、GameObject.GetComponent 和 Camera.main(在 2020.2 之前的版本中)可能很昂贵,因此请避免在 Update 方法中调用它们。 相反,在 Start 中调用它们并缓存结果。

例如,这演示了重复 GetComponent 调用的低效使用:

void Update()
{Renderer myRenderer = GetComponent<Renderer>();ExampleFunction(myRenderer);
}

  相反,您只能调用一次 GetComponent,因为该函数的结果已被缓存。 缓存的结果可以在 Update 中重复使用,而无需进一步调用 GetComponent。

private Renderer myRenderer;
void Start()
{myRenderer = GetComponent<Renderer>();
}
void Update()
{ExampleFunction(myRenderer);
}
使用对象池

  实例化和销毁会产生垃圾和垃圾收集 (GC) 峰值,并且通常是一个缓慢的过程。 与其频繁实例化和销毁游戏对象(例如,用枪射击子弹),不如使用可重复使用和回收的预分配对象池。


  在游戏中 CPU 峰值不太明显的某个时间点(例如,在菜单屏幕期间)创建可重用实例。 使用集合跟踪这个对象“池”。 在游戏过程中,只需在需要时启用下一个可用实例,禁用对象而不是销毁它们,然后将它们返回池中。

  这减少了项目中托管分配的数量,并且可以防止垃圾收集问题。

在此处了解如何在 Unity 中创建简单的对象池系统。

使用 ScriptableObjects

  将不变的值或设置存储在 ScriptableObject 而不是 MonoBehaviour 中。 ScriptableObject 是一种位于项目内部的资产,您只需设置一次。 它不能直接附加到游戏对象。

  在 ScriptableObject 中创建字段以存储您的值或设置,然后在 Monobehaviours 中引用 ScriptableObject。

  每次使用该 Monobehaviour 实例化对象时,使用 ScriptableObject 中的这些字段可以防止不必要的数据重复。

  观看此 ScriptableObjects 简介教程,了解 ScriptableObjects 如何帮助您的项目。 您还可以在此处找到文档。

[Unity] 优化您的移动游戏性能2020(上)相关推荐

  1. [Unity] 优化您的移动游戏性能2020(中)

    上篇: https://blog.csdn.net/u013716859/article/details/125588600 6. 项目配置 有一些项目设置会影响您的移动性能. 降低或禁用加速度计频率 ...

  2. win10和win7游戏测试软件,是时候和Win7说再见了!Win10游戏性能最多领先50%

    原标题:是时候和Win7说再见了!Win10游戏性能最多领先50% 微软已于今年初彻底放弃支持Windows 7操作系统,Windows 10的市场占有率也节节攀升,但仍有很多用户顽固坚守在Windo ...

  3. 比英伟达便宜4000元、功耗更低、游戏性能相同,AMD发布RX 6900 XT旗舰显卡

    晓查 发自 凹非寺  量子位 报道 | 公众号 QbitAI AMD全新Radeon RX 6000系列显卡来了! 今天凌晨,苏妈携RX 6800.RX 6800 XT.RX 6900 XT三款新显卡 ...

  4. Unity移动端游戏性能优化简谱之 以引擎模块为划分的CPU耗时调优

    <Unity移动端游戏性能优化简谱>从Unity移动端游戏优化的一些基础讨论出发,例举和分析了近几年基于Unity开发的移动端游戏项目中最为常见的部分性能问题,并展示了如何使用UWA的性能 ...

  5. Unity移动端游戏性能优化简谱之 常见游戏内存控制

    <Unity移动端游戏性能优化简谱>从Unity移动端游戏优化的一些基础讨论出发,例举和分析了近几年基于Unity开发的移动端游戏项目中最为常见的部分性能问题,并展示了如何使用UWA的性能 ...

  6. Unity 游戏性能优化(4)资源优化

    资源优化 1. 音频 1.1 音频导入设置 1.2 加载音频设置 1.3 压缩格式和质量 1.4 音频性能增强 2. 图片纹理 2.1 纹理压缩格式 2.2 纹理性能增强 2.2.1 减小纹理文件大小 ...

  7. Unity修改分辨率优化游戏性能

    前两天浏览到腾讯游戏学院上的一篇文章,利用 RenderTexture 降低实际渲染分辨率以提升渲染性能,结合着我们自己的项目,对这一方案进行的探索研究. 关于修改游戏分辨率来提高游戏性能有另种方案: ...

  8. Unity优化手机游戏学习教程

    流派:电子学习| MP4 |视频:h264,1280×720 |音频:AAC,48.0 KHz 语言:英语+中英文字幕(根据原英文字幕机译更准确)|大小解压后:3.69 GB |时长:6h 44m 创 ...

  9. 游戏性能优化技术干货分享——内存管理

    项目的性能优化主要围绕CPU.GPU和内存三大方面进行.接上期CPU优化专讲,我们本期和大家分享内存方面的优化心得. 无论是游戏还是VR应用,内存管理都是其研发阶段的重中之重. 然而,在我们测评过的大 ...

最新文章

  1. 如何搭建VUE开发环境
  2. [云炬python3玩转机器学习] 5-9 scikit-learn中的回归问题
  3. Linux交换Esc和Caps
  4. C#类型与SQLSEVER类型对比
  5. 华为云技术开放日(第三季)话题介绍和直播群入口
  6. 微信小程序入门四:实现table效果
  7. python怎么读取word文件大小_python操作word
  8. xtrabackup-增量备份
  9. 解决Sublime Text 2中文显示乱码问题
  10. webpack-dev-server是什么
  11. 使用ps 批处理图片(gif 转 png)
  12. 什么是工业大数据?工业大数据的价值体现在哪些方面?
  13. Power BI----到底什么是度量值?
  14. 自然语言处理(NLP)技术在医疗保健领域中的八个案例
  15. 天嵌E9卡片i.mx6q-Linux12.04搭建nfs环境以及从nfs启动开发板
  16. Win10 双屏:主屏和左右屏设置
  17. win7计算机磁盘清理,win7电脑清理磁盘的操作过程
  18. html期末作业代码网页设计——简洁日式料理餐饮(4页) HTML+CSS+JavaScript 父亲美食HTM5网页设计作业成品
  19. 我用50行代码居然「让天猫精灵把客厅灯开了」
  20. 边列表(edgelist)和邻接矩阵(adjacency)相互转化(how to convert edge list to adjacency, or adjacency to edge list)

热门文章

  1. The 2021 Sichuan Provincial Collegiate Programming Contest
  2. Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程
  3. 2018.12.17知识感悟
  4. shell获取天气预报
  5. MySQL OCP考试记
  6. [daily paper 11]2023 03 19 2022 1 Large Models are Parsimonious Learners Activation Sparsity in Trai
  7. 阿里出品Excel工具EasyExcel使用小结
  8. 双启计算机等级一级考试,一盘双启,解决U盘启动难题,量产工具实现CDROM和HDD格式双启动...
  9. 数据库系列(2):数据库系统的发展
  10. fpga hdmi接收和发送部分调试