原文:http://forum.china.unity3d.com/thread-28428-1-1.html
0x02 可變增量時間
ok,那我們來看看遊戲引擎的定時器是如何來實現的吧。假設我們手頭沒有一個現成的遊戲引擎,一切都需要自己來實現,那麼一個最簡單的遊戲循環大概就是下面這個樣子的。
double lastTime = Timer.GetTime();
...
while (!quit()){
double currentTime = Timer.GetTime();
double frameTime = currentTime - lastTime ;
UpdateWorld(frameTime);
RenderWorld();
lastTime = currentTime;
}
這種實現方式使得增量時間和具體的機器設備相關,並且它每一次的時間增量都不一定相同。
當機器十分快時,引擎可能通過線程休眠來保證固定的FPS。
while(timeout > 0.001 && deltaTime<timeout)
{
//...休眠後計算新的增量時間
}
所以看上去整個遊戲保證一個大致的更新頻率似乎不難。但是現在問題的關鍵在於每次更新時的時間增量無法保證相同。而在物理模擬中,保證一個固定的增量時間是十分重要的。這是因爲在遊戲引擎進行物理模擬時要使用數值積分,而作爲最簡單的數值積分方法——歐拉法在遊戲引擎中大量使用。
上面就是一個歐拉法的簡單例子。可以看到增量時間是很重要的。而在遊戲引擎的物理模擬中,一個不穩定的增量時間可能導致很多和預期相悖的結果。
由於此時計算的是真實的時間,而真實的增量時間無法保證固定,那換一個思路,我們把參與物理模擬的增量時間當做一個常量可以嗎?換句話說,不論遊戲的更新頻率如何,參與物理模擬的增量時間是一個常數。
0x03 固定增量時間
一個最簡單的固定增量時間的實現,顯然就是將固定的增量時間作爲一個常量參數傳遞給物理模擬模塊,這樣我們就能夠保證物理模擬的增量時間固定,同時還能將物理模擬的更新頻率和遊戲引擎的更新頻率進行解耦——物理的模擬不受引擎的更新頻率影響,無論遊戲的更新頻率是多少,傳遞給物理模擬的增量時間都是一個常量。
這裏還拿Unity引擎來舉例子,默認情況下項目的Fixed Timestep的值爲0.02s。也就是說物理模擬的頻率是50FPS,假設我們的遊戲的更新頻率是25FPS,那麼會發生什麼呢?沒錯,遊戲每1次Update時,物理模擬都要推進2次,也就是之前我們看到的在Update之前多次調用了FixedUpdate。那麼如果我們的遊戲更新頻率是100FPS呢?這次就變成了每2次Update調用1次FixedUpdate。
這也就解釋了爲何有的朋友在做相關的小測試的時候,在每次FixedUpdate內打印Time.deltaTime時輸出的都是0.02了,因爲Time.deltaTime並非兩次調用FixedUpdate之間真實的時間間隔,而是來自我們在項目的Time設置內設置的值——它是一個與真實時間無關的常量。
When called from inside MonoBehaviour’s FixedUpdate, returns the fixed framerate delta time.
那麼這種固定增量時間的邏輯如何用代碼表示呢?很簡單,只需要在主循環的內部,再來一個二級循環。
double simulationTime = 0;
double fixedTime = 20;
while (!quit()){
double realTime = Timer.GetTime();
while (simulationTime < realTime){
simulationTime += fixedTime;
UpdateWorld(fixedTime);
}
RenderWorld();
}
而Unity的實現顯然也是類似的,這個在Unity手冊中關於執行順序的相關章節內可以看到。
官網:https://docs.unity3d.com/500/Documentation/Manual/ExecutionOrder.html
上圖標明瞭FixedUpdate是屬於物理模擬模塊的,同時在主循環的內部,物理模擬的部分還有一個二級循環。