草庐IT

详解Unity中Time类的用法与深入探究

梦小天幼 2023-07-17 原文

前言

在游戏世界中,时间无疑是最为重要的参数,它是游戏世界能否正常运转的关键。一旦它出错,轻则卡顿,重则游戏世界崩坏。在使用Unity引擎创造的世界中,Time类无疑是必须要掌握的一个类,它是控制时间的关键,是造物主最为重要的权柄,有了它,我们就可以肆意的玩弄我们所创造的游戏世界,一言万年。

很中二的前言hhh,但Time类真的非常重要,本篇主要讲解一下Time类的各个API,以及它们对Unity各项系统的影响。

目录

Unity版本[2019.4.10f1] 梦小天幼 & 禁止转载

视频讲解:
【详解Unity】Time类与深入探究


一、Time类基础

1.Time类总览

大概浏览一遍即可,后续讲解到某个属性再翻回来看

变量释义
总时间
Time.time从游戏开始到现在所用的时间
Time.unscaledTime从游戏开始到现在所用的时间(不受timeScale影响)
Time.fixedTime从游戏开始到现在所用的时间
Time.fixedUnscaledTime从游戏开始到现在所用的时间(不受timeScale影响)
Time.realtimeSinceStartup游戏开始以来的实际时间
Time.timeSinceLevelLoad自最后一个非添加场景(non-additive scene)完成加载以来的时间
增量时间
Time.deltaTime帧间隔时间
Time.unscaledDeltaTime帧间隔时间(不受timeScale影响)
Time.fixedDeltaTime固定间隔帧时间
Time.fixedUnscaledDeltaTime固定间隔帧时间(不受timeScale影响)
Time.smoothDeltaTime经过平滑处理的Time.deltaTime
其他
Time.frameCount总帧数
Time.timeScale时间流逝尺度,1正常速度,2则2倍速
Time.inFixedTimeStep在FixedUpdate()中调用返回true,否则返回false
Time.maximumDeltaTime最大增量时间
Time.maximumParticleDeltaTime粒子更新的最大增量时间
Time.captureDeltaTime捕获增量时间
Time.captureFramerateTime.captureDeltaTime的倒数

2.何为Time.deltaTime

我们在学习Unity过程中,最先接触到的参数应该就是这个Time.deltaTime了,在Update中,我们的移动、旋转一般都要乘以一个Time.deltaTime,用以保证我们的速度是平均的、可控的。

因为Update是每帧调用一次,写在这个函数中的功能代码自然也是每帧调用一次,如果写死了,比如【transform.Translate(transform.forward)】,那么结果就是一秒执行了N次这句代码,移动速度就不可控了,这是就需要乘以一个Time.deltaTime。

Time.deltaTime简单来说,就是指 “前后两帧的间隔时间” ,So我们假设1秒钟内Update被调用了5次,那么帧间隔时间就是 1/5=0.2。

    //如图所示,假设Update每秒执行5次,那么这段代码则表示一秒移动了5米,很显然这不是我们想要的
    void Update(){
        transform.Translate(transform.forward); 
    }

    //如图所示,假设Update每秒执行5次,而Time.deltaTime的值是0.2
    //那么每次Update移动的距离就是0.2,一共执行了五次,五次移动距离一起刚好1米
    void Update(){
        transform.Translate(transform.forward * Time.deltaTime); 
    }

Update的执行次数是不固定的,用户电脑越强,它的1秒内执行次数越高,但是只要我们乘以了Time.deltaTime,就能保证它的移动速度是平均的。因为Time.deltaTime也是根据当前帧率计算出来的。

3.何为timeScale | 时间加速与减速

Time.timeScale属性用于控制游戏世界的时间流速,默认为1,正常状态,如果设置为2,则是两倍速,以此类推。必须大于等于0且小于100。

Time.timeScale之所以能控制两个立方体的旋转速度,是因为两个立方体的旋转代码中用到了Time.deltaTime,如果我们将旋转角度写死,那么Time.timeScale任它有天大能耐也无法控制的。

