关于游戏《因果》为什么需要在屏幕刷新率为60hz的环境下运行
在这里感谢《窗口旅行 Out Of Bounds》的作者发现了最大的问题,请大家多多支持《窗口旅行 Out Of Bounds》
如标题所示,这一篇帖子用于说明最大的恶性bug出现的原因
在完整说明之前需要知道开发《因果》时使用的技术和前置知识储备
在游戏引擎的普及下,大部分开发者会遗漏一个很致命且隐性的问题:游戏运行时的帧数
有一小部分pvp游戏,会出现“屏幕刷新率高,游戏运行的帧数越高”的问题,例如《Bodycam》
这看起来是一个好结果,但它实际达成的效果却是由设备优劣带来的不对等体验,尤其是基础体验
拿最基础的移动举例子,如果奉行“屏幕刷新率高,游戏运行的帧数越高”就会出现以下结果:
“屏幕刷新率越高,相较于其他屏幕刷新率比你低的设备,你移动的越快”
为什么会出现这种情况?
在游戏开发时都会选择一个基础的动画更新函数来制作和判断角色当前的状态和信息
本质上来讲,动画更新函数就是一个定时器,在启动的时候就规定每隔一段时间就执行一次,直到这个定时器被移除(停止)
例如移动,先写一个函数,告诉编译环境“我需要将玩家移动到离当前位置距离为x,方向为v的位置”
然后将其放进动画函数里,动画函数就会按照规定的时间跨度一直持续执行“我需要……”这个函数
很多时候,由于开发者的疏漏或考虑不周,如果移动速度为x,他们会直接设定为“每一帧移动x这么远”
这是非常严重的错误
如果没有额外的锁帧技术,游戏每一帧将会以一个绝对的距离更新位置,但游戏运行的帧速不一定是绝对的
例如在一些高端设备游戏能够跑到90帧,意味着每一秒会执行移动90次,如果移动速度为10,那么在这个高端设备上一秒的移动距离为90*10=900
但在一些中低端设备只能跑60帧,这时一秒只会执行移动60次,在这个低端设备一秒的移动距离为60*10=600,一秒移动的距离直接少了接近一半
连最基础的移动都会受到如此大的影响,更不用说其他需要额外判定的功能和需要实际播放的动画
这下问题明了了,但如何解决?
我的答案是规定一个绝对的时间跨度(每隔16毫秒)更新一次动画,也就是一秒更新60次(1000/60≈16.6,这里取整为16毫秒)
请注意,这并不是说等到16毫秒后再执行一次动画更新函数,动画的执行和动画更新函数的执行是分开的
动画更新函数依旧按照原有的最高帧率执行,但在其中获取此次调用动画更新函数的时间戳,在判断到下一个时间戳之间的差大于等于16毫秒后就执行一次动画函数
具体例子为:
我有一个函数的名称为fun移动(),动画更新函数的名称为fun更新()
fun更新()执行的时候会获取一次时间戳,假设当前的时间戳为0
fun更新()第二次执行的时候又获取一次时间戳,假设当前的时间戳为8毫秒(模拟120帧的情况),此时时间差为8-0=8毫秒,不够16毫秒,所以继续执行fun更新()
fun更新()第三次执行的时候又获取一次时间戳,假设当前的时间戳为16毫秒,满足16-0=16毫秒的条件,此时执行一次fun移动(),恢复时间戳记录时间为0,继续进行从第一步的判断
这套方法的好处是对于一些帧动画有很强的适配性(例如2d格斗游戏中的动作序列帧),不会出现因为帧数高而导致快速鬼畜播放的效果
知道了帧数如何影响游戏体验后,再来谈谈《因果》中出现的“高于60hz就会卡死”的bug
我目前的所有游戏作品均由H5技术(html+css+javascript)开发,目的是为了进行多平台打包和分发,其中pc端使用nw.js来打包成exe文件,移动端使用cordova来打包成安装包
在js(javascript)中一般使用requestAnimationFrame()来更新动画,这个函数可以跑满设定的最高屏幕刷新率的效果来执行,且每次调用的时候都会返回一个名为currentTime的时间戳
方案和上文中提到的方案一样,但问题是我错误地将“恢复时间戳记录时间”这一行写到了函数末尾而不是“判断时间差大于16毫秒”里
这是冲刺代码,本来lastTime=null这一行应该写成lastTime=currenTime的
下面是较为完整的代码
谨记不要熬夜写代码
我当时的思路应该是通过给lastTime赋值为null,再进行判断就会达到更新lastTime的目的
结果把lastTime=currentTime放到了函数的最后面
这就导致每执行一次动画更新函数都会恢复一次时间戳记录时间,在高刷屏上根本不会达成判断条件(即frametimeset>=16),预期中的下一帧根本不会到来,导致画面卡死(但仍然在运行)
解决方法很简单,将lastTime=null改成lastTime=currenTime,或将本存在于函数末尾的lastTime=currenTime移动到最前面
问题是所有的动画控制方案都是这个模板,想全改是一项十分难绷的工程
另外还有一些有动态刷新率的设备,保不准真的会以一个很低的刷新率来运行
还是谨记不要熬夜写代码吧
还好在正式上线之前《窗口旅行 Out Of Bounds》的作者反映了游戏按下shift键就会卡死的现象,经过排查后确定是屏幕刷新率的问题,紧急处理后在开头新增了需要将刷新率调到60hz才能游玩的提示
在这里再次感谢《窗口旅行 Out Of Bounds》的作者,也希望大家多多支持《窗口旅行 Out Of Bounds》!

