創作內容

5 GP

【筆記】解決Unity中使用FixedUpdate位移時的不連續

作者:廢物敗類窩囊廢│2018-12-14 23:09:20│巴幣:10│人氣:5679
首先聲明,這篇筆記的主要資料來源來自此:Timesteps and Achieving Smooth Motion in Unity,是我閱讀理解並查證相關資料後的整理。



Unity中,遊戲主迴圈以可變的time step(時間間隔)作為畫面的更新率,這個時間差在Unity 叫做deltaTime,它的優點是能在不同硬體規格上盡快的執行updates,但也容易造成位移看起來斷斷續續。

Unity的主迴圈

Unity的運作順序,在官方提供的手冊中有提供:https://docs.unity3d.com/uploads/Main/monobehaviour_flowchart.svg
Update與FixedUpdate是交錯執行非同時執行的,搞錯這點的話可能會不能理解為什麼當time step > Fixed time step會造成看起來不連續的位移了。

我們看到下面這段程式碼,Unity的迴圈大約可能長這樣子:

float currentSimulationTime = 0;
float lastUpdateTime = 0;

while (!quit) // variable steps
{
        while (currentSimulationTime  < Time.time) // fixed steps
        {        
              FixedUpdate();
              Physics.SimulationStep(currentState, Time.fixedDeltaTime);
              currentSimulationTime += Time.fixedDeltaTime;
        }
Time.deltaTime = Time.time - lastUpdateTime;
Update();
Render();
lastUpdateTime = Time.time;
}

主迴圈中,還有一個子迴圈,用來呼叫FixedUpdate()及物理運算,當當前模擬時間小於 Time.time(從遊戲開始時計算的時間 遊戲暫停時也會停止增加)會執行一次物理運算及FixedUpdate()。

理想上,我們會希望更新的情況長這樣:

實際上:

大多數情況下,遊戲畫面的更新率會大於FixedUpdate(),所以物件位置的更新並不會像畫面更新那樣頻繁的更新位置。

(左邊的攝影機在fixedUpdate中移動)

這裡必須注意,Fixed time的固定不是以固定實時差距執行的,而是以固定的時間差距量運算,以保證物理模擬的正確性

根據前面的程式碼,可以發現,假設執行Update時卡住了1s,而fixed time step為 0.02s,則在下一次Update前,FixedUpdate()會多執行50次補上之前漏掉的,這導致單次的位移量可能很大。

基於硬體與複雜的畫面變化,遊戲的畫面更新率幾乎是不可能維持穩定的,畫面每次更新時,有大量的運算要執行,一定會延遲個0.多秒,而這就足以造成位移的不連續。

解決方法

我們可以使用內插法或外插法,填滿在兩個Fixedupdate間的畫面,以達成平滑位移。

內插法

使用內插法平滑fixedDeltatime的結果,使繪製出位移的結果(位置)延遲,而這個延遲通常是可接受的。

外插法

外插法,預測物件在下個fixed step應該在哪,避免延遲,但這會更難繪製連續的結果,以及造成額外花費。

解決範例(來源

這邊有一個使用內插法的解決方案,主要需要三個腳本:
1.InterpolationController:
紀錄最近兩次的fixed step,在Updates與Time.time比較後產生一個全域插值。

using UnityEngine;
using System.Collections;

public class InterpolationController : MonoBehaviour
{
    private float[] m_lastFixedUpdateTimes;
    private int m_newTimeIndex;

    private static float m_interpolationFactor;
    public static float InterpolationFactor {
        get { return m_interpolationFactor; }
    }

    public void Start() {
        m_lastFixedUpdateTimes = new float[2];
        m_newTimeIndex = 0;
    }

    public void FixedUpdate()  {
        m_newTimeIndex = OldTimeIndex();
        m_lastFixedUpdateTimes[m_newTimeIndex] = Time.fixedTime;
    }

    public void Update() {
        float newerTime = m_lastFixedUpdateTimes[m_newTimeIndex];
        float olderTime = m_lastFixedUpdateTimes[OldTimeIndex()];

        if (newerTime != olderTime) {
            m_interpolationFacto
r = (Time.time - newerTime) / (newerTime - olderTime);
        } else {
            m_interpolationFactor = 1;
        }
    }
    
    private int OldTimeIndex() {
        return (m_newTimeIndex == 0 ? 1 : 0);
    }
}

2.InterpolatedTransform :
儲存物件最近兩次fixed step的transform,並對兩者使用插值法(使用全域插值)
如果你想移動物件,並不希望使用平滑 呼叫ForgetPreviousTransform後移動物件
這腳本要在加在需要使用FixedUpdate移動的物件上

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(InterpolatedTransformUpdater))]
public class InterpolatedTransform : MonoBehaviour
{
    private TransformData[] m_lastTransforms;
    private int m_newTransformIndex;

    void OnEnable() {
        ForgetPreviousTransforms();
    }