//正方体的自转代码
public class 自转 : MonoBehaviour
{
    public float speed;
    private void Update()
    {
        transform.Rotate(new Vector3(0, speed * Time.deltaTime, 0));
    }
}

这就意味着,timeScale并不是万能的,如果我们向让它去控制游戏世界中的一切,那么我们必须要按照Unity定下的规则办事,也就是在需要自己实现的运动功能代码部分乘以一个Time.deltaTime。

那么这里又引出一个问题,如果我不想被timeScale控制,但是我又要保证我的物体运动是平均可控的,怎么办?

那就引入一个新的属性 — Time.unscaledDeltaTime

该属性和Time.deltaTime没什么区别,但是当timeScale变更时,Time.unscaledDeltaTime是不受影响的。

4.获取时间

对于获取时间这块,Unity为我们提供了很多选择,我直接把上面的表格复制下来了,我们可以看到,对于获取时间足足有6个选择。

变量释义
总时间
Time.time从游戏开始到现在所用的时间
Time.unscaledTime从游戏开始到现在所用的时间(不受timeScale影响)
Time.fixedTime从游戏开始到现在所用的时间,固定更新
Time.fixedUnscaledTime从游戏开始到现在所用的时间,固定更新(不受timeScale影响)
Time.realtimeSinceStartup游戏开始以来的实际时间
Time.timeSinceLevelLoad自最后一个非添加场景(non-additive scene)完成加载以来的时间

最重要的就是Time.time啦,该属性会返回从游戏开始到现在所用的时间,以秒为单位,如果大家想要计算玩家从本次已经运行游戏多长时间了,就可以使用该属性来计算。

但是请注意! Time.time是受到Time.timeScale的影响的,如果你的游戏用到了Time.timeScale,那么这时就需要将受缩放影响的时间和不受缩放影响的时间区分开了,由此,Unity为我们提供了Time.unscaledTime。

这样的话我们可以使用Time.time来计算游戏世界内的时间流逝,如果这个世界被时停了,那么时间也理应被时停,对吧?

对于现实世界而言,我们使用Time.unscaledTime,毕竟该属性不受Time.timeScale影响。

除此之外,还有下面四个属性

其中Time.fixedUnscaledTime、Time.realtimeSinceStartup是和Time.unscaledTime一样,不受Time.Scae影响
而Time.fixedTime、Time.timeSinceLevelLoad是受到Time.Scale影响的。

那么它们的不同之处在什么地方呢?

受Scale影响

  • Time.time 由Time.deltaTime累加更新
  • Time.fixedTime 由Time.fixedDeltaTime累加更新
  • Time.timeSinceLevelLoad 自最后一个非添加场景(non-additive scene)完成加载以来的时间
    上述三个是保持同步的

不受Scale影响

  • Time.unscaledTime 由Time.unscaledDeltaTime累加更新
  • Time.fixedUnscaledTime 由Time.fixedUnscaledDeltaTime累加更新
  • Time.realtimeSinceStartup 从系统计时器获取时间更新
    上述三个也是保持同步的
  • 一旦timeScale为0,则fixedUnscaledTime停止更新,但timeScale一旦恢复为1,则fixedUnscaledTime与unscaledTime同步。
  • 一旦通过编辑器暂停游戏,timeScale、fixedUnscaledTime都会停止更新,但realtimeSinceStartup会继续更新,直到编辑器停止暂停,则三个参数同步。

5.Time.captureDeltaTime

该参数主要用于减慢游戏的帧率,保证Unity在帧与帧之间完成截图,更多的使用情况是完成一系列的截图,组成视频,从而达成录像的目的。

默认该值为0,如果不启用该值就直接开始截图,可能会出现卡顿的情况,也就是最终合成的视频可能无法保持恒定的帧速率,所以想要录制流畅的视频,务必使用它!

