Physics Best Practices 物理最佳实践
本文档主要是对Unity官方手册的个人理解与总结(其实以翻译记录为主:>)
仅作为个人学习使用,不得作为商业用途,欢迎转载,并请注明出处。
文章中涉及到的操作都是基于Unity2018.3版本
参考链接:https://unity3d.com/cn/learn/tutorials/topics/physics/physics-best-practices?playlist=30089
In this lesson we will be looking at some best practices for when using physics in a game and some evidence to demonstrate why they should be used.
在这节课中,我们将会看到在游戏中一些使用物理的最佳实践,以及一些可以证明为什么应该使用它们的证据。
Layers and Collision Matrix
层与碰撞矩阵
All game objects, if not configured, are created on the Default layer where (by default) everything collides with everything. This is quite inefficient. Establish what should collide with what. For that you should define different Layers for each type of object. For each new layer, a new row and column is added on the Collision Matrix. This matrix is responsible for defining interactions between layers. By default, when adding a new layer, the Collision Matrix is set for that new layer to collide with every other existing one, so it’s the developer’s responsibility to access it and setup its interactions. By correctly setting layers and setting up your Collision Matrix, you will avoid unnecessary collisions and testing on collision listeners. For demonstration purposes I’ve created a simple demo where I instantiate 2000 objects (1000 red and 1000 green) inside a box container. Green objects should only interact with themselves and the container walls, the same with red objects. On one of the tests, all the instances belong to the Default layer and the interaction are done by string comparing the game objects tag on the collision listener. On another test, each object type is set on their own Layer, and I configure each layer’s interaction through the collision matrix. No string testing is needed in this case since only the right collisions occur.
所有的游戏对象,如果没有配置,都是在Default 层上创建的,所有的东西都会与其他所有的东西发生碰撞。这是非常低效的。首先应该确定什么与什么相碰撞。为此,您应该为每种类型的对象定义到不同的层。对于每个新层,在碰撞矩阵上将会添加新的行和列。这个矩阵负责定义层之间的交互关系。默认情况下,当添加一个新层时,碰撞矩阵被设置为该新层与其他所有现有层都会发生碰撞,因此开发人员的责任就是使用它并设置它的交互关系。通过正确地设置层和碰撞矩阵,您将避免不必要的碰撞和对碰撞侦听器的验证。出于演示的目的,我创建了一个简单的demo ,其中在一个盒子容器中实例化了2000个对象(1000个红色和1000个绿色)。绿色对象只应该与自己及容器墙壁交互,红色对象也是如此。在其中一个测试中,所有实例都属于默认层,交互是通过比较碰撞侦听器上的game objects的tag来完成的。在另一个测试中,每个对象类型都设置在它们自己的层上,通过碰撞矩阵配置每个层的交互。在这种情况下不需要字符串测试,因为只有正确的碰撞才会发生。
Figure 1 : Collision Matrix config 碰撞矩阵配置
The image below is taken from the demo itself. It has a simple manager which counts the amount of collisions and automatically pauses after 5 seconds. It’s quite impressive the amount of unnecessary extra collisions occurring when using a common layer.
下面的图片来自Demo本身。它有一个简单的管理器,可以计算冲突的数量,并在5秒后自动暂停。在使用同一层时,会发生大量不必要的额外碰撞。
Figure 2 : Collisions amount over 5 seconds
For more specific data I also captured profiler data on the physics engine.
对于更详细的数据,还可以在物理引擎上捕获了分析器数据。
Figure 3 : Common vs Separate Layers physics profiler data
There’s quite a difference on the amount of CPU spent on physics, as we can see from the profiler data, from using a single layer ( avg ~27.7 ms ) to separate layers ( avg ~17.6ms ).
在CPU上的物理用时也是相当不同的,从分析器数据上看,同一层平均为27.7ms,不同层平均为17.6ms。
Raycasts 光线投射
Raycasting is a very useful and powerful tool available on the physics engine. It allows us to fire a ray on a certain direction with a certain length and it will let us know if it hit something. This however, is an expensive operation; its performance is highly influenced by the ray’s length and type of colliders on the scene.
光线投射是物理引擎上非常有用和强大的工具。它允许我们向特定方向以特定长度发射光线,如果它击中了什么,便会让我们知道。然而,这是一个昂贵的操作;它的性能受场景中光线的长度和碰撞体类型的影响很大。
Here are a couple of hints that can help on its usage.
有一些提示可以帮助我们了解它的用法。
- This one is obvious, but, use the least amount of rays that gets the job done.
明显的,用尽量少的光线完成工作。 - Don’t extend the ray’s length more than you need to. The greater the ray, the more objects need to be tested against it.
不要光线长度超过你需要的。越长,需要测试的物体越多。 - Don’t use use Raycasts inside a FixedUpdate() function, sometimes even inside an Update() may be an overkill.
不要在FixedUpdate()函数中使用,有时在Update()里也是多余的,尽量不要使用。 - Beware the type of colliders you are using. Raycasting against a mesh collider is really expensive.
注意你使用的碰撞体类型。对于网格碰撞体耗时是昂贵的。- A good solution is creating children with primitive colliders and try to approximate the meshes shape. All the children colliders under a parent Rigidbody behave as a compound collider.
一个好的解决方案是:用元碰撞体创建子集并尽量近似网格形状。父刚体下的所有子碰撞体表现得像个复合碰撞体。 - If in dire need of using mesh colliders, then at least make them convex.
如果一定要用网格碰撞体,至少确保为凸体。
- A good solution is creating children with primitive colliders and try to approximate the meshes shape. All the children colliders under a parent Rigidbody behave as a compound collider.
- Be specific on what the ray should hit and always try to specify a layer mask on the raycast function.
明确光线投射到哪些东西,并以层蒙版指出是在哪些层上投射计算。- This is well explained on the official documentation but what you specify on the raycast function is not the layer id but a bitmask.
官方文档有很好的解释,但要注意raycast函数指向的是个蒙版不是某层。 - So if you want a ray to hit an object which is on layer which id is 10, what you should specify is 1<<10 ( bit shifting ‘1’ to the left 10x ) and not 10.
所以,你想投射到层为10的物体上,你就当指向1<<10,而不是10。 - If you want for the ray to hit everything except what is on layer 10 then simply use the bitwise complement operator (~) which reverses each bit on the the bitmask.
如果让光线投射到第10层之外的所有对象上,只需要用位补运行符~,它会反转位掩码bitmask上的每一位。
- This is well explained on the official documentation but what you specify on the raycast function is not the layer id but a bitmask.
I’ve developed a simple demo where an object shoots rays which collide only with green boxes.
我开发了一个简单的demo,一个物体投射光线用于碰撞绿色盒子。
Figure 4 : Simple Raycast demo scene
From there I manipulate the number and length of the rays in order to get some profiler data to backup what I’ve written earlier. We can see from the graphics below the impact on performance that the number of rays and their length can have.
我操作光线的数量与长度为了获取一些分析器数据去备份我之前记录的。可以在下图中看出随着光线的数量与长度变化性能的影响。
Figure 5 : Number of rays impact on performance
Figure 6 : Rays length impact on performance
Also for demonstration purposes, I decided to make it able to switch from a normal primitive collider into a mesh collider.
同时演示的目的,我决定把一个正常的元碰撞体转成网格碰撞体。
Figure 7 : Mesh Colliders scene ( 110 verts per collider )
Figure 8 : Primitive vs Mesh colliders physics profiler data
As you can see from the profile graph, raycasting against mesh colliders makes the physics engine do a bit more work per frame.
正如分析图形所示,光线投射用于网格碰撞体每帧要做更多的工作。
Physics 2D vs 3D
Choose what Physics engine is best for your project. If you are developing a 2D game or a 2.5D (3D game on a 2D plane), using the 3D Physics engine is an overkill. That extra dimension has unnecessary CPU costs for your project. You can check the performance differences between both engines on a previous article I wrote specifically on that subject:http://x-team.com/2013/11/unity3d-v4-3-2d-vs-3d-physics/
选择适合你项目的最佳物理引擎。如果你开发一个2D游戏或2.5D(3D游戏在2D平面),使用3D物理引擎是多余的。额外的维度对你项目的CPU耗时是不必要的。你可以通过之前的专门介绍的文章中对比它们之间的性能不同之处。
Rigidbody刚体
The Rigidbody component is an essential component when adding physical interactions between objects. Even when working with colliders as triggers we need to add them to game objects for the OnTrigger events to work properly. Game objects which don’t have a RigidBody component are considered static colliders. This is important to be aware of because it’s extremely inefficient to attempt to move static colliders, as it forces the physics engine to recalculate the physical world all over again. Fortunately, the profiler will let you know if you are moving a static collider by adding a warning to the warning tab on the CPU Profiler. To better demonstrate the impact when moving a static collider, I removed the RigidBody of all the moving objects on the first demo I presented, and captured new profiler data on it.
刚体组件是对象之间物理交互必不可少的。即使碰撞体作为触发器时,我们也要把它们添加到对象上,让OnTrigger事件能够正常工作。没有刚体组件的游戏对象被认为是静态碰撞体。这一点很重要:尝试移动静态碰撞体效率是极低的,它会强制物理引擎重计算一遍物理世界。幸运的是,分析器会在你移动静态碰撞体时通过CPU 分析器的警告选项卡添加一个警告。为了更好演示移动静态碰撞体的影响,我移除了所有可移动对象的刚体在我之前呈现的demo中,并且捕捉新的分析器数据。
Figure 9 : Moving static colliders warning
As you can see from the figure, a total amount of 2000 warnings are generated, one for each moving game object. Also the average amount of CPU spent on Physics increased from ~17.6ms to ~35.85ms, which is quite a bit. When moving a game object, it’s imperative to add a RigidBody to it. If you want to control its movement directly, you simply need to mark it as kinematic on its rigid body properties.
从图中看出,共生成了2000个警告,每个移动的对象一个。CPU在物理上耗时平均值从17.6ms到35.85ms,相当大的数字。当移动一个游戏对象,添加刚体是必要的。如果你想直接控制它的运行,只需要把它的刚体属性作为运动学(kinematic)。
Fixed Timestep 固定时间步
Tweak the Fixed Timestep value on the Time Manager, this directly impacts the FixedUpdate() and Physics update rate. By changing this value you can try to reach a good compromise between accuracy and CPU time spent on Physics.
调整时间管理器上的固定时间步长值,这将直接影响FixedUpdate()和物理更新速率。通过改变这个值,您可以尝试在精度和CPU花费在物理上的时间之间达成一个很好的折衷。
Wrap-up 小结
All of the discussed topics are really easy to configure/implement and they will surely make a difference on your projects’ performance as almost every game you’ll develop will use the physics engine, even if it’s only for collision detection.
上面讨论的所有主题都非常容易配置/实现,它们肯定会对您的项目的性能产生影响,因为几乎您将要开发的每一款游戏都将使用物理引擎,即使它只是用于碰撞检测。