前言
在實際使用Update和FixedUpdate時,遇到一些操作在FixedUpdate不生效的情況。在網上找了一圈要么是一些還沒官方文檔通俗易懂的定義,要么沒有解釋到位沒能解惑,於是自己根據官方文檔分析並通過自己測試做出一些總結。
一、定義
先放出官方解釋:https://docs.unity.cn/cn/2021.2/ScriptReference/MonoBehaviour.FixedUpdate.html
Update:按實際幀率調用,受運行時設備的性能及一幀需要渲染物件數量和質量的影響。 游戲從場景簡單的地方移動到場景復雜的地方時會出現卡頓,往往就是一幀中加載的東西突然增加,加載時間變長導致幀數變少。
FixedUpdate:按固定的幀率調用,根據設置步長固定間隔的執行。例如:設置50幀,那么不管實際幀中一幀執行了多少秒,FixedUpdate固定每0.02s調用一次。
二、deltaTime和fixedDeltaTime
Time.deltaTime在官方的定義為:完成上一幀所用的時間(以秒為單位),用通俗易懂的話來描述就是,每一幀所間隔的時間。而Time.fixedDeltaTime也能適用於該解釋。
例如:50fps,那么對於fixedDeltaTime就是一個固定值0.02。而deltaTime會受設備性能和渲染情況影響,可能前10幀不穩定足足加載了0.4s,平均每幀0.04s,剩下的40幀平均一下就只剩0.015s了。
在Update中使用位移相關的邏輯時,就會有人建議用速度 Speed * Time.deltaTime 使操作邊順滑,這就類似於權重計算,因為1s的Time.deltaTime的和為1,這就確保了每秒位移的距離是相同的。但這么使用實際上只是解決了單位時間位移不同的問題,在幀率變換明顯時這種做法還是會顯得卡頓。
想象一下,你的角色正在以勻速10m/s的速度狂奔,突然跑到了新環境幀率猛地降到了10fps,第1幀加載了0.6s,后9幀加載0.4s,雖然因為使用了Speed * Time.deltaTime使得你的角色這一秒和前面一樣都跑了10m,但毫無疑問你會看到你的角色閃現了一下。
基於上述情況,把處理物理邏輯的代碼放到固定時間執行的FixedUpdate中處理會更加流暢,不會因為實際幀率的變化導致刷新角色行為變得時快時慢(簡單說就是一卡一卡的,甚至發生“閃現”)。
三、FixedUpdate中跳躍失效問題
那么一股腦把物理邏輯的代碼都放入FixedUpdate就行了嗎?有問題!
例如控制角色跳躍(Jump)的KeyDown或ButtonDown放在FixedUpdate中,會出現 按鍵沒有反應 或 按下一次鍵執行了多次 的情況。根據官方文檔對FixedUpdate的解釋不難推測,FixedUpdate的執行是基於實際幀的。
實際幀率100fps > 固定幀率 50fps的情況 ,FixedUpdate是較為均勻的在這100幀中每2幀執行一次。如果KeyDown或ButtonDown事件觸發在實際幀的1幀,3幀,4幀,7幀...,而FixedUpdate是在實際幀的2幀,4幀,6幀,8幀執行,那么就會出現“丟幀”,漏掉了1幀,3幀,7幀,只執行了第4幀的事件。
實際幀率25fps < 固定幀率50fps的情況,FixedUpdate就在每0.02s較為均勻的多次執行實際幀內容。如果KeyDown或ButtonDown事件觸發在實際幀的 1幀(0.05s),2幀(0.03s), 3幀(0.06s)總計0.14s,而FixedUpdate就可能在這0.14s中執行了7次實際幀觸發的事件,導致3次按鍵產生了7次響應。
這里提供一種驗證方式,在FixedUpdate中寫一個KeyDown或ButtonDown事件,分別調節FixedUpdate的固定幀率大致為實際幀率的0.25倍,0.5倍,1倍,1.5倍,2倍,然后快速或勻速按鍵。結果會發現按鍵次數和打印日志次數跟這個倍數相符合。(比如:固定幀率為實際幀率的0.25倍(1/4),那么就能看到大概按鍵4次打印一次結果)
void FixedUpdate()
{
if (Input.GetKeyDown(KeyCode.Q))
{
print("Q key pressed");
}
}
圖中為固定幀率和實際幀率近似1:1的情況
四、對Update和FixedUpdate的一些使用建議
基於上述結論,把物理邏輯部分放在FixedUpdate中時,
如果涉及到一些單次按鍵的情況,考慮到可能會出現“丟幀”,可以把這些單次按鍵的輸入檢測放在Update中,確保能捕獲到該事件。
void Update()
{
if(Input.GetKeyDown(KeyCode.Q))
isPressed = true;
}
void FixedUpdate()
{
if (isPressed)
{
//執行觸發按鍵的一些邏輯
isPressed = false;
}
}