(技术/留档)各类计时的实现方法与对比



  • 前言:
    接触唤境编程以来,已经有至少3人在此方面有疑问,加上我自己也没有深入研究这个内容,因此写下这篇技术讨论帖子,希望有需要的人可以得到帮助。
     
    一、游戏内的计时器
    在各类游戏中,我们时常能见到计时器的身影:技能冷却、死亡复活、关卡限时,各个地方都需要用到计时功能。一般来说,计时器根据时间数字变化方向可分为正计时和倒计时,根据功能可分为秒表、限时器、倒计时等。
    在唤境中,引擎为对象提供了“定时器”能力,这一能力的具体教程请参考官方视频:
    https://www.bilibili.com/video/BV1Qb411u7Nw
    二、计时精度
    在不同场景下,我们对于计时的精度是不同的。一般来说,秒级的精度足够一些节奏较慢、对时间不敏感的游戏使用了;0.1秒级的精度则适用于一些战斗游戏中技能的冷却计时,毫秒级的精度则在音游中应用较多。
    为保证内容统一,本文后续均以毫秒级精度为默认(后面也会介绍如何手动调整至所需精度)。
    三、计时器制作方法
    1.定时器能力法
    这是一种最简单、最方便的一种方法,即利用游戏内的定时器能力进行计时。它具有精度较高、操作方便、上手简单等特点,这种方法也是我最为推荐的。
    事件写法也很简单,这里贴出我的实现方法。 undefined
    计时模式是用于判断正计时还是倒计时,从而用不同的方式展示计时时间。
    核心思路:
    ①首先用户输入并确定一个毫秒数(也可以用事件指定);
    ②点击按钮后,将毫秒数写入全局变量;
    ③随后启动定时器,设定时间为(毫秒数/1000),因为定时器的预设时间是秒为单位;
    ④定时器运行中时,通过表达式和运算来展示计时时间;
    ⑤计时结束后自动停止并输出信息(图中内容用于后续分析)。
    很明显,这个思路简单易懂,而且很容易通过标签来区分多个定时器,从而实现多个技能冷却时间同时更新的效果。如果需要将精度下降,只需要在定时器表达式处加一层floor,同时配上合适的倍率乘除即可。
    floor(对象.定时器.currenttime("timer")*10)/10     //将精度设为0.1秒级的写法
    问题:实际上这种方法基本上是完美的,不过仍然存在一些精度上的问题;根据测试结果,定时器能力在面对不同预设数值时,产生的误差也不同。根据一般实验经验,相对偏差5%以内可认为是正常误差,在此条件下测试发现定时器设定为203ms及以下时相对偏差大于5%。若将精度调整至0.1秒级则近乎完全无法出现显示误差。
    不过,在预览模式(帧率为75fps)的情况下,误差基本上都在15ms以内,可认为是1帧的误差。
    undefined
    结论:在时间超过205ms后,此方法的误差几乎不可计,是一种理想的实现方法。
     
    2.系统时间表达式法
    此处仅以正计时为例。
    undefined
    这种方式是利用系统内置的时间表达式进行计时。唤境内提供了几种类型的时间表达式,例如unixtime、wallclocktime等,这两个表达式都精确到了毫秒级(区别在于后者会被自动换算至秒级,不过小数部分仍然保留了)。那么根据这些表达式就可以很容易制作一个计时器。
    以下是参考事件表(仅考虑正计时,禁用部分为使用unixtime的方法)。
    核心思路与上个类似:
    ①指定计时毫秒数;
    ②循环输出计时结果,同时判断是否到时间了;
    ③到时间时立即停止计时,通过动作组输出信号,分析结果。
    分析:
    根据测试结果,这种方法的误差相比于上一种定时器方法来说会偏低一些,而且在时间非常短(小于200ms)时,计时偏差不会随着时间变短而提高。
    undefined
    但这种方法有其独有的问题,那就是偏差不可控。在定时器方法中,同一个预设时间下的计时误差是趋于稳定的,如果追求完全精确还可以使用事件判断并校正误差。但在系统时间表达式方法中,低计时时间下的偏差是不稳定的,例如测试中使用20ms计时,有时候可以0%完美计时,有时候则差25%之多。
     
    3.帧速率法
    此处仅以正计时为例。
    与上一种类似,这种方法仍然利用了系统内置的表达式。不同的是,这一次我们从获取原始时间换成了获取帧间隔时间。有关帧率、帧间隔等内容,可以参考我之前写过的教程:https://bbs.evkworld.cn/topic/1911
    undefined
    核心思路:
    ①指定计时时间;
    ②计时开始后,每帧为变量加上一个dt(帧间隔);
    ③判断变量达到预设时间后停止计时,发送信号、进行分析。
    虽然相似,但这里运用的方式更巧妙:由于唤境的设定,非触发器条件会每帧执行一次,因此我们利用这一点制作了帧间隔敏感的计时器。
    dt表达式用于获取上一帧的帧间隔,因此若游戏运行在60fps下,则每秒会执行60次事件,就会加上60次dt;而每个dt都接近16.667ms,至少加上的60次dt一定刚好等于1秒。因此,游戏无论运行在何种帧率下,这种方法都能保证每秒的计时数据都加上了1秒。
    分析:
    根据测试结果,这种方案似乎是最可靠的。因为它引入了帧间隔,消除了大量的不稳定性,若帧率维持稳定,则几乎每一个预设时间都有其稳定的误差,也就是说可以很轻松地进行误差校准。并且该方法相对于前两种来说,本身的误差也更小,是在计时精度要求极高的情况下最为推荐的方式。更为优秀的是,它产生的误差一般为负值,这对于延迟校准来说显然是一件好事。
    不过,这种方法也并非完美。由于引入了帧速率,因此误差会随着玩家帧率变化而改变;如果玩家实际运行的帧率过低,则误差会较大。此外,当计时时间极其短,甚至小于帧间隔时,计时器将完全无法工作(在0秒处即停止),因为从计时开始到结束的时间还不够1帧。
     
    四、方法对比

    1.性能对比
    实际上我很少关注这些东西的性能,因为即使每帧进行运算、刷新,消耗的性能对于现代CPU来说仍然是九牛一毛。不过我们仍然有必要关注性能,因此我在每个方法的测试中引入了CPU时间的监控。
    实际测试结果如下:(仅采用正计时,默认时间单位ms)
    undefined
    注:由于笔记本CPU性能调度策略等问题,不同方法的测量误差可能较大;因此对10s预设使用每次重启游戏的方式进行测试,连续测试3次取均值。
    测试结论:在实际游戏中,1.4%的CPU单核占用差距其实可以忽略不计;三者均拥有较好的性能,无需担心计时器的运行会对游戏性能造成影响。
     
    2.横向对比
    总结上述研究内容,我们可以得到以下对比表格。
    每一格以1为相对高分,包含主观部分。
    undefined
    经过标准化后,再计算NCEF(标准消费者经验指数),则可以得出以下结果:
    undefined
    需要注意的是,此处的NCEF算法未经校准,且表格内包含较强主观因素,因此不能代表实际推荐程度;请根据实际情况使用合适的加权数自行计算并选择。
     
    五、总结
    经过了一晚上的探索,我认为收益良多;计时器只是游戏开发中微小的功能,但仍然有足够多的内容值得摸索。其实还有一种通过时间组件来制作的方法,不过我没有足够的时间来尝试,并且认为它的效果与直接使用系统表达式基本一致,因此不作额外尝试。



  • 谢谢;老大,收藏了


Log in to reply