Unity 开发人员最常犯的 10 个错误
已发表: 2022-03-11Unity 是用于多平台开发的出色而直接的工具。 它的原理很容易理解,您可以直观地开始创建您的产品。 但是,如果有些事情没有考虑到,当您将工作推进到下一个级别时,它们会减慢您的进度,因为您正从初始原型阶段或接近最终版本。 本文将提供有关如何克服最常见问题以及如何避免新项目或现有项目中的基本错误的建议。 请注意,本文的观点更侧重于 3D 应用程序开发,但所提及的一切也适用于 2D 开发。
常见的 Unity 错误 #1:低估项目规划阶段
对于每个项目,在项目的应用程序设计和编程部分开始之前确定几件事情是至关重要的。 如今,当产品营销成为整个过程的重要组成部分时,清楚了解所实施应用程序的业务模型将是什么也很重要。 您必须确定您将为哪些平台发布产品,以及您的计划中包含哪些平台。 还需要设置支持的最低设备规格(您会支持较旧的低端设备还是仅支持更新的型号?)以了解您可以承受的性能和视觉效果。 本文中的每个主题都受到这一事实的影响。
从更技术的角度来看,在将资产和模型提供给程序员的同时,应该提前设置创建资产和模型的整个工作流程,尤其是在模型需要进行更多更改和改进时的迭代过程。 您应该对所需的帧速率和顶点预算有一个清晰的概念,这样 3D 艺术家才能知道模型必须达到的最大分辨率,以及他必须做多少 LOD 变化。 还应指定如何统一所有测量以具有一致的比例,并在整个应用程序中导入过程。
关卡的设计方式对未来的工作至关重要,因为关卡的划分对性能有很大影响。 在设计新关卡时,您必须始终考虑性能问题。 不要带着不切实际的愿景去。 问问自己“可以合理实现吗?”这个问题总是很重要的。 如果不是这样,您不应该将宝贵的资源浪费在难以实现的事情上(当然,如果将其作为主要竞争优势不是您业务战略的一部分)。
常见的 Unity 错误 #2:使用未优化的模型
让您的所有模型做好充分准备,以便能够在您的场景中使用它们而无需进一步修改,这一点至关重要。 好的模型应该满足几件事。
正确设置比例很重要。 有时,由于这些应用程序使用的单位不同,因此无法从您的 3D 建模软件中正确设置。 为了使一切正确,请在模型导入设置中设置比例因子(为 3dsMax 和 Modo 保留 0.01,为 Maya 设置 1.0),并注意有时您需要在更改比例设置后重新导入对象。 这些设置应确保您可以在场景中仅使用基本比例 1、1、1 来获得一致的行为并且没有物理问题。 动态批处理也更有可能正常工作。 此规则也应应用于模型中的每个子对象,而不仅仅是主对象。 当您需要调整对象尺寸时,请在 3D 建模应用程序中而不是在 Unity 中针对其他对象进行调整。 但是,您可以在 Unity 中尝试缩放以找出合适的值,但对于最终应用程序和一致的工作流程,最好在导入 Unity 之前做好一切准备。
关于对象的功能及其动态部分 - 让您的模型得到很好的划分。 子对象越少越好。 将对象的各个部分分开,以备不时之需,例如,动态移动或旋转、用于动画目的或其他交互。 每个对象及其子对象都应使其枢轴相对于其主要功能正确对齐和旋转。 主对象的 Z 轴应指向前方,并且枢轴应位于对象的底部,以便更好地放置在场景中。 在物体上使用尽可能少的材料(更多内容见下文)。
所有资产都应具有适当的名称,以便轻松描述其类型和功能。 在所有项目中保持这种一致性。
常见的 Unity 错误 #3:构建相互依赖的代码架构
Unity 中功能的原型设计和实现非常容易。 您可以轻松拖放对其他对象的任何引用,处理场景中的每个对象,并访问它拥有的每个组件。 但是,这也可能存在潜在危险。 除了明显的性能问题(在层次结构中查找对象和访问组件有其开销)之外,使部分代码完全相互依赖也存在很大的危险。 或者依赖于您的应用程序特有的其他系统和脚本,甚至依赖于当前场景或当前场景。 尝试采用更加模块化的方法并创建可重用的部分,这些部分可用于应用程序的其他部分,甚至可以在整个应用程序组合中共享。 在 Unity API 之上构建您的框架和库,就像您构建知识库一样。
有很多不同的方法可以确保这一点。 一个好的起点是 Unity 组件系统本身。 当特定组件需要与应用程序的其他系统通信时,可能会出现复杂情况。 为此,您可以使用接口使系统的某些部分更加抽象和可重用。 或者,您可以使用事件驱动的方法对来自外部范围的特定事件做出反应,方法是创建消息传递系统或直接注册到其他系统的部分作为侦听器。 正确的方法是尝试将游戏对象属性与程序逻辑分开(至少类似于模型控制器原理),因为很难确定哪些对象正在修改其变换属性,例如位置和旋转。 这应该完全是其控制者的责任。
尝试使所有内容都有据可查。 总是把它当作你应该在很长一段时间后返回你的代码,并且你需要快速理解这部分代码到底在做什么。 因为实际上,您经常会在一段时间后到达应用程序的某些部分,这对于快速解决问题是不必要的障碍。 但不要过度。 有时,一个适当的类、方法或属性名称就足够了。
常见的 Unity 错误 #4:浪费你的表现
最新的手机、游戏机或台式电脑产品线永远不会先进到无需关心性能。 性能优化总是需要的,它们为使您的游戏或应用程序与市场上的其他产品相比看起来有所不同提供了基础。 因为当您在一个部分中保存一些性能时,您可以使用它来完善应用程序的其他部分。
有很多地方需要优化。 需要整篇文章来了解这个主题的表面。 至少,我会尝试将这个领域划分为一些核心领域。
更新循环
不要在更新循环中使用性能密集型的东西,而是使用缓存。 一个典型的例子是访问场景中的组件或其他对象或脚本中的密集计算。 如果可能,将所有内容缓存在Awake()
方法中,或者将您的架构更改为更加事件驱动的方法,以便在需要时触发事件。
实例化
对于经常实例化的对象(例如,FPS 游戏中的子弹),为它们创建一个预先初始化的池,并在需要时选择一个已经初始化并激活它。 然后,不要在不再需要它时将其销毁,而是将其停用并将其返回到池中。
渲染
使用遮挡剔除或 LOD 技术来限制场景的渲染部分。 尝试使用优化模型来控制场景中的顶点数。 请注意,顶点数不仅仅是模型本身的顶点数,它还会受到法线(硬边)、UV 坐标(UV 接缝)和顶点颜色等其他因素的影响。 此外,场景中的一些动态灯光会极大地影响整体性能,因此请尽可能提前烘焙所有内容。
绘制调用
尝试减少绘图调用次数。 在 Unity 中,您可以通过对静止对象使用静态批处理和对移动对象使用动态批处理来减少绘制调用。 但是,您必须首先准备场景和模型(批处理对象必须共享相同的材质),并且动态对象的批处理仅适用于低分辨率模型。 或者,您可以通过脚本将网格组合成一个 ( Mesh.CombineMeshes
) 而不是使用批处理,但您必须注意不要创建太大的对象,因为在某些平台上无法利用视锥体剔除。 一般来说,关键是尽可能少地使用材料并在整个场景中共享它们。 您有时需要从纹理创建图集,以便能够在不同对象之间共享一种材质。 一个好的技巧是在较大的环境中烘焙光照时,使用更高分辨率的场景光照贴图纹理(不是生成的分辨率,而是纹理输出的分辨率)来降低它们的数量。
透支问题
不需要时不要使用透明纹理,因为它会导致填充率问题。 可以将它用于复杂且更远的几何图形,例如树木或灌木丛。 当您需要使用它时,更喜欢 alpha 混合着色器而不是带有 alpha 测试的着色器或移动平台的剪切着色器。 通常,为了识别这些问题,请尝试降低应用程序的分辨率。 如果有帮助,您可能会遇到这些填充率问题,或者您需要进一步优化着色器。 否则,可能是更多的内存问题。
着色器
优化着色器以获得更好的性能。 减少通过次数,使用精度较低的变量,用预先生成的查找纹理替换复杂的数学计算。
始终使用分析器来确定瓶颈。 这是一个很棒的工具。 对于渲染,您还可以使用很棒的 Frame Debugger,它可以帮助您了解很多关于使用它分解渲染过程时一般情况下的工作原理。