    public void ForgetPreviousTransforms() {
        m_lastTransforms = new TransformData[2];
        TransformData t = new TransformData(
                                transform.localPosition,
                                transform.localRotation,
                                transform.localScale);
        m_lastTransforms[0] = t;
        m_lastTransforms[1] = t;
        m_newTransformIndex = 0;
    }

    void FixedUpdate() {
        TransformData newestTransform = m_lastTransforms[m_newTransformIndex];
        transform.localPosition = newestTransform.position;
        transform.localRotation = newestTransform.rotation;
        transform.localScale = newestTransform.scale;
    }

    public void LateFixedUpdate() {
        m_newTransformIndex = OldTransformIndex();
        m_lastTransforms[m_newTransformIndex] = new TransformData(
                                                    transform.localPosition,
                                                    transform.localRotation,
                                                    transform.localScale);
    }

    void Update() {
        TransformData newestTransform = m_lastTransforms[m_newTransformIndex];
        TransformData olderTransform = m_lastTransforms[OldTransformIndex()];

        transform.localPosition = Vector3.Lerp(
                                    olderTransform.position,
                                    newestTransform.position,
                                    InterpolationController.InterpolationFactor);
        transform.localRotation = Quaternion.Slerp(
                                    olderTransform.rotation,
                                    newestTransform.rotation,
                                    InterpolationController.InterpolationFactor);
        transform.localScale = Vector3.Lerp(
                                    olderTransform.scale,
                                    newestTransform.scale,
                                    InterpolationController.InterpolationFactor);
    }

    private int OldTransformIndex() {
        return (m_newTransformIndex == 0 ? 1 : 0);
    }

    private struct TransformData {
        public Vector3 position;
        public Quaternion rotation;
        public Vector3 scale;

        public TransformData(Vector3 position, Quaternion rotation, Vector3 scale) {
            this.position = position;
            this.rotation = rotation;
            this.scale = scale;
        }
    }
}
FixedUpdate:
將最近一次的transform讀入transform

LatedFixedUpdate:
將當前transform紀錄

Update:將物件位置繪製在兩次Fixed step之間,並用InterpolationController.InterpolationFactor平滑

3. InterpolatedTransformUpdater:
每次FixedUpdate()執行時 呼叫LateFixedUpdate紀錄當前transform
using UnityEngine;
using System.Collections;

public class InterpolatedTransformUpdater : MonoBehaviour
{
    private InterpolatedTransform m_interpolatedTransform;
    
    void Awake() {
        m_interpolatedTransform = GetComponent<InterpolatedTransform>();
    }

void FixedUpdate() {
        m_interpolatedTransform.LateFixedUpdate();
    }
}



三個腳本必須如上圖這樣設定,附帶InterpolatedTransform的物件必須在FixedUpdate中移動,因為任何在Update中的transformations(pos,rota,scale改變)都會覆寫插值。

參考資料




引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4226672
All rights reserved. 版權所有,保留一切權利

相關創作

留言共 24 篇留言

廢物敗類窩囊廢漢堡
所以那個彈飛BUG呢???

12-15 00:23

廢物敗類窩囊廢
彈飛bug是哪個的ㄚ 是被火往上燒那個嗎12-15 00:26
廢物敗類窩囊廢漢堡
你還是講中文吧 阿鬼

12-15 00:27

廢物敗類窩囊廢
所以彈飛bug是哪ㄍ的12-15 00:28
廢物敗類窩囊廢漢堡
LAG跑到虛無

12-15 00:30

廢物敗類窩囊廢
那應該就是這個吧 因為我把fixed step調超小 畫面lag就跑一大堆fixedupdate 就飛走了ㄅ12-15 00:35
廢物敗類窩囊廢漢堡
太遜ㄌㄅ

12-15 00:36

廢物敗類窩囊廢漢堡
學一半的後果啊 阿鬼[e1]

12-15 00:39

廢物敗類窩囊廢
可撥廢物敗類窩囊廢==12-15 00:40
廢物敗類窩囊廢漢堡
好猛哦

08-26 18:57

廢物敗類窩囊廢
https://truth.bahamut.com.tw/s01/201906/b7e58269a7ce0b91d09cf201b5ba11fe.JPG?w=30008-26 18:58
小戴
可以教我做遊戲嗎?

09-23 13:50

廢物敗類窩囊廢
不可以09-23 18:28
Manners
請問一下使用了這個物理效果就消失了該怎麼辦?

10-29 23:21

Manners
就是我是一個賽車遊戲 , 加了這個 到了上坡 會直接穿牆 沒有物理反應

10-29 23:35

廢物敗類窩囊廢
所以是加了這些腳本後就壞了嗎 如果沒有特殊需求 不如camera直接在fixedUpdate裡移動10-29 23:39
廢物敗類窩囊廢
這篇文的結論我沒寫出來 就是盡量不要拆成兩個time step混用 要用物理把state通通放在fixedUpdate就好10-29 23:45
Manners
賀喔 我素素跨麥

