本文将为大家讲解在游戏中使用物理的最佳实践,并通过一些案例来演示为何要这么用。
层(Layer)与碰撞矩阵

所有的游戏对象创建时如果未设置层,则默认位于Default层,位于Default层表示可以与一切事物发生碰撞。这样做非常低效。我们应当为各层设定好能够发生碰撞的对象。为此,应当为不同类型的对象定义不同的层级。每新添加一个层级,碰撞矩阵中就会新添加一行和一列。该矩阵用于定义不同层之间的交互关系。默认情况下,新添加的层在碰撞矩阵中被设置为可以与所有层发生碰撞,所以开发者需要手动设置新层间的交互关系。正确设置好层和碰撞矩阵,可以避免很多不必要的碰撞和碰撞检测。这里创建了简单的Demo进行演示,Demo是在一个盒状容器中实例化了2000个对象(1000个红色,1000个绿色)。物体只会与相同颜色的物体以及容器壁产生交互。在Demo的其中一个测试中,所有物体的层都设为Default,然后通过比较碰撞函数中不同游戏对象的标签字符串来判断是否进行交互。而在另一个测试中,每种颜色的物体都位于单独的层,然后设置碰撞矩阵来定义层间的交互。这样就不需进行字符串比较了,碰撞只会发生在正确的层上。

094533heshyovyyzzss8hu.png
图1:碰撞矩阵配置

下图是Demo运行截图。用一个简单的管理器来统计所有的碰撞次数,并于5秒后自动暂停。结果相当的难以置信,使用相同层会产生大量不必要的碰撞。

094533lo6jr66ssurkspuz.png
图2:五秒的碰撞次数

这里还通过Profiler中的物理引擎来了解更详细的数据。

094534ujmr2ff2gn5z6p8m.png
图3:相同层和不同层在物理分析器中的数据比较
正如在分析器中所观察到数据情况,两个测试对于物理计算上的CPU消耗截然不同。使用相同层平均消耗约27.7毫秒,不同层平均消耗约17.6毫秒。

射线投射(Raycast)

射线投射是物理引擎中一个非常有用且强大的工具。使用它可以向特定方向发射一条特定长度的射线,通过这个射线就能知道它是否击中某物。然而这样的操作会产生很大的性能消耗。它的性能会受到在场景中射线的长度和碰撞器类型的巨大影响。

这里有一些提示,可以帮助我们更好的使用它。

第一条很明显,就是使用尽可能少的射线来完成。 按需设置射线长度,不要过长。射线越长,需要与之进行碰撞检测的物体就越多。 不要在FixedUpdate()函数中使用射线投射,有时即使是在Update()函数中,射线投射也会导致过多的消耗。 注意使用碰撞器的类型,射线投射与网格碰撞器进行交互的性能消耗是相当大的。 比较好的解决方案是使用多个基础碰撞器组合出网格形状。可以将父节点Rigidbody下所有的子碰撞器作为混合碰撞器使用。 如果非得使用网格碰撞器,至少将其设为凸面。 为了明确射线会碰撞到哪些物体,应当在Raycast函数中指明层级遮罩(Layer Mask)。 这在官方文档中给出了详细说明,在Raycast函数指定的是位掩码而非层ID。 如果希望射线射向ID为10的层,在Raycast函数中应当设置参数为1 << 10(将 1 左移10位)而非直接设为10。 如果希望射线击中除了层为10以外的所有对象,可以使用位运算符(~)轻松对位掩码按位取反。

下面的Demo展示了射线只与绿色盒子发生碰撞的情况。

094535qfljwksjodeettkz.png
图4:简单的射线投射Demo场景。

通过操作射线的数量和长度,来验证之前提到的数据分析。可以从下图中发现,射线的数量和长度对性能影响极大。

094535zomz0ycj5yjohm3t.png
图5:射线数量对性能的影响

094535qv0iigjiolhumkgj.png
图6:射线长度对性能的影响

下面演示使用网格碰撞器代替基础碰撞器的效果。

094538bbc3t3s333tbsn3c.png
图7:网格碰撞器场景(每个碰撞器有110个顶点)

094539li2fdkwf2wf1d23c.png
图8:基础碰撞器和网格碰撞器在物理分析器中的情况

正如分析器截图所示,射线投射与网格碰撞器的碰撞检测在每帧都增加了不少工作。

2D与3D物理系统的比较

为项目选择最合适的物理引擎,如果只开发2D游戏或2.5D游戏(2D平面上的3D游戏效果),使用3D物理引擎就太浪费了。这样会为项目带来额外的不必要的CPU消耗。可以使用下面文章中提到的方法来检测使用不同的物理引擎所带来的消耗:

http://x-team.com/2013/11/unity3d-v4-3-2d-vs-3d-physics/

刚体(Rigidbody)

Rigidbody是为对象间添加物理交互必不可少的组件。即使将碰撞器作为触发器使用,也需要为游戏对象添加刚体组件,以便OnTrigger 事件函数可以正常工作。没有刚体组件的游戏对象会被视为静态碰撞器。了解这点非常重要,因为尝试移动静态碰撞器会带来很大的性能消耗,它会迫使物理引擎重新计算整个物理世界的数据。幸运的是,当移动静态碰撞器时,分析器会在CPU分析器上添加一个警告标签给予提示。为了更好的展示移动静态碰撞器所带来的影响,我移除了之前第一个Demo中所有移动对象的Rigidbody组件,然后通过分析器重新获取数据。

094540a3ppzdtlluuc855j.png
图9:移动静态碰撞器的警告

如图所示,总计生成了2000个警告,每次移动游戏对象都会产生警告。CPU在物理计算上所耗费的平均时间约从17.6毫秒增长至35.85毫秒,这个增长相当惊人。因此,当移动一个游戏对象时,必须为其添加刚体组件。如果想直接控制物体的移动,只需简单的勾选Rigidbody组件的的Is Kinematic属性即可。

Fixed Timestep

调整Time Manager中的 Fixed Timestep值,会直接影响FixedUpdate()函数 和物理引擎的更新率。调整该值可以在物理系统的精确度和CPU耗时之间达到一个良好的平衡。

总结

本文所讨论的主题都可以轻松配置或实现,并且会为您的项目带来显著的性能提升,因为几乎所有项目都会用到物理引擎,即时只是碰撞检测也会有所区别。

社区作者Ricardo Aguiar

Ricardo Aguiar是来自Azorean的开发者,在视频游戏行业拥有超过7年的专业经验。他从完成硕士论文《使用跟踪和可视化设备开发游戏》开始了游戏开发之旅。随后加入种子工作室,致力于开发任天堂DS,iOS和PS3平台的第一款葡萄牙语游戏《围攻(Under Siege)》。之后加入X-Team工作,并使用Unity引擎开发移动平台游戏。


原文链接:http://unity3d.com/cn/learn/tutorials/topics/physics/physics-best-practices?playlist=30089
感谢Unity官方翻译组成员“mnfengqing”对本文翻译所做的贡献。
转载请注明来源:Unity官方中文社区 (forum.china.unity3d.com)。请勿私自更改任何版权说明信息。




Unity, 物理锐亚教育

锐亚教育 锐亚科技 unity unity教程