下面这张动态图展示了录制一组图片的整个过程,但是表现不是很明显,可以结合下面给出的源码看,这就是一个案例应用

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class 截图 : MonoBehaviour
{
    //存放截图文件夹的名字
    public string folder = "Test截图";
    //截图需保持的帧率
    public int frameRate = 25;
    public Button startBtn;
    public Button stopBtn;
    public bool key = false;

    public void Update
    {
        if(key)
        {
            Shot();
        }
    }

    //点击开始按钮
    public void StartSS()
    {
        // 设置播放帧率
        Time.captureDeltaTime = 1.0f / frameRate;
        //创建文件夹
        System.IO.Directory.CreateDirectory(folder);
        key = true;
    }

    //点击结束按钮
    public void StopSS()
    {
        key = false;
        //结束录制后,记得将该值置零
        Time.captureDeltaTime = 0;
    }

    //录制函数
    public void Shot()
    {
        // 添加文件名到文件夹名称(格式为“0005.png”)
        //Test截图/0001.png
        string name = string.Format("{0}/{1:D04}.png", folder, Time.frameCount);

        // 捕获截图 PNG文件保存至XX路径
        ScreenCapture.CaptureScreenshot(name);
    }
}

6.关于timeAsDouble

上述列表中还有六个参数的双精度版本的参数我没有写入,因为这是2021LTS版新增的内容,因为比较简单,所以不作赘述。
详情参阅:https://docs.unity3d.com/cn/current/Manual/WhatsNew2021LTS.html

Time.timeAsDouble引入了各种属性,使您可以在项目中使用双精度时间。这对于构建长时间运行的应用程序(如专用游戏服务器)的创作者特别有用。


二、Time与生命周期函数的循环