10-29 23:45

Manners
我發現我相機本來就有用fixedupdate 跟蹤物件,可是我物件會有一個功能,可能是因為數據差異太快 會有顫抖的問題。好像改fixedupdate也沒用,所以來用這個滑順的
還是相機可以放這個滑順的?

10-30 00:00

廢物敗類窩囊廢
那你車子是fixedUpdate裡移動的嗎 這個顫抖是因為兩個time step的更新頻率不同造成的抖動
同個time step的話 camera應該跟object的相對距離固定 不會看起來是抖動的
10-30 00:04
Manners
都是在fixedUpdate裡面噎
[e15]

10-30 00:05

廢物敗類窩囊廢
那車子跟camera的程式碼我可以看看嗎10-30 00:07
Manners
可喔
等我一下 我給你一部分

10-30 00:07

Manners
public void Run() //移動的程式碼
{
Bicycle.transform.Rotate(Vector3.forward, -500f * Time.fixedDeltaTime);
}

// 相機的
public class CameriaTrack : MonoBehaviour {
public Transform targetTr;
public float dist = 2f;
public float height = 3.0f;
public float dampTrace = 5.0f;
public Transform tr;
void Start () {
tr = GetComponent<Transform> ();
}
//Update更改為Fixed DeltaTime fixedDeltaTime
void FixedUpdate () {
tr.position = Vector3.Lerp (tr.position, targetTr.position - (targetTr.forward * dist) + (Vector3.up * height), Time.fixedDeltaTime * dampTrace);
tr.LookAt (targetTr.position);
}
}


相機的部分 我有用b物件讓他追蹤 車子部分移動也有讓b物件同時移動

10-30 00:11

廢物敗類窩囊廢
run為什麼是用物件rotate rotate是旋轉
物理移動的話 應該用rigbody給他速度或force吧10-30 00:16
Manners
因為那段式 傾斜的哈哈 我是傾斜的時候左右太快的話 畫面會抖動

rigbody 我試試看 因為我車子是雙輪 會倒車哈哈哈不知道可不可以

10-30 00:19

Manners
這樣物理要傾斜還是要用rotate吧?

10-30 00:23

廢物敗類窩囊廢
rigbody會有物理作用 你不把rotation鎖起來他會自己轉10-30 00:24
Manners
現在還有遇到一個問題,就是當我重新遊玩後,物件會不能移動,當我關閉InterpolationObjectController他就可以動了 , 有辦法就是把這個腳本初始化嗎?

10-30 01:53

Manners
InterpolationObjectController是有一個人寫的 他可以再讓文章這篇腳本
有物理效果

10-30 01:53

Manners
https://github.com/develax/Smooth-Motion-in-Unity

這篇

10-30 01:57

廢物敗類窩囊廢
OK 不過這樣會不會偶爾出現抖動呢 然後不能移動可能跟factor有關10-30 12:48
Manners
好像會抖動但比原本穩定很多 你這篇文章得不會抖動嗎?因為我也塞到物件會穿牆, 還是他可以放相機?

10-30 15:16

廢物敗類窩囊廢
最好的方法就是不要直接改transform的pos來達成物理
除非你要自己模擬整套的physics
那你把整個專案寄給我 我看一下會比較清楚實際狀況10-30 16:04
Manners
那factor該怎麼辦 哈哈哈 我有呼叫作者說的resetTransfrom 沒用,但我用按鈕 一直按就能動一下下

10-30 15:19

Manners
賀啊 等我整理一下,我物理做很爛 哈哈哈哈

10-30 16:37

Manners
我有把github.com/develax/Smooth-Motion-in-Unity 放進去專案 然後放在玩家一 裡面了
我這個是連手機 我把手機端的東西拿掉而已
他在手機端左右傾斜的時候會移動太快導致有點卡的感覺

https://drive.google.com/file/d/1TFP9KCLv29jVopcXJEXIJJ6POnA7Ofd0/view?usp=sharing

也是物理沒做好的關係 因為我也不是物理移動

分為左轉右轉 左傾右傾 前進,遊戲重新開始後就不能移動了

https://drive.google.com/file/d/1TFP9KCLv29jVopcXJEXIJJ6POnA7Ofd0/view?usp=sharing

10-30 18:00

廢物敗類窩囊廢
我發送請求了10-30 18:49
Manners
接受了 感恩

10-30 19:22

我要留言提醒:您尚未登入,請先登入再留言

5喜歡★j2492104 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:【筆記】遊戲設計模式Pa... 後一篇:快研畢ㄌ 自我回顧一下...

追蹤私訊切換新版閱覽

作品資料夾

b2541369所有人
近期會開始投稿一些圖,歡迎追蹤我的小屋謝謝。看更多我要大聲說15小時前


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】