Unity.Time 小白科普

23 篇文章 3 订阅
13 篇文章 1 订阅

       Unity.Time 里面的一系列和时间相关的数据,往往是我们做动画处理的依据,比如 Time.deltaTime*每秒移动距离=本帧需移动的距离,等等,可以说Time类是非常重要的,他直接关系游戏中的逻辑更新,同时Unity.Time 和fixedUpdate 也息息相关,直接决定了每帧中fixedUpdate的调用次数。现在因为一个偶然的任务需要做客户端和服务端的帧同步,所以我有回头特地研究了下Unity.Time 发现很多东西并不是和我想象的一致,也许有的小白会和我一样没有注意到这些细节,所以将我的研究历程写下来供和我一样的小白们参考。

        首先在讲解Unity.Time之前,有一个概念需要明确,就是Unity引擎的所有代码执行都是在一个主线程里面执行的,比如你的脚本里面的update,awake,你开启的携程等等,他们都会按照Unity引擎的调度机制依次执行,注意没有任何一个地方是并行执行的,是依次执行。.而这个执行的流程如下图,这个是抄的别人的。

 

原文地址为:https://blog.csdn.net/akof1314/article/details/39323081 不太明白这个执行流程的可以点进去看看,

可以看到Unity引擎就是这样一遍,又一遍的从上到下的执行这些回调函数然后将渲染出的画面发送到显示器,使得我们的游戏得以运行,像图中所表示的这样从头到脚的执行一次为一帧,执行这一帧所用的时间就是我们这一帧的时长。

在理想的情况下我们每帧的执行时长应该是0,也就是立即绘制出来,但是事实上不是这样,我们的逻辑运算,物理碰撞,画面显示,帧的同步。都是需要时间的。这导致在上一帧开始的那个时间点到下一帧开始的时间点之间我们的游戏世界的时间一直在流逝,而这一段时间内我们却没有做出任何反应(忙着在做上一帧的处理),所以当我们到达下一帧时,首先需要通过Time.deltaTime/Time.fixedDeltaTime 的数量来循环调用物理处理部分,依次推导物理运行直到现在的时间点。正如上面图上所画OnCliisionXXX之后又有一条执行路径返回到FixedUpdate上方,继续一次物理处理部分。然后接下来调用Update时,我们将可以通过Time.deltaTime 知道游戏世界至上一帧后经过了多长时间,然后进行响应的处理。至此主要的两个数据,Time.deltaTime 和 Time.fixedDeltaTime 的来源和用法以及意义我们都知道了。都搞定了,生活真美好不是吗?hoho,别天真,图样图森破 。。。。。

接下来所有的故事开始之前,我们要明确两个概念,现实时间,游戏世界时间他们是不同的,就像现实世界和游戏世界是两个不同的世界一样,他们两个的时间也不是一一对应的,否则玩一把文明6得要好几代人了(顺便推荐一下文明是个好游戏)。所以Unity 分别设置了两个沙漏Time.realtimeSinceStartup 和 Time.time 来分别保存游戏开始到当前帧经过了多少现实时间和经过了多少游戏世界时间。同样Time.deltaTime也并不是你的电脑在上一帧开始的那个时间点到下一帧开始的时间点之间的现实时间,而是经过缩放机制处理后的游戏世界时间。所以我们需要明白的是,Time.time ,Time.DeltaTime, fixedTime,fixedDeltaTime 这些常用的时间数据都是基于游戏世界时间的,和现实世界时间是不同的,他们之间有一系列的机制进行处理和转化,接下来我们将对这些会影响到我们Time 数据的机制进行分析。

1.V Sync Count 垂直同步

垂直同步的意思是,开启这个功能,我们的引擎将只有在收到显卡画面更新完成的通知后,才能进入下一帧处理,假设我们的显示器刷新率是60/秒,则每刷新一次的时间是1/60=0.016秒,如果我们当前帧的渲染时间是0.006秒,则引擎需要在渲染完成后等待0.01秒收到垂直同步信号后,才能更新图像到显示器,并开始调用下一帧的渲染。此功能可以在IDE(edit->projectSettings->Quality(inspecter面板)->V SyncCount) 修改设置。

测试代码如下:

public class NewBehaviourScript1 : MonoBehaviour {

    private int m_frameCount;
    private System.DateTime m_lastTime;
    private double m_passTime;
    private double m_intervalTime;

    private void Awake()
    {
        m_lastTime = System.DateTime.Now;
        m_passTime = 0;
        m_intervalTime = 0;
        m_frameCount = 0;
    }

