目录

1.前言

2.测试

1.Time.timeScale = 1.0 (无卡顿)

2.Time.timeScale = 0.5 (无卡顿)

3.Time.timeScale = 2.0 (无卡顿)

4.Time.timeScale = 0 (无卡顿)

5.Time.timeScale = 1.0 (且运行中发生卡顿)

3.结论

4.总结

5.范例


1.前言

想来大家都知道Unity中Time.timeScale可以影响游戏的时间,一般通过调整这个值可以实现动画、基于差值的计算、特效等的加速或减速、甚至是停止效果。那么这个值到底影响的是什么时间,它对Update、LateUpdate、和FixedUpdate有影响吗?

2.测试

1.Time.timeScale = 1.0 (无卡顿)

Unity 判断 text 长度定时清空 unity time.timescale_Real

Unity 判断 text 长度定时清空 unity time.timescale_Time_02

如上图,这是Time.timeScale = 1.0 时的测试数据,且运行流畅

1)白色logUpdate中的打印

2)黄色logFixedUpdate中的打印;

3)offsetTime = 当前帧的Time.time - 上一帧的Time.time

4)Time.deltaTime是上一帧的调用时长,可以看到它与Update中打印的offsetTime相同,所以Time.deltaTime = 当前帧的Time.time - 上一帧的Time.time

5)Time面板的Fixed Timestep设置为0.02,所以Time.fixedDeltaTime = 0.02

6)FixedUpdate中打印的offsetTime = 0.02,它与Time.fixedDeltaTime相同

7)offsetRealTime = 当前帧的Time.realtimeSinceStartup - 上一帧的Time.realtimeSinceStartup,可以看到在不卡顿的情况下offsetRealTime和offsetTime很接近

2.Time.timeScale = 0.5 (无卡顿)

Unity 判断 text 长度定时清空 unity time.timescale_Real_03

这是Time.timeScale = 0.5 时的测试数据,且运行流畅

1)可以看到Time.deltaTime平均 = 0.008秒,相当于 Time.timeScale = 1.0 时平均时间(0.016)的一半,offsetTime也缩小了一半,说明Time.time受Time.timeScale的缩放影响,且成正比

2)Update中打印的offsetRealTime没有变化,说明Time.realtimeSinceStartup不受Time.timeScale影响

3)FixedUpdate的调用频率变慢了,基本每两个Update调用才调用一次,可以看到Time.fixedDetlaTime和FixedUpdate中打印的offsetTime没有发生变化依然是0.02,FixedUpdate中打印的offsetRealTime时间翻了一倍,说明什么呢?我们先放一放,后面来分解

3.Time.timeScale = 2.0 (无卡顿)

Unity 判断 text 长度定时清空 unity time.timescale_游戏开发_04

这是Time.timeScale = 2.0 时的测试数据,且运行流畅

1)可以看到Time.deltaTime平均 = 0.033秒,相当于 Time.timeScale = 1.0 时平均时间(0.016)的一倍,offsetTime也放大了一倍,说明Time.time受Time.timeScale的缩放影响,且成正比

2)Update中打印的offsetRealTime没有变化,说明Time.realtimeSinceStartup不受Time.timeScale影响

3)FixedUpdate的调用频率变快了,基本每个Update间隔中会调用两次,可以看到Time.fixedDetlaTime和FixedUpdate中打印的offsetTime没有发生变化依然是0.02,FixedUpdate中打印的offsetRealTime时间一次为0.016,另一次为0,为什么呢?

结合Time.timeScale = 0.5的情况说明FixedUpdate的每次调用间隔固定为Time面板的Fixed Timestep设置的值0.02,相当于Time.fixedDeltaTime的值,但是注意:这里的时间指的是Time.time的时间变化,每过0.02秒调用一次FixedUpdate。所以当Time.timeScale = 2.0时Time.time的时间增长翻了一倍平均0.033秒,约等于0.02 * 2,需要调用两次FixedUpdate;但其实真实时间offsetRealTime只过了0.016秒,所以在一帧中调用了两次FixedUpdate、并使每一次的Time.time增量相同,固定为0.02秒来保证它的调用平衡

4.Time.timeScale = 0 (无卡顿)

Unity 判断 text 长度定时清空 unity time.timescale_游戏开发_05

 这是Time.timeScale = 0 时的测试数据,且运行流畅

1)Time.deltaTime = 0,上面分析已知,Time.deltaTime和Time.time受Time.timeScale正比影响,所以当Time.timeScale = 0时,Time.deltaTime = 0,Time.time增长停滞

2)offsetRealTime = 0.016,真实时间调用没有变化,说明Update本身的调用不受Time.timeScale影响,它是根据真实的上一帧调用时间间隔来调用

3)发现此时只有Update的打印,FixedUpdate的打印没有了。根据上面的分析可知FixedUpdate的调用是按照固定的时间间隔(Time面板的Fixed Timestep值),并且它是受Time.time影响的,因为此时Time.deltaTime = 0,Time.time增长停滞,所以就没有了FixedUpdate的调用

5.Time.timeScale = 1.0 (且运行中发生卡顿)

Unity 判断 text 长度定时清空 unity time.timescale_Time_06

 这是Time.timeScale = 1.0 时,且运行中发生卡顿的测试数据

