加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
本帖最后由 逸豫 于 2022-2-28 19:09 编辑
不知道以前有没有人提出过,MV的游戏有时候 明明FPS没有降低,但是游戏玩起来就是卡卡的,尤其是在地图滚动时。
最近终于下定决心去研究这个问题,发现问题出在MV的以帧为基础的刷新机制上。
给不懂代码的人:
命名为JitterFix.js,然后放到plugin文件夹中,直接启用。
代码发布到public domain,你可以随意不署名甚至署你自己的名字使用(当然你愿意署我的名更好)。
/*:
* @plugindesc Fix the graphics jitter mostly noticeable when scrolling maps.
* @author Zumi Kua
*
* @help just enable this plugin, no need to do anything.
*/
/*: zh
* @plugindesc 修复游戏地图在滚动时不定时出现的卡顿感
* @author Zumi Kua
*
* @help 实际原因为performance.now返回的数值似乎会有误差,导致两帧之间的deltaTime在1/60上下来回横跳,进而导致在一次
* requestAnimationFrame回调中一次updateScene也没调用,然后在下一次requestAnimationFrame回调中连续调用两次updateScene
* 无法解决在非60Hz的显示器上的卡顿问题
*
* 使用requestAnimationFrame作为参数提供的DOMHighResTimeStamp似乎不会有该问题
*
*/
SceneManager.update = function ( stamp) {
try {
this .tickStart ( ) ;
if ( Utils.isMobileSafari ( ) ) {
this .updateInputData ( ) ;
}
this .updateManagers ( ) ;
this .updateMain ( stamp) ;
this .tickEnd ( ) ;
} catch ( e) {
this .catchException ( e) ;
}
} ;
const DEBUG = false ;
SceneManager.updateMain = function ( stamp) {
if ( Utils.isMobileSafari ( ) ) {
this .changeScene ( ) ;
this .updateScene ( ) ;
} else {
let fTime = ( stamp - this ._currentTime) / 1000 ;
if ( fTime > 0.25 ) fTime = 0.25 ;
this ._currentTime = stamp;
this ._accumulator += fTime;
const old_ftime = fTime;
const old_accu = this ._accumulator;
let i = 0 ;
while ( this ._accumulator >= this ._deltaTime) {
i++;
this .updateInputData ( ) ;
this .changeScene ( ) ;
this .updateScene ( ) ;
this ._accumulator -= this ._deltaTime;
}
if ( DEBUG && i !== 1 ) {
console.log ( i, old_ftime, old_accu) ;
}
}
this .renderScene ( ) ;
this .requestUpdate ( ) ;
} ;
/*:
* @plugindesc Fix the graphics jitter mostly noticeable when scrolling maps.
* @author Zumi Kua
*
* @help just enable this plugin, no need to do anything.
*/
/*: zh
* @plugindesc 修复游戏地图在滚动时不定时出现的卡顿感
* @author Zumi Kua
*
* @help 实际原因为performance.now返回的数值似乎会有误差,导致两帧之间的deltaTime在1/60上下来回横跳,进而导致在一次
* requestAnimationFrame回调中一次updateScene也没调用,然后在下一次requestAnimationFrame回调中连续调用两次updateScene
* 无法解决在非60Hz的显示器上的卡顿问题
*
* 使用requestAnimationFrame作为参数提供的DOMHighResTimeStamp似乎不会有该问题
*
*/
SceneManager.update = function ( stamp) {
try {
this .tickStart ( ) ;
if ( Utils.isMobileSafari ( ) ) {
this .updateInputData ( ) ;
}
this .updateManagers ( ) ;
this .updateMain ( stamp) ;
this .tickEnd ( ) ;
} catch ( e) {
this .catchException ( e) ;
}
} ;
const DEBUG = false ;
SceneManager.updateMain = function ( stamp) {
if ( Utils.isMobileSafari ( ) ) {
this .changeScene ( ) ;
this .updateScene ( ) ;
} else {
let fTime = ( stamp - this ._currentTime) / 1000 ;
if ( fTime > 0.25 ) fTime = 0.25 ;
this ._currentTime = stamp;
this ._accumulator += fTime;
const old_ftime = fTime;
const old_accu = this ._accumulator;
let i = 0 ;
while ( this ._accumulator >= this ._deltaTime) {
i++;
this .updateInputData ( ) ;
this .changeScene ( ) ;
this .updateScene ( ) ;
this ._accumulator -= this ._deltaTime;
}
if ( DEBUG && i !== 1 ) {
console.log ( i, old_ftime, old_accu) ;
}
}
this .renderScene ( ) ;
this .requestUpdate ( ) ;
} ;
致对原因感兴趣的人:
首先来看MV的游戏主循环代码:
SceneManager.updateMain = function ( ) {
if ( Utils.isMobileSafari ( ) ) {
this .changeScene ( ) ;
this .updateScene ( ) ;
} else {
var newTime = this ._getTimeInMsWithoutMobileSafari( ) ;
var fTime = ( newTime - this ._currentTime) / 1000 ;
if ( fTime > 0.25 ) fTime = 0.25 ;
this ._currentTime = newTime;
this ._accumulator += fTime;
const old_accu = this ._accumulator;
let i = 0 ;
while ( this ._accumulator >= this ._deltaTime) {
++i;
this .updateInputData ( ) ;
this .changeScene ( ) ;
this .updateScene ( ) ;
this ._accumulator -= this ._deltaTime;
}
if ( i !== 1 ) {
console.log ( i, fTime, old_accu) ;
}
}
this .renderScene ( ) ;
this .requestUpdate ( ) ;
} ;
SceneManager.updateMain = function ( ) {
if ( Utils.isMobileSafari ( ) ) {
this .changeScene ( ) ;
this .updateScene ( ) ;
} else {
var newTime = this ._getTimeInMsWithoutMobileSafari( ) ;
var fTime = ( newTime - this ._currentTime) / 1000 ;
if ( fTime > 0.25 ) fTime = 0.25 ;
this ._currentTime = newTime;
this ._accumulator += fTime;
const old_accu = this ._accumulator;
let i = 0 ;
while ( this ._accumulator >= this ._deltaTime) {
++i;
this .updateInputData ( ) ;
this .changeScene ( ) ;
this .updateScene ( ) ;
this ._accumulator -= this ._deltaTime;
}
if ( i !== 1 ) {
console.log ( i, fTime, old_accu) ;
}
}
this .renderScene ( ) ;
this .requestUpdate ( ) ;
} ;
关于MV的刷新代码为什么这么写,可以参考:
https://gafferongames.com/post/fix_your_timestep/
如果你有Unity的使用经验的话也可以回忆一下Unity的Update和fixedUpdate。
这个updateMain函数每次窗口重绘时都会被调用,可以理解为一般游戏的gameLoop循环体。
理想状态下,每次重绘距离上次的时间应该都为1/60(假设我们的显示器刷新率为60hz),那么,每次重绘都会对应一次updateScene。
当此次重绘距离上次的时间大于1/60,则此次调用updateScene后,多余的时间会记录在accumulator中,供下次使用
而当此次重绘距离上次的时间小于1/60,则该帧不会进行updateScene调用,全部的时间会记录在accumulator中,供下次使用
那么,如果每次重绘距离上次重绘的时间在1/60左右反复横跳,即每次重绘的时间大概是这样呢:
2 0.01699999999254942 0.033333333612730484
0 0.015999999828636646 0.01633333343391655
2 0.018000000156462193 0.03433333359037874
0 0.016000000294297934 0.01633333371331332
2 0.01699999999254942 0.033333333705862736
0 0.015999999828636646 0.016333333527048802
2 0.018000000156462193 0.03433333368351099
0 0.015999999828636646 0.016333333340784285
2 0.01699999999254942 0.0333333333333337
2 0.01699999999254942 0.033333333612730484
0 0.015999999828636646 0.01633333343391655
2 0.018000000156462193 0.03433333359037874
0 0.016000000294297934 0.01633333371331332
2 0.01699999999254942 0.033333333705862736
0 0.015999999828636646 0.016333333527048802
2 0.018000000156462193 0.03433333368351099
0 0.015999999828636646 0.016333333340784285
2 0.01699999999254942 0.0333333333333337
(请看中间列,它代表了每次重绘距离上次重绘的时间,顺带一提,第一列是该帧调用updateScene的次数)
可以看出,当距离上次重绘的时间小于1/60时,因为目前距离上一逻辑帧经过的时间不足一逻辑帧的时间,所以这次重绘不会调用updateScene。
当距离上次重绘的时间大于1/60时,加上上一帧小于1/60所留下的时间,我们现在有了两逻辑帧所需要的时间,这次重绘会调用两次updateScene。
而这样重绘时间在大于1/60与小于1/60之间来回横跳,正是卡顿感的成因。
而至于原因,目前我还没法给出一个确切的答案,不过解决方法倒是有:
requestAnimationFrame也会传一个当前时间作为参数,它传回的这个当前时间比我们用_getTimeInMsWithoutMobileSafari获得的要准确的多,将我们通过_getTimeInMsWithoutMobileSafari获取的时间戳改为requestAnimationFrame传回的时间戳,卡顿的情况也的确消失了。
(这也引出了一个这个现象可能的原因,MV在调用updateMain之前也调用了一些其他的函数,而这些函数执行消耗的时间不固定,且消耗的时间长到无法忽略)
另:如果你的显示器刷新率不是60hz,那么卡顿感应该是无法解决的。(以144hz为例,3帧中必然有2帧是不会调用updateScene的)。
这个也发布于rpgmakerweb,作者也是我:https://forums.rpgmakerweb.com/i ... -stuttering.144992/