• 游戏
  • 工业
  • 资源
  • 社区
  • 学习
  • 支持
开发
Unity 引擎
为任何平台构建2D和3D游戏
下载计划和定价
商业化
应用内购买(IAP)
发现并管理各商店的IAP
聚合平台
最大化收入并优化变现
Ad Quality
保护您应用的用户体验
Tapjoy
建立长期用户忠诚度
所有变现产品
用户获取
用户获取
被发现并获取移动用户
Unity向量AI
将玩家与合适的游戏连接
Aura设备内广告
在用户高峰参与时触达用户
所有增长产品
使用案例
3D协作
实时构建和审查3D项目
沉浸式培训
在沉浸式环境中培训
客户体验
创建互动3D体验
所有行业解决方案
行业
制造业
实现运营卓越
零售
将店内体验转化为在线体验
汽车
提升创新和车内体验
所有行业
技术库
文档
官方用户手册和API参考
开发者工具
发布版本和问题跟踪器
路线图
查看即将推出的功能
术语表
技术术语库
洞察
案例分析
真实成功案例
最佳实践指南
专家提示和技巧
所有资源
新增功能
博客
更新、信息和技术提示
新闻
新闻、故事和新闻中心
社区中心
讨论
讨论、解决问题和连接
事件
全球和本地活动
社区故事
Made with Unity
展示Unity创作者
直播活动
加入开发者、创作者和内部人员
Unity奖项
庆祝全球的Unity创作者
适合每个级别
Unity Learn
免费掌握Unity技能
专业培训
通过Unity培训师提升您的团队
Unity新手
准备开始
开始您的学习
Unity基础路径
你是Unity 新手?开始您的旅程
使用指南
可操作的技巧和最佳实践
教育
对于学生
开启您的职业生涯
对于教育者
增强您的教学
教育资助许可证
将Unity的力量带入您的机构
认证
证明您的Unity精通
支持选项
获取帮助
帮助您在Unity中取得成功
成功计划
通过专家支持更快实现目标
常见问题解答
常见问题解答
联系我们
与我们的团队联系
计划和定价
语言
  • English
  • Deutsch
  • 日本語
  • Français
  • Português
  • 中文
  • Español
  • Русский
  • 한국어
社交
货币
采购
  • 产品
  • Unity Ads
  • 订阅
  • Unity Asset Store
  • 经销商
教育
  • 学生
  • 教师
  • 机构
  • 认证
  • 学习
  • 技能发展计划
下载
  • Unity Hub
  • 下载存档
  • Beta 版测试
Unity Labs
  • 实验室
  • 作品
资源
  • 学习平台
  • 社区
  • 文档
  • Unity QA
  • 常见问题解答
  • 服务状态
  • 案例分析
  • Made with Unity
Unity
  • 我们公司
  • 新闻简报
  • 博客
  • 事件
  • 工作机会
  • 帮助
  • 新闻
  • 合作伙伴
  • 投资人
  • 附属机构
  • 安防
  • 社会影响力
  • 包容性与多样性
  • 联系我们
版权所有 © 2025 Unity Technologies
  • 法律
  • 隐私政策
  • Cookie
  • 不要出售或分享我的个人信息

“Unity”、Unity 徽标及其他 Unity 商标是 Unity Technologies 或其分支机构在美国及其他地区的商标或注册商标(单击此处获取更多信息)。其他名称或品牌是其各自所有者的商标。

Hero background image

高级编程和代码架构

探索代码架构,进一步优化图形渲染。 本文属Unity项目优化技巧系列文章的第四篇。你可以用它们指导用更少的资源运行更高的帧率。尝试了这些最佳实践后,请务必查看系列的其他页面: 配置Unity项目以获得更强的性能 高端图形的性能优化 管理PC和主机游戏的GPU使用情况 更强的物理性能,流畅的游戏体验
为方便起见,此网页已进行机器翻译。我们无法保证翻译内容的准确性或可靠性。如果您对翻译内容的准确性有疑问,请参阅此网页的官方英文版本。
请点击这里。
  • 了解 Unity PlayerLoop
  • 构建自定义 Update Manager
  • 减少每一帧的代码
  • 缓存开销较大的函数的结果
  • 避免空Unity事件和调试日志语句
  • 禁用堆栈跟踪记录
  • 使用哈希值而不是字符串参数
  • 集中对象
  • 利用ScriptableObject的强大功能