    void Start () {
       
    }

    void Update () {

        Debug.Log("Update() Time.frameCount=" + Time.frameCount+", Time.realtimeSinceStartup="+ Time.realtimeSinceStartup + " | Time.unscaledTime=" + Time.unscaledTime.ToString() + " Time.unscaledDeltaTime=" + Time.unscaledDeltaTime.ToString() + ", | Time.time=" + Time.time.ToString()+" Time.deltaTime=" + Time.deltaTime);

        transform.Translate(Vector3.right * 0.1f);
    }

    private void OnRenderObject()
    {
        m_frameCount++;

        m_intervalTime = (System.DateTime.Now - m_lastTime).TotalSeconds;
        m_passTime += m_intervalTime;
        Debug.Log("OnRenderObject() m_frameCount=" + m_frameCount + ",m_passTime=" + m_passTime + " 间隔=" + m_intervalTime);
        m_lastTime = System.DateTime.Now;

    }
}

将上面的代码绑定到任意一个Gameobject执行,会得到如下结果:

未打开了垂直同步:

可以看到Time.realTimeSinceStartup 之间的时间差,还是 Time.unscaleDeltaTime 和 我们自己用 DateTime.Now 计算出的时间差 都在小数点后第三位也就是毫秒级间隔而且误差很小(这三个时间之间的细微误差我各人猜测可能是存储精度问题导致的)

打开垂直同步:

可以看到Time.realTimeSinceStartup 之间的时间差,还是 Time.unscaleDeltaTime 和 我们自己用 DateTime.Now 计算出的时间差 都在小数点后第二位都是0.015左右。

上面两个测试结果可以发现,开通垂直同步后我们的单帧的时长明显的增加了,因为我的显示器的刷新率是60所以1/60=0.016秒,大致和上面的0.015秒差不多,所以我们现在可以基本确定垂直同步对帧运行时长的影响。

2.Time.maximumDeltaTime 帧时长最大限制

这一项从字面就可以看出是控制DeltaTime的,上面我们已经讲过,电脑实际运行的现实时间和游戏世界时间是不同的,因此这一项的作用就是当你的现实时间中的一帧的处理时长超过了Time.maximumDeltaTime设置的值时,最多只有Time.maximumDeltaTime的时长被增加到游戏世界的时间上。

测试代码如下:

public class NewBehaviourScript1 : MonoBehaviour {

    private int m_frameCount;
    private int m_count;
    private System.DateTime m_lastTime;
    private double m_passTime;
    private double m_intervalTime;
    // Use this for initialization

    private void Awake()
    {
        m_lastTime = System.DateTime.Now;
        m_passTime = 0;
        m_intervalTime = 0;
        m_frameCount = 0;

        m_count = 0;
    }
    void Start () {
       
    }

    private void FixedUpdate()
    {
        //Debug.Log("FixedUpdate() Time.frameCount=" + Time.frameCount + ", Time.realtimeSinceStartup=" + Time.realtimeSinceStartup + " | Time.fixedUnscaledTime=" + Time.fixedUnscaledTime.ToString() + " Time.fixedUnscaledDeltaTime=" + Time.fixedUnscaledDeltaTime.ToString() + ", | Time.fixedTime=" + Time.fixedTime.ToString() + " Time.fixedDeltaTime=" + Time.fixedDeltaTime);

    }

    // Update is called once per frame
    void Update () {

        

        Debug.Log("Update() Time.frameCount=" + Time.frameCount+", Time.realtimeSinceStartup="+ Time.realtimeSinceStartup + " | Time.unscaledTime=" + Time.unscaledTime.ToString() + " Time.unscaledDeltaTime=" + Time.unscaledDeltaTime.ToString() + ", | Time.time=" + Time.time.ToString()+" Time.deltaTime=" + Time.deltaTime);

        transform.Translate(Vector3.right * 0.1f);
    }

    private void OnRenderObject()
    {
        m_frameCount++;

        m_intervalTime = (System.DateTime.Now - m_lastTime).TotalSeconds;
        m_passTime += m_intervalTime;
        Debug.Log("OnRenderObject() m_frameCount=" + m_frameCount + ",m_passTime=" + m_passTime + " 间隔=" + m_intervalTime);
        m_lastTime = System.DateTime.Now;

        
        //这里做延迟相当于渲染延迟
        m_count++;

        if (m_count > 5)
        {
            m_count = 0;

            for (int i = 0; i < 300000000; i++)
            {
                float a = 1f / 35.6f;
            }
        }

    }
}