常见的 Unity 错误 #5:忽略垃圾收集问题
有必要意识到,尽管垃圾收集器 (GC) 本身可以帮助我们提高效率并专注于编程中的重要事情,但我们应该明确了解一些事情。 GC 的使用不是免费的。 通常,我们应该避免不必要的内存分配,以防止 GC 过于频繁地触发自身,从而因帧率峰值而破坏性能。 理想情况下,每帧根本不应该有任何新的内存分配定期发生。 然而,我们怎样才能实现这个目标呢? 这实际上是由应用程序架构决定的,但是您可以遵循一些有助于帮助的规则:
- 避免在更新循环中进行不必要的分配。
- 将结构用于简单的属性容器,因为它们不是在堆上分配的。
- 尝试预先分配数组或列表或其他对象集合,而不是在更新循环中创建它们。
- 避免使用单声道有问题的东西(例如 LINQ 表达式或 foreach 循环),因为 Unity 使用的是较旧的、未经过理想优化的 Mono 版本(在撰写本文时,它是经过修改的 2.6 版,并在路线图上进行了升级)。
- 在
Awake()
方法或事件中缓存字符串。 - 如果需要在更新循环中更新字符串属性,请使用 StringBuilder 对象而不是字符串。
- 使用分析器来识别潜在问题。
常见的 Unity 错误 #6:最后优化内存和空间使用
有必要从项目一开始就关注应用程序的最低内存和空间使用率,因为当您将优化留在预发布阶段时,这样做会更加复杂。 在移动设备上,这一点更为重要,因为我们那里的资源非常短缺。 此外,如果安装大小超过 100MB,我们可能会失去大量客户。 这是因为蜂窝网络下载有 100MB 的限制,也因为心理原因。 当您的应用程序不浪费客户宝贵的电话资源时总是更好,并且当应用程序较小时,他们更有可能下载或购买您的应用程序。
要查找资源消耗者,您可以使用编辑器日志,您可以在其中查看(在每次新构建之后)分为不同类别的资源大小,例如音频、纹理和 DLL。 为了更好地定位,Unity Asset Store 上有编辑器扩展,它将为您提供文件系统中引用的资源和文件的详细摘要。 实际内存消耗也可以在分析器中看到,但建议在连接到目标平台上构建时对其进行测试,因为在编辑器或目标平台以外的任何东西上进行测试时会出现很多不一致。
最大的内存消耗者通常是纹理。 最好使用压缩纹理,因为它们占用的空间和内存要少得多。 使所有纹理平方,理想情况下,使两边的长度为 2 的幂 (POT),但请记住,Unity 也可以自动将 NPOT 纹理缩放为 POT。 当处于 POT 形式时,可以压缩纹理。 Atlas 纹理一起填充整个纹理。 有时您甚至可以使用纹理 Alpha 通道为着色器提供一些额外信息,以节省额外空间和性能。 当然,尽量为场景重复使用纹理,并在可能保持良好视觉外观的情况下使用重复纹理。 对于低端设备,您可以在质量设置中降低纹理的分辨率。 对较长的音频片段(如背景音乐)使用压缩音频格式。
当您处理不同的平台、分辨率或本地化时,您可以使用资产包为不同的设备或用户使用不同的纹理集。 安装应用程序后,可以从 Internet 动态加载这些资产包。 这样,您可以在游戏期间通过下载资源来超过 100MB 的限制。
常见的 Unity 错误 #7:常见的物理错误
有时,当在场景中移动物体时,我们没有意识到物体上有一个碰撞器,改变它的位置会迫使引擎重新计算整个物理世界。 在这种情况下,您应该为其添加Rigidbody
组件(如果您不希望涉及外力,可以将其设置为非运动学)。
要修改带有Rigidbody
的对象的位置,请始终在新位置不跟随前一个位置时设置Rigidbody.position
,或在连续移动时设置Rigidbody.MovePosition
,这也考虑了插值。 修改它时,始终在FixedUpdate
中应用操作,而不是在Update
函数中。 它将确保一致的物理行为。
如果可能,请在游戏对象上使用原始碰撞器,例如球体、长方体或圆柱体,而不是网格碰撞器。 您可以从多个对撞机中合成最终的对撞机。 物理可能是应用程序的性能瓶颈,因为它的 CPU 开销和原始碰撞器之间的碰撞计算速度要快得多。 您还可以在时间管理器中调整固定时间步长设置,以在不需要物理交互的准确性时减少物理固定更新的频率。
常见的 Unity 错误 #8:手动测试所有功能
有时可能会倾向于通过在播放模式下进行试验来手动测试功能,因为它非常有趣,并且您可以直接控制一切。 但是这个很酷的因素会很快减少。 应用程序变得越复杂,程序员必须重复和考虑的任务就越繁琐,以确保应用程序的行为符合最初的预期。 由于其重复性和被动性,它很容易成为整个开发过程中最糟糕的部分。 此外,由于手动重复测试场景并不那么有趣,因此某些错误更有可能贯穿整个测试过程。
Unity 有很好的测试工具来自动执行此操作。 通过适当的架构和代码设计,您可以使用单元测试来测试孤立的功能,甚至可以使用集成测试来测试更复杂的场景。 您可以显着减少记录实际数据并将其与所需状态进行比较的尝试检查方法。
毫无疑问,手动测试是开发的关键部分。 但是它的数量可以减少,整个过程可以更加健壮和快速。 如果无法实现自动化,请准备好您的测试场景,以便能够尽快解决您要解决的问题。 理想情况下,点击播放按钮后的几帧。 实施快捷方式或作弊以设置所需的测试状态。 此外,将测试情况隔离起来,以确定是什么导致了问题。 游戏模式中每一次不必要的测试都在累积,测试问题的初始偏差越大,你就越有可能根本不测试问题,你会希望一切正常。 但它可能不会。
常见的 Unity 错误 #9:认为 Unity Asset Store 插件将解决您的所有问题
相信我; 他们不会。 在与一些客户合作时,我有时会面临过去使用资产商店插件处理每一件小事的趋势或遗留问题。 我并不是说 Unity Asset Store 上没有有用的 Unity 扩展。 它们有很多,有时甚至很难决定选择哪一个。 但是对于每个项目来说,保持一致性很重要,因为不明智地使用不能很好地组合在一起的不同部分可能会破坏一致性。
另一方面,对于需要很长时间才能实现的功能,使用 Unity Asset Store 中经过良好测试的产品总是很有用的,这可以为您节省大量的开发时间。 但是,请谨慎选择,使用经过验证的,不会给您的最终产品带来很多无法控制和奇怪的错误。 五星级评论是一个很好的开始。
如果您想要的功能不难实现,只需将其添加到您不断增长的个人(或公司)库中,以后可以在您的所有项目中再次使用这些库。 这样,您就可以同时提高您的知识和工具集。
Unity 常见错误 #10:无需扩展 Unity 基本功能
有时 Unity Editor 环境对于基本的游戏测试和关卡设计来说似乎已经足够了,扩展它是浪费时间。 但相信我,事实并非如此。 Unity 的巨大扩展潜力来自于能够使其适应各种项目中需要解决的特定问题。 这可以改善在 Unity 中工作时的用户体验,也可以显着加快整个开发和关卡设计工作流程。 不幸的是不使用内置功能,例如内置或自定义的属性抽屉、装饰器抽屉、自定义组件检查器设置,甚至不使用自己的编辑器窗口构建整个插件。
结论
我希望这些主题在您进一步推动 Unity 项目时对您有用。 有很多东西是项目特定的,所以它们不能被应用,但是在尝试解决更困难和更具体的问题时,记住一些基本规则总是有用的。 对于如何在项目中解决这些问题,您可能有不同的意见或程序。 最重要的是在整个项目中保持惯用语的一致性,以便团队中的任何人都可以清楚地了解应该如何正确解决特定领域。
进一步阅读 Toptal 工程博客:
- Unity AI 开发:有限状态机教程