目录
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 (无卡顿)
如上图,这是Time.timeScale = 1.0 时的测试数据,且运行流畅
1)白色log为Update中的打印
2)黄色log为FixedUpdate中的打印;
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 (无卡顿)
这是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 (无卡顿)
这是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 (无卡顿)
这是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 (且运行中发生卡顿)
这是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.deltaTime和Time.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(子弹时间)影响
部分测试代码:
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);
}
}