从代码中我们可以看到,我们在OnRenderObject中增加了一个300000000次的循环已模拟渲染延迟的情况,同样的我们关闭了垂直同步,避免受到干扰。结果如下:

可以看到蓝色高亮部分,就是我们发生延迟的地方,我们可以发现延迟导致帧时长为2.3秒,Time.realtimeSinceStartup 的时间差一样也是2.3秒左右,但是我们记录的Time.deltaTime 只有0.33333333,Time.time 也只增加了0.3333333,这事因为默认情况下,Time.maximumDeltaTime 的值为1/3秒,所以可以确定,当我们的帧时长超过了Time.maximumDeltaTime 设置的只,将会只有Time.maximumDeltaTime 的时长增加到游戏世界时间中。

3.Time.captureFramerate 拍摄帧率

名字上很容易让人误解,因为除了垂直同步,没有其他地方控制帧时长的,跟不谈帧率了,这里的captureFramerate的意思是游戏世界的帧率,即每一帧对应到多长的游戏世界的时长,写成公式就是:

1/Time.captureFramerate = 每帧增加多长游戏世界时间

设置这个就是我们每帧,给游戏时间的时间增加一个固定时长,这个与默认的游戏世界时间的增加的机制是完全不同的,默认的机制是我们每帧的执行时长在小于Time.maximumDeltaTime的情况下根据实际帧执行时长增加游戏世界的时间,超过了就增加Time.maximumDeltaTime,而设置了Time.captureFramerate 后的机制是每一帧不论执行多长时间,都固定的为游戏世界时间增加一个固定的时长。

测试代码如下(依然关闭垂直同步避免受到干扰):

public class NewBehaviourScript1 : MonoBehaviour {

    private int m_frameCount;
    private int m_count;
    private System.DateTime m_lastTime;
    private double m_passTime;
    private double m_intervalTime;
    // Use this for initialization

    private void Awake()
    {
        Time.captureFramerate = 2;

        m_lastTime = System.DateTime.Now;
        m_passTime = 0;
        m_intervalTime = 0;
        m_frameCount = 0;

        m_count = 0;
    }
    void Start () {
       
    }

    private void FixedUpdate()
    {
        //Debug.Log("FixedUpdate() Time.frameCount=" + Time.frameCount + ", Time.realtimeSinceStartup=" + Time.realtimeSinceStartup + " | Time.fixedUnscaledTime=" + Time.fixedUnscaledTime.ToString() + " Time.fixedUnscaledDeltaTime=" + Time.fixedUnscaledDeltaTime.ToString() + ", | Time.fixedTime=" + Time.fixedTime.ToString() + " Time.fixedDeltaTime=" + Time.fixedDeltaTime);

    }

    // Update is called once per frame
    void Update () {

        

        Debug.Log("Update() Time.frameCount=" + Time.frameCount+", Time.realtimeSinceStartup="+ Time.realtimeSinceStartup + " | Time.unscaledTime=" + Time.unscaledTime.ToString() + " Time.unscaledDeltaTime=" + Time.unscaledDeltaTime.ToString() + ", | Time.time=" + Time.time.ToString()+" Time.deltaTime=" + Time.deltaTime);

        transform.Translate(Vector3.right * 0.1f);
    }

    private void OnRenderObject()
    {
        m_frameCount++;

        m_intervalTime = (System.DateTime.Now - m_lastTime).TotalSeconds;
        m_passTime += m_intervalTime;
        Debug.Log("OnRenderObject() m_frameCount=" + m_frameCount + ",m_passTime=" + m_passTime + " 间隔=" + m_intervalTime);
        m_lastTime = System.DateTime.Now;

        
        //这里做延迟相当于渲染延迟
        m_count++;

        if (m_count > 5)
        {
            m_count = 0;

            for (int i = 0; i < 300000000; i++)
            {
                float a = 1f / 35.6f;
            }
        }

    }

可以看到,我们就是多加了一行,设置Time.captureFramerate =2 也就是每帧增加0.5秒的游戏世界时间

结果如下:

 

可以看到,无论我们的帧执行时长是低于0.5,还是高于0.5,Time.time 都是按照0.5秒增加,Time.deltaTime 也总是0.5。所以可以确定Time.captureFramerate 是表示每帧固定增加多少游戏世界时长。

4.Time.timeScale 时间缩放

这一项从字面意思已经很好理解了,就是对每帧增加的游戏世界时间进行缩放,需要注意的是这个缩放是 Time.captureFramerate 和 Time.maximumDeltaTime 机制处理过后的需要增加的游戏世界时间,而不是对帧执行时间的直接缩放。

测试代码这里就不写了,和上面的Time.captureFramerate 的测试代码一样,吧下面这一段改一改成这样就可以了