了解 Unity PlayerLoop

Unity PlayerLoop包含与游戏引擎核心互动的函数。该结构包含许多处理初始化和每帧更新的系统。所有脚本都将依赖PlayerLoop来创建游戏。进行性能分析时,您会在 PlayerLoop 下看到项目的用户代码 - 在 EditorLoop 下看到编辑器组件。

了解Unity FrameLoop的执行顺序很重要。每个Unity脚本都会以预定的顺序运行多个事件函数。了解 Awake、Start、Update 以及创建脚本生命周期以提高性能的其他函数之间的区别。

比如在处理刚体时用FixedUpdate代替Update,或在游戏开始前用Awake代替Start来初始化变量或游戏状态。使用这些功能可以最大限度减少每一帧上运行的代码。Awake在脚本分层的生命周期内只会被调用一次,并且总是在Start函数之前。这意味着你应该使用Start来处理已知可以与其他对象交谈的对象,或者在初始化时查询它们。

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

自定义更新管理器图

构建自定义 Update Manager

如果您的项目有苛刻的性能要求(例如开放世界游戏 ) , 请考虑使用 Update、LateUpdate 或 FixedUpdate 创建自定义 Update Manager。

Update或LateUpdate的常见用法是仅在满足某些条件时运行逻辑。这可能导致进行大量每帧回调,这些回调实际上不会运行任何代码,除非检查此条件。

每当 Unity 调用 Update 或 LateUpdate 等消息方法时,就会进行互操作调用 – 这意味着从 C/C++ 端到托管 C# 端的调用。对于少数对象,这不是问题。当对象数以千计时,开销会变得很大。

当活动对象需要回调时订阅该 Update Manager,当不需要时取消订阅。这种模式可以减少对MonoBehaviour对象的许多互操作调用。

请参考游戏引擎优化技术来了解应用实例。

减少每一帧的代码

考虑是否必须在每一帧上运行代码。你可以从Update、LateUpdate和FixedUpdate中移除不必要的逻辑。这些 Unity 事件函数是放置必须每帧更新的代码的便利场所,但您可以提取任何不需要以该频率更新的逻辑。

仅在发生更改时执行逻辑。记住利用事件形式的观察者模式等技术来触发特定的函数签名。

如果需要使用Update,可以每隔n帧运行代码。这是应用时间片的一种方式,这是一种将繁重的工作负载分配到多个帧的常见技术。

在本例中,ExampleExpensiveFunction每三帧运行一次。

诀窍是将该功能与其他帧上运行的其他工作交织在一起。在本例中,您可以在 Time.frameCount % interval == 1 或 Time.frameCount % interval == 2 时“调度”其他开销较大的函数。

或者,使用自定义 Update Manager 类来每 n 帧更新订阅的对象。

缓存开销较大的函数的结果

在2020.2之前的Unity版本中,GameObject.Find、GameObject.GetComponent和Camera.main的成本可能很高,所以最好不要在Update方法中调用它们。

此外,如果经常调用OnEnable和OnDisable方法,请尽量避免在它们中放置开销较大的方法。频繁调用这些方法可能会导致 CPU 峰值。

尽可能在初始化阶段运行代价高昂的函数,如MonoBehaviour.Awake和MonoBehaviour.Start。缓存需要的引用,并在以后重复使用。请在上一节的Unity PlayerLoop部分详细了解脚本顺序的执行情况。

下方例子展示了低效率的GetComponent重复调用:

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

而GetComponent只会被缓存一次。缓存的结果可以在Update中重复使用,而无需进一步调用GetComponent。

阅读更多关于Order of execution for event functions的内容。

避免空Unity事件和调试日志语句

