目前实习在的项目组的产品,经常有玩家反馈某个地方倒计时会变快,内部也有小伙伴发现了这个问题,在 iPad Pro 上更是达到了 2 倍速, 但因为受影响的用户很少很少,一直没修。上周,因为日程上不是很忙,便找了一天去修复这个 bug。
经过测试, web 端不会出现这个情况,但微信小游戏上会有速度变快。
首先是在白鹭的开发者论坛上,找了个一篇帖子基于系统时间的计时器 DateTimer(不受 FPS 影响)看了下代码, 没有问题, 根据时间戳计算差值,然后再 dispatch 事件。 便直接放进项目里尝试,确实解决了倒计时变快的问题,但这很怪异,白鹭不应该会有这么奇怪的 bug,然后我的 Leader 拉了个群, 其中包括引擎组和隔壁项目组的大佬们,讨论这个问题。
其中一位指出,白鹭也是这么做的,通过查看源码,确实是的, 但在注释里标记了,最高支持到 60 FPS,而 iPad Pro 是 120 FPS 的设备,因此导致了 1000ms Delay 的 Timer,会在 1 秒内触发两次,而我们的倒计时并没有计算时间的差值,而是直接 timer--
所以导致速度异常。
而 Web 端没有问题是因为项目有限制 FPS 到 60, 但这个限制对微信小游戏不好使,解决方法是手动使用 wx.setPreferredFramesPerSecond(fps)
,限制 FPS 到 60。
分析
ergret-core/src/egret/utils/Timer.ts source
101
102
103
104
105
106
107
108
109
110
| public set delay(value: number) {
if (value < 1) {
value = 1;
}
if (this._delay == value) {
return;
}
this._delay = value;
this.lastCount = this.updateInterval = Math.round(60 * value);
}
|
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
| /**
* @private
* Ticker 以 60FPS 频率刷新此方法
*/
$update(timeStamp: number): boolean {
let deltaTime = timeStamp - this.lastTimeStamp;
if (deltaTime >= this._delay) {
this.lastCount = this.updateInterval;
}
else {
this.lastCount -= 1000;
if (this.lastCount > 0) {
return false;
}
this.lastCount += this.updateInterval;
}
this.lastTimeStamp = timeStamp;
this._currentCount++;
let complete = (this.repeatCount > 0 && this._currentCount >= this.repeatCount);
if (this.repeatCount == 0 || this._currentCount <= this.repeatCount) {
egret.TimerEvent.dispatchTimerEvent(this, egret.TimerEvent.TIMER);
}
if (complete) {
this.stop();
TimerEvent.dispatchTimerEvent(this, TimerEvent.TIMER_COMPLETE);
}
return false;
}
|
白鹭在设置 delay 的时候,会对 updateInterval
赋值 60 * dealy
,然后 $update
方法中,会对这个值做计算,然后调用事件, 但 $update
方法是以系统默认的刷新率刷新的。
比如在 60fps 设备上用 1/60s 调用 $update
,而在 120fps 设备上是以 1/120s
调用,但 updateInterval
却还保持在 60 * dealy
,因此导致在 120fps 设备上,设置的 dalay 从原本 1000ms,变成了 500ms,也就导致了 1s 内发了两次事件