本文概述
- 常见的Unity错误#1:低估了项目计划阶段
- 常见的Unity错误#2:使用未优化的模型
- 常见的Unity错误#3:构建相互依赖的代码架构
- 常见的Unity错误#4:浪费你的表现
- 常见的Unity错误#5:忽略垃圾回收问题
- 常见的Unity错误#6:最后优化内存和空间使用情况
- 常见的Unity错误#7:常见的物理错误
- 常见的Unity错误#8:手动测试所有功能
- 常见的Unity错误#9:认为Unity资源商店插件将解决你的所有问题
- 常见的Unity错误#10:无需扩展Unity基本功能
- 总结
Unity是用于多平台开发的出色而直接的工具。它的原理很容易理解, 你可以直观地开始创建产品。但是, 如果你不考虑某些事项, 那么当你从原型阶段开始或即将发布最终版本时, 它们会减慢你的工作进度, 使你的工作进入下一个阶段。本文将提供有关如何克服最常见问题以及如何避免新项目或现有项目中的基本错误的建议。请注意, 本文的观点更多地集中在3D应用程序开发上, 但是提到的所有内容也适用于2D开发。
Unity是用于多平台开发的出色而直接的工具。
鸣叫
常见的Unity错误#1:低估了项目计划阶段
对于每个项目, 至关重要的是在项目的应用程序设计和编程甚至开始之前就确定几件事。如今, 产品营销已成为整个过程的重要组成部分, 因此, 清楚地了解所实施应用程序的业务模式将是很重要的。你必须确定将要发布产品的平台以及计划中的平台。还需要设置最低支持的设备规格(你将支持较旧的低端设备还是仅支持较新的型号?), 以了解可以提供的性能和视觉效果。本文中的每个主题都受此事实影响。
从技术角度来看, 应该预先设置创建资源和模型的整个工作流程, 同时将其提供给程序员, 尤其是在模型需要更多更改和完善时, 特别注意迭代过程。你应该对所需的帧速率和顶点预算有清楚的了解, 以便3D艺术家可以知道模型必须具有的最大分辨率以及必须执行的LOD变化量。还应指定如何统一所有测量以具有一致的比例, 以及在整个应用程序中导入过程。
关卡的设计方式对于将来的工作至关重要, 因为关卡的划分会极大地影响性能。设计新级别时, 始终必须牢记性能问题。不要抱有不切实际的愿景。问自己一个问题:”能否合理实现?”始终很重要。如果不是这样, 你就不应将宝贵的资源浪费在难以实现的事情上(当然, 要把它作为主要的竞争优势不属于你的业务战略)。
常见的Unity错误#2:使用未优化的模型
至关重要的是, 要做好所有模型的准备, 以便能够在场景中使用它们而无需进一步修改。好的模型应该满足几件事。
正确设置比例尺很重要。有时, 由于这些应用程序使用的单位不同, 因此无法在3D建模软件中正确设置此设置。为了使所有设置正确, 请在模型导入设置中设置比例因子(3dsMax和Modo保留0.01, Maya设置1.0), 并请注意, 有时你需要在更改比例设置后重新导入对象。这些设置应确保你可以在场景中仅使用基本比例尺1、1、1、1来获得一致的行为并且没有物理问题。动态批处理也更有可能正确运行。此规则还应该应用于模型中的每个子对象, 而不仅仅是主要对象。当你需要调整对象尺寸时, 请针对3D建模应用程序中而不是Unity中的其他对象进行调整。不过, 你可以在Unity中尝试使用scale来找出合适的值, 但是对于最终应用程序和一致的工作流程, 在导入到Unity之前做好一切准备都是很好的。
关于对象的功能及其动态部分, 请对模型进行合理划分。子对象越少越好。分开对象的各个部分, 以防万一, 例如, 出于动画目的或其他交互作用而动态移动或旋转时。每个对象及其子对象的枢轴都应根据其主要功能正确对齐和旋转。主要对象的Z轴应指向前方, 枢轴应位于对象的底部, 以更好地放置到场景中。在对象上使用尽可能少的材料(更多信息请参见下文)。
所有资源均应使用适当的名称, 以方便地描述其类型和功能。在所有项目中保持这种一致性。
常见的Unity错误#3:构建相互依赖的代码架构
在Unity中对功能进行原型设计和实现非常容易。你可以轻松地拖放对其他对象的任何引用, 对场景中的每个对象进行寻址, 并访问其具有的每个组件。但是, 这也可能具有潜在的危险。除了明显的性能问题(在层次结构中查找对象和访问组件会产生开销)之外, 使代码的各个部分完全相互依赖也存在很大的危险。或者依赖于你的应用程序独有的其他系统和脚本, 甚至依赖于当前场景或当前方案。尝试采用更具模块化的方法, 并创建可重用的部件, 这些部件可在应用程序的其他部分使用, 甚至在整个应用程序组合中共享。以与构建知识库相同的方式在Unity API之上构建框架和库。
有很多方法可以确保这一点。一个很好的起点是Unity组件系统本身。当特定组件需要与应用程序的其他系统进行通信时, 可能会出现复杂情况。为此, 你可以使用接口使系统的各个部分更加抽象和可重用。另外, 你可以使用事件驱动的方法来响应外部作用域中的特定事件, 方法是创建一个消息传递系统, 或者通过直接注册到其他系统的某些部分作为侦听器。正确的方法是尝试将gameObject属性与程序逻辑分开(至少类似于模型控制器原理), 因为很难确定哪些对象正在修改其变换属性, 例如位置和旋转。它应完全由其控制人负责。
尝试使所有内容都有据可查。始终将其视为好象你应该在很长一段时间后返回代码一样, 并且需要快速了解代码的这一部分到底在做什么。因为实际上, 一段时间后你会经常进入应用程序的某些部分, 这对于快速跳入问题是不必要的障碍。但是不要过分。有时, 适当的类, 方法或属性名称就足够了。
常见的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.position, 或者在连续移动时设置Rigidbody.MovePosition, 如果它连续移动, 则还要考虑插值。进行修改时, 请始终在FixedUpdate中应用操作, 而不是在Update函数中应用操作。这将确保一致的物理行为。
如果可能, 请在游戏对象(例如球体, 盒子或圆柱体)上使用基本碰撞器, 而不是网格碰撞器。你可以使用多个对撞机中的一个来组成最终的对撞机。物理可能会成为你应用程序的性能瓶颈, 因为它的CPU开销以及原始碰撞器之间的碰撞计算起来要快得多。你也可以在”时间管理器”中调整”固定时间步长”设置, 以在不需要物理交互精度时减少物理固定更新的频率。
常见的Unity错误#8:手动测试所有功能
有时可能会倾向于通过在播放模式下进行实验来手动测试功能, 因为这很有趣并且你可以直接控制所有内容。但是这个很酷的因素可以很快降低。应用程序变得越复杂, 程序员必须重复并思考的繁琐任务, 以确保应用程序按照其最初的意图运行。由于其重复性和被动性, 它很容易成为整个开发过程中最糟糕的部分。另外, 由于手动重复测试场景并不是那么有趣, 因此, 在整个测试过程中出现某些错误的可能性更高。
Unity具有出色的测试工具可以自动执行此操作。借助适当的体系结构和代码设计, 你可以使用单元测试来测试隔离的功能, 甚至可以使用集成测试来测试更复杂的场景。在记录实际数据并将其与所需状态进行比较时, 你可以大大减少尝试检查的方法。
毫无疑问, 手动测试是开发的关键部分。但是它的数量可以减少, 并且整个过程可以更健壮和更快。如果无法实现自动化, 请准备好测试场景, 以尽快解决你要解决的问题。理想情况下, 单击播放按钮后几帧。实现快捷方式或作弊设置以设置所需的测试状态。另外, 请隔离测试情况, 以确保引起问题的原因。累积测试时间后, 播放模式中每隔不必要的时间, 并且测试问题的初始偏差越大, 你根本不会测试该问题的可能性就越高, 你将希望一切正常。但这可能不会。
常见的Unity错误#9:认为Unity资源商店插件将解决你的所有问题
相信我;他们不会。与某些客户一起工作时, 我有时会遇到过去对每件小东西都使用资源商店插件的趋势或遗留问题。我并不是说Unity资源商店中没有有用的Unity扩展。它们很多, 有时甚至很难决定要选择哪一个。但是对于每个项目, 保持一致性很重要, 如果不明智地使用不太合适的不同部件, 可能会破坏一致性。
另一方面, 对于要花很长时间才能实现的功能, 使用Unity Asset Store中经过良好测试的产品总是有用的, 这可以为你节省大量的开发时间。但是, 请仔细选择并使用经过验证的方法, 它们不会给最终产品带来很多无法控制的怪异错误。五星级的评论是一个良好的开端。
如果你不难实现所需的功能, 只需将其添加到不断增长的个人(或公司)库中, 即可在以后的所有项目中再次使用。这样, 你就可以同时提高自己的知识和工具集。
常见的Unity错误#10:无需扩展Unity基本功能
有时候, 看来Unity Editor环境足以进行基本的游戏测试和关卡设计, 并且进行扩展很浪费时间。但是请相信我, 事实并非如此。 Unity的巨大扩展潜力来自能够使其适应各种项目中需要解决的特定问题。这可以改善在Unity中工作时的用户体验, 也可以大大加快整个开发和关卡设计工作流程的速度。不幸的是, 不使用内置功能, 例如内置或自定义属性抽屉, 装饰器抽屉, 自定义组件检查器设置, 甚至不使用自己的编辑器窗口构建整个插件。
总结
我希望这些主题对你进一步推进Unity项目很有用。很多事情都是特定于项目的, 因此无法应用, 但是在尝试解决更困难和更具体的问题时, 记住一些基本规则总是很有用的。你可能对如何解决项目中的这些问题有不同的意见或过程。最重要的是在整个项目中保持习惯用法一致, 以便团队中的任何人都可以清楚地了解应如何正确解决特定领域。
评论前必须登录!
注册