Log 语句(尤其是在 Update、LateUpdate 或 FixedUpdate 中)可能会降低性能,因此在构建之前禁用 log 语句。要想快速做到这点,你可以考虑在预处理指令里加上一个Conditional Attribute。

比如,你可以创建一个自定义类,如下所示。

使用自定义类生成日志消息。如果你在Player Settings > Scripting DefineSymbols 中禁用 ENABLE_LOG 预处理器,所有的 log 语句都会一下子消失。

处理字符串和文本是 Unity 项目中常见的性能问题来源。因此,删除 log 语句及其代价高昂的字符串格式可能会获得巨大的性能提升。

类似地,空MonoBehaviour也需要占用资源,所以你应该删除空的Update或LateUpdate方法。如果您采用以下方法来进行测试,请使用前处理器指令:

#if Unity_EDITOR
void Update()
{
}
#endif

这时,你可以在编辑器内用Update进行测试,避免不必要的开销。

这篇关于10,000次Update调用的博客文章解释了Unity如何执行MonoBehaviour.Update。

禁用堆栈跟踪记录

使用 Player Settings 中的 Stack Trace 选项控制日志消息的类型。如果应用程序正在记录发布版本中的错误或警告消息(例如,在野外生成崩溃报告),请禁用堆栈跟踪以提高性能。

了解有关堆栈跟踪日志记录的更多信息。

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

Unity不会在内部使用字符串名称来命名Animator、Material或Shader属性。出于速度考虑,所有属性名称都哈希为属性 ID,这些 ID 用于寻址属性。

在Animator、Material或Shader上使用Set或Get方法时,请使用整数值方法而非字符串值方法。字符串值方法执行字符串哈希,然后将哈希 ID 转发给整数值方法。

将 Animator.StringToHash 用于 Animator 属性名称,将 Shader.PropertyToID 用于 Material 和 Shader 属性名称。

与此相关的是数据结构的选择,它会影响性能,因为每帧会迭代数千次。请遵循C#的MSDN数据结构指南来选择合适的结构。

对象池脚本界面

集中对象

实例化和销毁会产生垃圾收集高峰(GC)。这通常是一个缓慢的过程,因此与其定期实例化和销毁游戏对象(例如,从枪中发射子弹 ) , 不如使用可重复使用和回收的预分配对象池。

在游戏的某个时间点(如菜单界面或加载界面,此时 CPU 峰值不太明显)创建可重用的实例。通过集合跟踪此对象“池”。在游戏过程中,你可以根据需要启用下一个分层对象,禁用而不是销毁它们,然后再将其送回池中。这样可以减少项目中托管分配的数量,并防止 GC 问题。

同样地,避免在运行时添加组件;调用AddComponent也会带来一些成本。每当在运行时添加组件时,Unity 必须检查重复项或其他所需的组件。使用已经设置好的组件实例化预制件性能更好,所以要与对象池结合使用。

与此相关,在移动Transform时,使用Transform.SetPositionAndRotation一次更新位置和旋转。这可避免两次修改变换的开销。

如果需要在运行时实例化游戏对象,请将其父对象重新定位以进行优化,请参阅下文。

有关Object.Instantiate的详情,见Scripting API。

在这里学习如何在Unity中创建一个简单的对象池系统。

可编程对象池

利用ScriptableObject的强大功能

将不变的值或设置存储在 ScriptableObject 中,而不是 MonoBehaviour。ScriptableObject是项目里的资产。它只需设置一次,不能直接连接到游戏对象。

在ScriptableObject中创建字段来存储值或设置,然后在MonoBehaviour中引用ScriptableObject。使用ScriptableObject的字段可以避免每次使用该MonoBehaviour实例化对象时产生不必要的数据重复。

观看介绍ScriptableObjects操作指引并在此查找相关文档。

Unity 关键艺术 21 11
获取免费电子书

我们有史以来最全面的指南之一收集了 80 多个关于如何为 PC 和游戏主机优化游戏的可操作技巧。这些深入的技巧由我们的 Success 和 Accelerate Solutions 专家工程师创建,可帮助您充分利用 Unity 并提高游戏性能。

下载电子书