前言
本文翻译自一位西班牙的美女博主Lidia Martinez Prado ,她是一位有着丰富从业经历的图形学工程师 ,同时也是一位技术总监和技术美工 。由于博主看到关于 FixedUpdate & FixedTimeStep & Maximum Allowed Timestep 的相关提问常年占据着Unity官方论坛 ,而论坛上的解释又十分摸棱两可(官方文档同样也是) ,浅显而不深入的回答并没有从根本上解决提问者的疑惑 。造成这种情况的原因是 ,除了小部分的开发者,大部分开发者自身对FixedUpdate & FixedTimeStep & MaximuVm Allowed Timestep的运作机制都不甚清晰 。所以为了能够帮助更多的开发者了解其背后的运行机制 ,Lidia Martinez Prado在其博客上发表了这篇文章 ,同时在Youtube上上传了配套的教学视频 .
Understanding FixedUpdate, FixedTimeStep and Maximum Allowed Timestep in Unity
This post is for everybody who needs a longer and more detailed explanation on this concepts in Unity, and for those who think they know but want to check if they are right...
理解FixedUpdate , FixedTimeStep 和 Maximum Allowed Timestep
这篇博文是为所有有需要的人准备的 ,无论是寻求对该概念进行充分解释的人,还是想验证自己的理解是否正确的人 ,这篇文章都会帮助到你 !
Introduction: Are you sure you understand it?
Everybody thinks they understand the difference and how it works, but the truth is that when people have serious problems with syncronization, physics, animation, inaccuracy of physics and such, they are forced to finally understand deeply what FixedUpdate(), Update(), Maximum Time Step, Time Step size and such concepts truly mean.
And sadly, it is a very important concept that everybody should understand in order to make games. You need to know exactly how all these concepts work.
Even when I thought I completely understood it, I had to come back and study it again after trying to explain it to my colleagues. It's common to talk with somebody who says "Oh yes, FixedUpdate(), I understand how it works", it doesn't take that much time to find out they don't. It needs quite a bit of brainpower to get it and most of the programmers I know don't spend much time understanding this.
Will you be like them? Hopefully I grabbed your attention.
话说 : 你真的理解它吗 ?
许多人认为自己已经理解了这些不同概念之间的差异,以及其是如何运作的。不过,实际上,人们只有在遇到严重问题时才会意识到自己的认知是浅显且不可靠的 ,这些严重的问题涉及到游戏的同步、物理模拟 、动画、物理系统运行的不精确等 ,这些问题才会最终迫使开发者去深入的理解FixedUpdate()、Update()、Maximum Time Step、Time Step size,理解这些概念的真正意义 。
可是非常令人遗憾的是 ,这些概念是非常重要的 ,重要到每一个想做游戏开发的人都应该理解 。你需要准确的理解这些概念,并且知晓它们的工作原理 !
即使我认为我自己十分了解这些概念 ,我也不得不在尝试向我的同事解释它之后 ,反过来自己在学习一遍 ;你常常会遇到很多人,ta们会跟你说:"Oh,yes,我理解FixedUpdate()是怎样工作的",事实上 ,你不需要花费多长时间进行交流,就会发现,这种理解只能算是半桶水 ;理解这些概念的确是需要耗费一些大脑能量,但是,我认识的大多数程序员并没有花费太多的时间去搞定它 ;
你想和他们一样吗 ? 希望我勾起了你的好奇心 !
YouTube Video
I made a video out of this blog post if you are interested in listening instead of reading. This blog post is more accurate and more carefully written, and the english is probably much better, but please feel free to visit the video instead:
除了这篇文章 , 我还制作了一个视频,以方便那些更偏好视频方式学习的人 。通常意义上来说 ,这篇文章的表述会更精确,因为我仔细斟酌了自己的用词 ,并且文章中的英语表达或许更好 。不过,如果你偏好视频,还是请放轻松去浏览视频:
A frame in Unity
这或许是最重要,也是我最常访问的网页之一 :
This is the simplified version of a frame in Unity(在Unity中 ,每一帧都可以简化为以下流程 ) :
As the arrows depict, this cycle is repeated all the time. Once every frame. I guess you understand that if your app runs at 60fps it means it executes this cycle 60 times...
Well. In fact, the previous image is not true. The truth is that it runs this way:
正如箭头所描绘的 ,这是一个不断进行循环的流程 ,每一帧都会执行一次 ;例如你的应用帧率为60fps ,那么代表这个流程每秒将会被执行60次,每帧都执行一次 ;
不过 ,事实上 ,上述的流程仅仅只是简化版 ,真正的流程是这样的 :
Sometimes the physics step is not run at all in a frame and sometimes one or more times!. How many, why?. Let's first see in detail what each section does:
有时候的帧处理中,并不包含Physics这一步,但有时候的帧处理中却可以包含多次Physics! 为什么会这样,如何决定每帧到底执行多少次Physics ? 让我们先来看一下每一个步骤的具体细节 :
As you can see, with the asterik symbol, that the physics part is equivalent basically to running FixedUpdate() method for all components and everything related to them. And it is executed from 0 to N times.
Then goes the game logic where Update() is executed just once and always once. At the end of the game logic LateUpdate() is called (after all Updates() are called for all components and more stuff), then we move on to rendering all the parts of the image, then the end of frame is executed, then we come back to physics again
There are many other things happening inbetween, but these are the ones that matter to us right now.
正如你所见的 ,图中的*号表明 ,physics部分执行的流程
接下来的部分是game logic(游戏逻辑) ,在这里 ,Update()只会执行一次 ,并且在每一帧中的game logic部分 ,也都将永远只执行一次 ;在game logic部分的末尾 ,LateUpdate()会被调用执行(当所有组件中的Update()调用完成之后)。然后是Rendering的部分 ,这一步将把所有的图像渲染到屏幕上 。最后执行的是the end of frame ,然后执行流程回到开头的physics部分,继续循环执行 ;
其实 ,上述的流程也只是简化的版本 ,只不过在上述的流程中我们全面的介绍了与我们的文章主题有关的部分 。在每一帧中 ,还有许多事情(步骤)发生在上述流程之间 ,不过那些事情并不是我们现在所应关心的 ;
How long is each frame?
If you open the Stats window on the Game View, the time that your frame takes is 0.8ms to execute and draw completely and your game runs at 1260.8 frames per second.
That is the sum of all the steps in the frame, including the N physics steps done that frame.
The time spent on executing a frame is NEVER the same. That is why the profiler is not a perfect line, every frame takes a different time to execute.
The duration in ms of each physics part depends on the complexity of the physics calculations in Unity as well as your code inside FixedUpdate() functions in all enabled MonoBehaviours (components).
So why are physics executed 0 times to N times instead of just one? Here's the key concept. Probably the most difficult question to answer.
Basic concept : Game Time vs Wall Clock Time(基本概念:Game Time vs Wall Clock Time)
This is absolutely key to understand these concepts and allows you to understand the rest of the concepts.
这个概念绝对是你理解上文概念的关键所在 ,并且让你可以顺畅理解下文中的概念 ;
"Game time" is the time passed inside the game world. It is different from the time passing for us, the human players, people using the computer, which is called "wall clock time".
"Game Time"是我们所创造的游戏世界中才有的时间概念。它和我们身处真实世界所经历的时间是不同的 ,人类玩家,人类操作计算机 ,这种时间我们称作"wall clock time" ;
An app is said to be "realtime" when time in the game world passes at the same speed as in the real world. 10 seconds inside the game will be 10 seconds for us too aproximately.
当一个游戏被称作"实时"的时候 ,就意味着游戏世界中的时间(game time)流逝和现实世界中的时间(wall clock time)流逝在速度上保持一致(或者说几乎一致) 。例如,游戏中的10秒钟和我们所经历的10秒钟是几乎一致的(所以这段游戏历程可以被认为是实时的) 。
If the game slows down because of lag in the network, a slow computer, or the physics complexity at a certain moment, the game time will not catch up with time in the real world.
当遇到网络延迟,电脑性能不足,或者物理系统在某一时刻的处理压力过大 ,那么就会导致游戏在运行上的迟钝 ,这时候game time将不会和现实世界中的时间保持一致 ;
What are FixedDeltaTime and DeltaTime ?
Have you ever used Time.fixedDeltaTime and Time.deltaTime to access the amount of time passed?.
你有没有使用过 Time.fixedDeltaTime 和 Time.deltaTime 去获取时间的流逝 ?
DeltaTime should be called inside Update() or related functions, but it is rarely used inside FixedUpdate(). It is used to get the game time from the previous Update(). Careful with this: Not the real time passing for us, the humans playing the game.
DeltaTime应该被使用在Update()及其相关联的函数的内部 , 不过它却很少被用在FixedUpdate()的内部 。它被用于获取自先前一帧的Update()到目前帧的Update()所经历的game time . 请注意 :获取的不是真实世界的时间(wall clock time),而是游戏世界中的时间(game time) ;
This missunderstanding is the origin of the top one question asked by beginners: Why is my procedural animation, calculation, not executing smoothly?. Every calculation you do in your Update() function needs to be scaled by Time.deltaTime, so that the movement is relative to the time passed in-game, from the last frame. If the last frame was executed very fast, your animation should advance very little too. Always multiply your values by Time.deltaTime to get a really smooth and consistent movement.
对上述概念的不当理解 ,是导致这个问题被初学者一直提起的罪魁祸首 ,这个问题是 : 为什么我的程序化动画计算,没有顺滑的执行 ? 事实上 ,你在Update()中做的(运动方面的)计算都应该乘以(即scale)Time.deltaTime,所以才会使gameObject的运动与游戏中自上一帧流逝的时间建立正确适当的联系。如果上一帧非常快的执行完了(即快速的短时间内完成了上述的帧流程),那么你的物体运动就同样应该按程序化只前进一点点 ;所以,请铭记 ,当你执行运动计算时 ,总是应当乘以Time.deltaTime以便获取顺滑且连续性的运动 ;
On the other hand, Time.fixedDeltaTime always returns the same value. And it is the same as the value set in the Time Settings (unless you call it from the Update() function, which returns deltaTime instead, something absolutely confusing and that they should change asap).
从另一方面来说 ,Time.fixedDeltaTime总是会返回相同的值 。并且这个值就是我们在Time Setting里面设置过的(除非你尝试在Update()里调用它,但这时返回的值其实并不是Time.fixedDeltaTime,而时被自动替换为Time.deltaTime返回,这是一件十分令人感到困惑的事情,应当尽快修复这个问题);
Let's first open the Time settings, something very important that you have to understand. (Edit -> Project Settings -> Time).
让我们打开Time settings , 这里有一些十分重要的东西 ,你需要去理解 . ((Edit -> Project Settings -> Time).)
These are the default values. The ones that are important to us are Fixed Timestep and Maximum Allowed Timestep. I think most of the people play with these numbers without knowing what they are doing. But these can absolutely break physics, destroy the efficiency of your game or even stall it to its death if you don't know what you are achieveing with them.
这些都是默认的值 。对我们最重要的设置项是 Fixed Timestep 和 Maximum Allowed Timestep .我认为大多数开发者都会与这些参数打交道 ,却并不知道它们的作用是什么 。不过这些设置项如果设置的不正确,绝对能使你的物理引擎崩溃,摧毁游戏的运行效率,甚至使你的游戏崩溃卡死。
What value should we set Fixed Timestep to?
The value is set in seconds. 0.02 means 20 ms. We will find out which values are the best.
First, these are the rules:
- It has to be over 0
- The smaller the value, the more accurate and stable the physics will be, but the efficiency of your game will decrease, sometimes enough to stall or even break the game.
- A bigger value improves efficiency in your game, but increases the chances of physics being more and more unstable.
This is the key to understand everything:
The fixed timestep doesn't represent the "wall clock time" we give it to execute physics, as most people think. It represents the amount of "game time" that the physics have to calculate on each step. The "wall clock time" it takes to execute a physics step is unrelated to the amount of "game time" simulated by that step.
So remember this:
Fixed Time Step = Amount of game world time advanced in one physics step (one call to FixedUpdate)
这些值的设定是以秒为单位的 ,0.02意味着20ms 。我们将会探讨如何设置最合适的Fixed Timestep .
首先 ,必须遵循下列的规则 :
- 值必须是大于0的 ;
- 值越小 ,每帧开头就会尽可能的执行跟多的physics步骤 ,这就会使得物理模拟更加的精确且稳定 ,不过代价却是 ,这会削弱游戏的运行效率(物理系统会占用大量的运行资源) ,有时候甚至会使游戏卡死,或者直接崩溃 ;
- 值越大 ,游戏的运行效率会显著提升 ,因为每帧开头倾向于更少次数的physics调用(意味着物理系统占用更少的系统资源) ,不过这会增加物理模拟的不稳定性和不精确性 ;
这是理解所有事情的关键之处 :
大部分人都会误认为Fixed Timestep是"wall clock time" ,并且规定了每一帧中的每一步physics的处理时长都为这个"wall clock time" 。Fixed Timestep代表的是game time ,是处理每一步physics在理论上所必须要花费的时长;执行每一步physics所要花费的"wall clock time"是和"game time"无关的 ;
原作者的这段表述初看起来有点懵,我来进行以下补充解释吧:
现实世界中 ,物理现象是每时每刻都在进行的 ,我们称之为实时的物理模拟;而在游戏世界中,我们需要一种机制来模拟这种实时性(将在下文介绍这种机制) ;正如上述的 , Fixed Timestep并不表示处理每一步physics所需要花费的"wall clock time" , 它表示的是game time ,是用来衡量上一帧的总运行game time,从而得出下一帧的开头部分需要执行几步physics(追上上一帧流逝的game time,从而模拟这种实时性),而每一步physics所需要执行的"wall clock time"是受多方面因素的影响 , 比如那一刻的cpu负载等等 ;在下文会详细介绍这种机制 , 这里只需简单理解即可 ;
所以请铭记这一点 :
Fixed Time Step = 处理每步physics(会调用一次FixedUpdate())所花费的game time ;
Example
注意 : 我们以下的讨论前提是:假定游戏是"real time"的 ,即game time 和 wall clock time的流逝速度相一致;
Frame 1 took 10ms from start to end to execute and render in our screen.
帧1从头到尾耗费了10毫秒用于帧处理 ,并最终把该帧渲染到了屏幕上 ;
To keep the simulation realtime, in frame 2 we need to advance the state of the game world as much "game time" as "wall clock time" it took to execute the previous frame.
因为帧1的帧处理耗费了一定量的时间 (游戏是"real time"的,所以这里的时间可以是"game time",也可以是"wall clock time"),所以我们需要推进游戏世界的状态,以保持物理模拟的实时性 ;
,
Our FixedTimeStep is set to 0.004 (4ms). That means, each physics step calculates only 4ms worth of "wall clock time" out of those 10ms. We will need to run 2 physics steps in order to reach 8ms from the previous frame. The remaining 2ms are not enough to take a third step, so it will be accumulated for the next frame.
我们将FixedTimeStep设置为了0.04(4ms) 。 这意味着 ,每一步physics将从那10ms中取4ms 。所以我们将需要运行2步physics ,以便物理模拟能追上上一帧开头流逝的8ms ,这种机制使得物理模拟近似于是实时性的 ;剩余的2ms不足以满足4ms的FixedTimeStep ,因此不能在下一帧中单成一步physics 。这2ms将被和下一帧的处理时长进行累加,为下下帧做准备 ,或者换句话说 ,为下下帧的physics的"追上"而作准备 ;
Suppose that executing one physics step takes 1ms of "wall clock time" (calculations are generally similar on all physics steps on similar situations), so our physics step will take 2x1ms = 2ms of "wall clock time" to execute. The rest of execution (Update(), rendering...) takes 1ms of "wall clock time" for whatever reason in this frame. So the entire frame takes 2+1 = 3ms of "wall clock time", instead of the 10ms we had before.
我们假设处理每一步physics将耗费1ms的"wall clock time"(因为所有physics的计算量大体相同,运行环境也大体相同),所以处理所有的physics将总共耗费 2*1ms = 2ms 的"wall clock time" , 进一步 ,假定剩余帧流程(Update() , rendering ...)的处理总共耗费了1ms的"wall clock time" 。所以整体的帧处理耗费了 2+1 = 3ms 的"wall clock time" ,而不是上一帧的10ms ;
Now we move on to the next frame. We start with physics.
现在我们继续讨论下一帧 ,以physics部分开始 。
To keep game time in sync with wall clock time, again, we need to calculate 3ms of game time plus 2ms of the previous gametime that we couldn't take. We can calculate 4 our of those 5 doing one step of 4ms. The other 1ms is left for the next step. The rest of the execution takes 1ms of "wall clock time" again. This frame took 1ms of one physics step + 1ms of the rest = 2ms of "wall clock time".
为了保持game time 与 wall clock time 的同步 ,再一次,我们需要将3ms和它上一帧剩余的2ms进行累加 ,进而用FixedTimeStep进行衡量。我们只能从5ms中取出一个4ms ,所以在下一帧将会只有一步physics ;剩余的1毫秒同上述的2ms一样 ,同下一帧的执行时长进行累加 。剩余的帧流程也同上述一样,假定其耗费了1ms的wall clock time去执行 ;所以这一帧将总共耗费 physics的1ms + 剩余帧流程的1ms = 2ms的"wall clock time" .
Now our fourth frame will have to calculate 2ms from the previous frame plus 1ms left that we didn't calculate from frame 2. That makes 3ms, not enough for our 4ms timestep!. This time there will be no physics steps at all until we accumulate enough gametime to calculate on subsequent frames.
在第4帧中 ,由于先前的第3帧的总处理时长为2ms ,第2帧剩余了1ms ,所以可供第4帧的physics去追赶的时间一共有3毫秒 ,但是这不足以用我们的FixedTimeStep去衡量得出哪怕一步的physics,所以第4帧的流程中将不包含physics;
I hope you get how this works after this detailed example. 希望你能通过这个详细的演示样例,搞明白帧处理的机制 ;
Key concept to remember: A physics step with a timestep set to 1 will take the same wall clock time to execute as a physics step with a timestep set to 0.01. But it will have to execute 100 times more to calculate the same amount of gametime physics. More accurate, less unstable... but tremendously slower. A wise and adjusted selection of that value will be important to ensure equilibrium.
需要理解的关键概念: 如果将FixedTimeStep设置为 1 ,那么单步physics的执行时长(wall clock time,也可以说是game time,因为游戏是"实时的")将会和FixedTimeStep设置为0.01时一样. 不过对于一定量用于处理physics的game time(physics需要追上的game time,1s) ,后者将至少需要执行100次 ,在物理模拟上 ,这将更加的精确,更加的稳定 ...不过会使游戏占用更多的系统资源,从而使游戏运行效率更低,或者说缓慢 。一个明智且调整好的值对于保持游戏的运行平衡(效率)至关重要
What happens with the accumulated time? How does it affect the simulation?
As we saw in the previous example, we need to keep track of the amount of unsimulated time. We consume that accumulated time in fixed time step sized chunks until the accumulated time left is less than the size of one chunk.
正如我们在之前的例子所见到的 ,我们需要对之前的帧所累积的时间保持追踪 。对之前所累积的时间来说,FixeTimeStep代表了每步physics将从之前所积累的时间中追上多上的时间 ;当之前所积累的时间用FixedTimeStep衡量之后 ,若剩余的不足以在被FixedTimeStep衡量 ,那么这段剩余时间将会继续和下一帧的处理时间进行累加 ,从而重复上述的"追赶"机制 ,以保持物理模拟的"实时性" ;
This is how it is implemented: 这是上述机制的伪代码实现 :
float accumulated = 0;
(...)
// At the start of each frame accumulated += Time.deltaTime;
while (accumulated >= Time.fixedDeltaTime)
{
FixedUpdate();
accumulated -= Time.fixedDeltaTime;
}
How does this accumulation affects the look of my game?
累加的时间是如何影响游戏的画面表现的 ?
The truth is that the state of the game world as it is rendered at the end of each frame is never accurate. Game time always lags behind wall clock time an amount between 0 and Time.fixedDeltaTime seconds.
真相是每一帧所代表的游戏世界的状态(在帧流程最后的rendering部分会被渲染到屏幕上),都是不精确的 Game Time总是相对于wall clock time有滞后性,大概的时间差距在 0 到 Time.fixedDeltaTime之间(以秒为单位) 。
Moreover, when game time runs in slow motion, you can clearly see objects popping from one step to the next because there are lots of frames that are not executing any physics steps.
此外 ,当game time处于slow motion模式(Time.scale<1)而流逝时 ,你可以清楚的看到游戏对象的运动不连贯性(顺滑性) ,表现为游戏对象会从一个位置异常的跳动到另外一个位置 ,因为这之间的许多帧的每一帧都没有进行任何的physics处理 ;
To help hide this lag, game engines apply some interpolation / extrapolation strategies to make it less distracting.
为了改善这种滞后性 ,游戏引擎会应用名为 内插法 / 外插法 的策略去减少这种分散性(运动的异常跳跃,不顺滑);
What is MaximumTimeStep for?
Ok. Now enters the maximum time step value in the Time Settings.
万事俱备 !现在让我们开始讨论Time Settings里的maximum time step设置项 。
As you have seen, the number of physics steps needed for each case can be reduced and increased depending on many variables. What happens if physics takes forever to calculate for whatever reason? what happens if the rendering is extremely slow for a certain frame but game time is passing anyway?
正如你所见的 , 每一帧的开头部分需要有几步physics ,是取决于各种因素变量的 。 如果physics流程因某些因素而持续的执行 ,或者换句话说 ,如果帧流程永远的停滞在了计算physics上 ,这会导致什么后果 ? 如果某一帧的rendering部分执行的非常缓慢 ,会导致大量的game time流逝 , 进而导致下一帧的physics部分需要耗费大量的计算机资源去"追赶"上一帧流逝的game time , 这又会导致什么后果 ?
Our last frame was very slow and took 40 ms to render. In the next frame we need to calculate the physics for 40 ms, and it means having 10 steps minimum if our timestep was set to 0.04. If this setting was incorrectly set, that could make the frame take even longer and longer eventually hanging the game. If the accumulated time gets bigger and bigger, this phenomena is called "death spiral", and you can either recover from it or get the game to crash.
假如我们上一帧执行的非常缓慢,仅rendering部分就花费了40ms 。所以在当前帧我们的physics部分需要"追赶"那40ms(近似,忽略rendering部分之前),这意味着我们将至少需要10步physics(FixedTimeStep=0.04)。在这种情况下,如果Maximum Time Step没有被正确的设置 ,将会导致接下来的每一帧的帧处理花费的game time ,一帧比一帧高 ,最终导致游戏的彻底卡死 ;如果 累加时间量 越来越大 ,就导致了上述的现象 ,称之为"螺旋性死亡" ,此时,游戏要么从这种状态里恢复 , 要么就会直接崩溃 ;
To put a limit to this madness, physics engines add the maximumTimeStep setting, which allows us to tell the engine: If you have to calculate more than X time from the previous frame, clamp it to the maximum I tell you, then calculate the rest of the time in the next frame.
为了对这种潜在的"疯狂现象"加以限制,游戏引擎添加了一个名为maximumTimeStep的设置项,它允许开发者告知引擎: 如果你在当前帧需要"追赶"先前帧的Game Time量超过了X ,
So if we have 20ms of timestep and maximum timestep is 40ms, you can only execute 0, 1 (20ms) or 2 (40ms) steps each frame. The rest will be accumulated too. If too much time is accumulated, it will not be accurate or will give the impression of slow motion. If it only happens in certain moments, that will not be as problematic as destroying the fps of the game.
So, try not to accumulate too much time. If that happens, we have a serious problem, so we need to make the fixed timestep bigger so we execute less physics steps, or make our physics code more efficient inside FixedUpdate(), or make our game more efficient in general.