1)可以看出上图,第二条(白色log)Update的打印中,offsetRealTime = 0.293秒,说明发生了卡顿;它的下一条Update(上图蓝框处)的Time.deltaTime = 0.2秒,为什么不是0.293呢?因为看上图左边的Time面板,Maximum Allowed Timestep设置为0.2,意味着Time.deltaTime的上限即为0.2秒

2)发现在卡顿前后的两次Update调用间隔中,FixedUpdate调用了10次。根据上面的分析可以知道:调用次数 = Time.time增量 / Time.fixedDeltaTime = 0.2 / 0.02 = 10,所以FixedUpdate被调用了10次

3.结论

通过以上分析可以得出如下结论:

1)Time.time的每帧的增量即为Time.deltaTime

2)Time.deltaTimeTime.time都受Time.timeScale影响,且是正比的关系

3)Time.deltaTime最大调用时间间隔为Time面板的 Maximum Allowed Timestep 值

4)Time.realtimeSinceStartup不受Time.timeScale影响,它为真实的游戏时间

5)Update的调用不受Time.timeScale影响,它的调用间隔和真实的上一帧调用时间有关

6)LateUpdate的调用同Update,不受Time.timeScale影响(截图时忘了开打印,实际同Update)

7)FixedUpdate的调用受Time.timeScale影响,且成正比,当Time.timeScale = 0时,FixedUpdate不被调用

8)FixedUpdate的调用时间间隔为Time面板的 Fixed Timestep 值,受Time.time影响

4.总结

1)想让游戏对象受Time.timeScale(子弹时间)影响时,则将游戏对象和 Time.time / Time.deltaTime 做关联计算并放在Update中更新,或者将它放在FixedUpdate中做更新(使用Time.fixedDeltaTime,或按帧计算)

2)想让游戏对象不受Time.timeScale(子弹时间)影响时,则将游戏对象和 Time.realtimeSinceStartup 做关联计算并放在Update中更新

3)Unity中默认Animation、Animator和粒子特效都是受Time.timeScale(子弹时间)影响

4)想让Animator不受Time.timeScale(子弹时间)影响时,可以将它的更新模式改为:animator.updateMode = AnimatorUpdateMode.UnscaledTime;

5)想让Animation或粒子特效不受Time.timeScale(子弹时间)影响时,要用Time.realtimeSinceStartup做采样计算,并对它们做更新

5.范例

前面的分析图都是基于log输出的不够直观。下面给一个直观的动图来形象的理解以上分析:

1)上方立方体的位移是和Time.realtimeSinceStartup挂钩的计算,不受Time.timeScale(子弹时间)影响

2)下方球体的位移是和Time.time挂钩的计算,受Time.timeScale(子弹时间)影响

Unity 判断 text 长度定时清空 unity time.timescale_游戏_07

部分测试代码:

private void Update()
{
    offsetRealTime = Time.realtimeSinceStartup - lastUpdateTime1;
    //if (offsetRealTime > 0.1f)
    {
        Debug.Log($"Time.deltaTime = {Time.deltaTime}, offsetTime = {(Time.time - lastUpdateTime).ToString("f3")}" +
            $", offsetRealTime = {offsetRealTime.ToString("f3")}");
    }
    lastUpdateTime = Time.time;
    lastUpdateTime1 = Time.realtimeSinceStartup;

    deltaTime = Time.deltaTime;
    MoveUpdateByTime(deltaTime);

    MoveUpdateByRealTime(Time.realtimeSinceStartup - lastRealTime);
    lastRealTime = Time.realtimeSinceStartup;
}

private void FixedUpdate()
{
    offsetRealTime = Time.realtimeSinceStartup - lastFixedUpdateTime1;
    //if (offsetRealTime > 0.1f)
    {
        Debug.LogWarning($"Time.fixedDeltaTime = {Time.fixedDeltaTime}, offsetTime = {(Time.time - lastFixedUpdateTime).ToString("f3")}" +
            $", offsetRealTime = {offsetRealTime.ToString("f3")}");
    }
    lastFixedUpdateTime = Time.time;
    lastFixedUpdateTime1 = Time.realtimeSinceStartup;
}

private void Play()
{
    isForward = true;
    Move();
    lastRealTime = Time.realtimeSinceStartup;
    isForward_Real = true;
    MoveReal();
}

private void Move()
{
    if (isForward)
    {
        from = originPos_From;
        to = originPos_To;
    }
    else
    {
        from = originPos_To;
        to = originPos_From;
    }
    targetTrans.localPosition = from;
    isMoving = true;
    factor = 0;
}

private void MoveUpdateByTime(float deltaTime)
{
    if(isMoving)
    {
        factor += deltaTime * speed;
        if (factor >= 1f)
        {
            isForward = !isForward;
            Move();
        }
        else
            targetTrans.localPosition = to * factor + from * (1f - factor);
    }
}

private void MoveReal()
{
    if (isForward_Real)
    {
        from_Real = originPos_From_Real;
        to_Real = originPos_To_Real;
    }
    else
    {
        from_Real = originPos_To_Real;
        to_Real = originPos_From_Real;
    }
    targetTrans_Real.localPosition = from_Real;
    isMoving_Real = true;
    factor_Real = 0;
}

private void MoveUpdateByRealTime(float deltaTime)
{
    if (isMoving_Real)
    {
        factor_Real += deltaTime * speed;
        if (factor_Real >= 1f)
        {
            isForward_Real = !isForward_Real;
            MoveReal();
        }
        else
            targetTrans_Real.localPosition = to_Real * factor_Real + from_Real * (1f - factor_Real);
    }
}