有关生命周期函数的调用顺序我之前出过一篇文章,详情请移步(https://blog.csdn.net/weixin_43147385/article/details/123928964?spm=1001.2014.3001.5502)
接下来主要是讲TimeScale对生命周期函数的影响。

下图为生命周期函数调用关系图,供参考

我们首先把循环流程图搬出来,供后续讲解参考(该图从Unity文档找的,我重新画了一下)

1.Time.time和Time.UnscaledTime的时间为何不相同?

在timeScale总为1的情况下,按理说,time和UnscaledTime应该是相同的,除非timeScale不为1,它们才会就此不同。但实际上从系统运行一开始,这俩参数就不一致了。

若想深究起原因,就不得不谈谈这两个参数都是依靠谁来进行更新的,如何进行更新的。

上面已经提过这个问题,time依靠deltaTime更新,UnscaledTime则依靠UnscaledDeltaTime更新。

它们对总时长累加一次,Update就更新一次,但是这个累加是有限制的,这个限制就是maximumDeltaTime,该参数默认为0.33。

但该参数的限制仅对deltaTime生效,仔细看上面给出的循环流程图,进行到第二步时就会判断deltaTime是否大于maximumDeltaTime,若大于,则直接返回maximumDeltaTime,若小于就进行下一步。

所以差异就在这个地方,当我在启动Unity时,它会卡顿两秒才成功启动,这两秒钟内是Unity内部在调用资源,但当我按下按钮一瞬间后,实际上time就已经开始计时了,只不过屏幕还没有刷新出来,而Unity在卡顿的时候,帧与帧之间的间隔时间会很大,导致deltaTime的值变得很大,但是maximumDeltaTime的存在就是为了防止这种事情发生,所以time被限制住了,而UnscaledTime则没有被限制住,所以实际上UnscaledTime才是真正的Unity从运行开始到现在的时间。

2.timeScale为0时会影响哪个周期函数?

这里我们做一个小案例即可明了

从上图中我们可以很清晰的看出,当timeScale为0时,FixedUpdate停止了刷新,而Update和LateUpdate则没有停。其原因我们可以在循环流程图中得到解答。

每当deltaTime被添加到Time.time中时就会检查一次,fixedTime和time是否差距过大,如果不大就持续更新Update,如果差距大于fixedDeltaTime则累加一次fixedTime,然后更新一次FixedUpdate。

So,当timeScale为0时,deltaTime受此影响变为0,循环依旧运行,但time也一直在累加,但是一直都是加0,所以time与fixedTime的差距始终小于fixedDeltaTime,所以始终更新Update,而无法满足更新FixedUpdate的条件。

3.物理更新为什么要放到FixedUpdate中呢?

我们经常听别人说,物理相关的一定要放到FixedUpdate中,但是为什么这么写呢?

通过上一段我们已经了解,timeScale会影响FixedUpdate,其实答案已经很明了了。

我们假设有一架不停发射的机枪,架在主角必经之路,这时如果主角打开了设置界面,世界是不是需要暂停?需要等待主角关掉设置界面。那么这个时候机枪怎么办?

我们假设机枪代码如下:

public class 机枪 : MonoBehaviour
{
    public GameObject bullet;
    public float force_ = 40;
    private void Update()
    {
        GameObject g;
        g = GameObject.Instantiate(bullet);
        g.AddComponent<Rigidbody>();
        g.GetComponent<Rigidbody>().AddForce(transform.forward * force_);
    }
}

这里使用了Update,那么时停过程中,Update是不会暂停的,So,机枪会不断生成子弹,但子弹并不会发射,因为代码给子弹添加了力,这里是由物理系统控制的。所以这里我们换成FixedUpdate,一旦时停,代码也会停止运行。



三、总结和参考资料

1.总结

最后总结,来一张全参数的变化图,还挺好玩的,你们可以自己去做一下.

挑重要的说
关于time的总结

  • time和fixedTime和timeSinceLevelLoad总是同步
  • unscaledTime和fixedUnscaledTime和realtimeSinceStartupas总是同步

关于deltaTime的总结

  • deltaTime受到timeScale影响
  • deltaTime最大值受到maximumDeltaTime影响
  • unscaledDeltaTime不受timeScale影响
  • fixedDeltaTIme不受timeScale影响
  • fixedUnscaledDeltaTime受到timeScale影响,会按照反比改变
  • smoothDeltaTime总是会在deltaTime和unscaledDeltaTime计算出一个平衡值

关于对周期函数影响的总结

  • FixedUpdate会受到timeScale影响,为0时会停止运行,为2时为2倍速运行
  • Update不会受到timeScale影响

2.参考资料

[1].Unity官方.Unity官方文档-Time类
[2].Unity官方.Unity官方文档-Time类API
[3].SerenaHaven.Unity——Time.timeScale详解
[4].Unity官方.New in Unity 2021 LTS
[5].天生爱赞美.Unity关于Time.timeScale详解

有关详解Unity中Time类的用法与深入探究的更多相关文章

  1. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  2. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  3. 没有类的 Ruby 方法? - 2

    大家好!我想知道Ruby中未使用语法ClassName.method_name调用的方法是如何工作的。我头脑中的一些是puts、print、gets、chomp。可以在不使用点运算符的情况下调用这些方法。为什么是这样?他们来自哪里?我怎样才能看到这些方法的完整列表? 最佳答案 Kernel中的所有方法都可用于Object类的所有对象或从Object派生的任何类。您可以使用Kernel.instance_methods列出它们。 关于没有类的Ruby方法?,我们在StackOverflow

  4. ruby - Rails 关联 - 同一个类的多个 has_one 关系 - 2

    我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下

  5. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  6. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  7. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  8. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  9. ruby - 为什么当我调用类的实例方法时,初始化不显示为方法? - 2

    我正在写一篇关于在Ruby中几乎一切都是对象的博客文章,我试图通过以下示例来展示这一点:classCoolBeansattr_accessor:beansdefinitialize@bean=[]enddefcount_beans@beans.countendend所以从类中我们可以看出它有4个方法(当然,除非我错了):它可以在创建新实例时初始化一个默认的空bean数组它可以计算它有多少个bean它可以读取它有多少个bean(通过attr_accessor)它可以向空数组写入(或添加)更多bean(也通过attr_accessor)但是,当我询问类本身它有哪些实例方法时,我没有看到默认

  10. ruby-on-rails - Rails 中同一个类的多个关联的最佳实践? - 2

    我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来

随机推荐