  private void Awake()
    {
        //Time.captureFramerate = 2;
        Time.timeScale = 2;

        m_lastTime = System.DateTime.Now;
        m_passTime = 0;
        m_intervalTime = 0;
        m_frameCount = 0;

        m_count = 0;
    }

可以看到,我们设置Time.timeScale=2 也就是放大两倍,这样导致游戏世界时间快两倍。

结果如下:

可以看到,红色标记的两条记录,第二条记录显示,实际的帧执行时间 Time.unscaleDeltaTime= 0.017 被翻倍成了  Time.deltaTime=0.034 (我们计算的间隔和 Time.unscaleDeltaTime 有0.01内的误差,这个可能是存储误差导致的,但是可以看到相同的增长变化),第一条记录显示,实际的帧执行时间 Time.unscaleDeltaTime= 2.37 经过Time.maximumDeltaTime  的机制处理后,增加的游戏世界时间为 Time.deltaTime= Time.maximumDeltaTime *2 所以是0.666667 ,所以可以确定这个缩放是在Time.maximumDeltaTime 和Time.captureFramerate  这些过滤机制的结果只上进行的。

4.Time.fixedDeltaTime 固定更新时差

要想讲清楚 fixedDeltaTime 就会涉及到fixedUpdate 这里面关联到很多其他数据,基本上所有fixed相关的时间数据,事实上都是通过 time 推导出来的,由于太复杂,我这里直接用一张图来表示他们的关系。

如图所示,fixedDeltaTime 的确是决定了 fixUpdate 的调用频率,Unity引擎将根据当前帧的time 和最后一次调用fixedDeltaTime 的时间差,决定调用几次 fixUpdate,每次调用都会更新 fixedTime,并且根据当前最后一次调用fixupdate时的fixedTime 与time 的时间差,倒推出每次调用fixupdate时的 fixedUnscaleDeltaTime 和 fixedUnscaleTime数据。

大家可以通过如下代码自行测试:

public class NewBehaviourScript1 : MonoBehaviour {

    private int m_frameCount;
    private int m_count;
    private System.DateTime m_lastTime;
    private double m_passTime;
    private double m_intervalTime;
    // Use this for initialization

    private void Awake()
    {
        //Time.captureFramerate = 2;
        //Time.timeScale = 2;

        m_lastTime = System.DateTime.Now;
        m_passTime = 0;
        m_intervalTime = 0;
        m_frameCount = 0;

        m_count = 0;
    }
    void Start () {
       
    }

    private void FixedUpdate()
    {
        Debug.Log("FixedUpdate() Time.frameCount=" + Time.frameCount + ", Time.realtimeSinceStartup=" + Time.realtimeSinceStartup + " | Time.fixedUnscaledTime=" + Time.fixedUnscaledTime.ToString() + " Time.fixedUnscaledDeltaTime=" + Time.fixedUnscaledDeltaTime.ToString() + ", | Time.fixedTime=" + Time.fixedTime.ToString() + " Time.fixedDeltaTime=" + Time.fixedDeltaTime);

    }

    // Update is called once per frame
    void Update () {

        

        Debug.Log("Update() Time.frameCount=" + Time.frameCount+", Time.realtimeSinceStartup="+ Time.realtimeSinceStartup + " | Time.unscaledTime=" + Time.unscaledTime.ToString() + " Time.unscaledDeltaTime=" + Time.unscaledDeltaTime.ToString() + ", | Time.time=" + Time.time.ToString()+" Time.deltaTime=" + Time.deltaTime);

        transform.Translate(Vector3.right * 0.1f);
    }

    private void OnRenderObject()
    {
        m_frameCount++;

        m_intervalTime = (System.DateTime.Now - m_lastTime).TotalSeconds;
        m_passTime += m_intervalTime;
        Debug.Log("OnRenderObject() m_frameCount=" + m_frameCount + ",m_passTime=" + m_passTime + " 间隔=" + m_intervalTime);
        m_lastTime = System.DateTime.Now;

        
        //这里做延迟相当于渲染延迟
        m_count++;

        if (m_count > 5)
        {
            m_count = 0;

            for (int i = 0; i < 300000000; i++)
            {
                float a = 1f / 35.6f;
            }
        }

    }

到这里常用的,Time数据基本上都讲完了,欢迎大家留言交流。转载请注明出处,谢谢。

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值