设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 4184|回复: 15
打印 上一主题 下一主题

[原创发布] 解决MV游戏明明FPS为60但运行时不流畅、有抖动感的问题

[复制链接]

Lv3.寻梦者

宛若

梦石
0
星屑
1568
在线时间
526 小时
注册时间
2007-8-19
帖子
1493

极短24参与开拓者

跳转到指定楼层
1
发表于 2022-2-28 00:45:12 | 只看该作者 回帖奖励 |正序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
本帖最后由 逸豫 于 2022-2-28 19:09 编辑

不知道以前有没有人提出过,MV的游戏有时候明明FPS没有降低,但是游戏玩起来就是卡卡的,尤其是在地图滚动时。

最近终于下定决心去研究这个问题,发现问题出在MV的以帧为基础的刷新机制上。

给不懂代码的人:

命名为JitterFix.js,然后放到plugin文件夹中,直接启用。

代码发布到public domain,你可以随意不署名甚至署你自己的名字使用(当然你愿意署我的名更好)。
JAVASCRIPT 代码复制
  1. /*:
  2.  * @plugindesc Fix the graphics jitter mostly noticeable when scrolling maps.
  3.  * @author Zumi Kua
  4.  *
  5.  * @help just enable this plugin, no need to do anything.
  6.  */
  7. /*: zh
  8.  * @plugindesc 修复游戏地图在滚动时不定时出现的卡顿感
  9.  * @author Zumi Kua
  10.  *
  11.  * @help 实际原因为performance.now返回的数值似乎会有误差,导致两帧之间的deltaTime在1/60上下来回横跳,进而导致在一次
  12.  * requestAnimationFrame回调中一次updateScene也没调用,然后在下一次requestAnimationFrame回调中连续调用两次updateScene
  13.  * 无法解决在非60Hz的显示器上的卡顿问题
  14.  *
  15.  * 使用requestAnimationFrame作为参数提供的DOMHighResTimeStamp似乎不会有该问题
  16.  *
  17.  */
  18.  
  19. SceneManager.update = function(stamp) {
  20.     try {
  21.         this.tickStart();
  22.         if (Utils.isMobileSafari()) {
  23.             this.updateInputData();
  24.         }
  25.         this.updateManagers();
  26.         this.updateMain(stamp);
  27.         this.tickEnd();
  28.     } catch (e) {
  29.         this.catchException(e);
  30.     }
  31. };
  32.  
  33. const DEBUG = false;
  34.  
  35. SceneManager.updateMain = function(stamp) {
  36.     if (Utils.isMobileSafari()) {
  37.         this.changeScene();
  38.         this.updateScene();
  39.     } else {
  40.         let fTime = (stamp - this._currentTime) / 1000;
  41.         if (fTime > 0.25) fTime = 0.25;
  42.  
  43.         this._currentTime = stamp;
  44.         this._accumulator += fTime;
  45.         const old_ftime = fTime;
  46.         const old_accu = this._accumulator;
  47.         let i = 0;
  48.         while (this._accumulator >= this._deltaTime) {
  49.             i++;
  50.             this.updateInputData();
  51.             this.changeScene();
  52.             this.updateScene();
  53.             this._accumulator -= this._deltaTime;
  54.         }
  55.         if(DEBUG && i !== 1){
  56.             console.log(i, old_ftime, old_accu);
  57.         }
  58.     }
  59.     this.renderScene();
  60.     this.requestUpdate();
  61. };


致对原因感兴趣的人:

首先来看MV的游戏主循环代码:
JAVASCRIPT 代码复制
  1. SceneManager.updateMain = function() {
  2.     if (Utils.isMobileSafari()) {
  3.         this.changeScene();
  4.         this.updateScene();
  5.     } else {
  6.         var newTime = this._getTimeInMsWithoutMobileSafari();
  7.         var fTime = (newTime - this._currentTime) / 1000;
  8.         if (fTime > 0.25) fTime = 0.25;
  9.         this._currentTime = newTime;
  10.         this._accumulator += fTime;
  11.         const old_accu = this._accumulator;
  12.         let i = 0;
  13.         while (this._accumulator >= this._deltaTime) {
  14.             ++i;
  15.             this.updateInputData();
  16.             this.changeScene();
  17.             this.updateScene();
  18.             this._accumulator -= this._deltaTime;
  19.         }
  20.         if(i !== 1){
  21.             console.log(i, fTime, old_accu);
  22.         }
  23.     }
  24.     this.renderScene();
  25.     this.requestUpdate();
  26. };


关于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左右反复横跳,即每次重绘的时间大概是这样呢:

代码复制
  1. 2 0.01699999999254942 0.033333333612730484
  2. 0 0.015999999828636646 0.01633333343391655
  3. 2 0.018000000156462193 0.03433333359037874
  4. 0 0.016000000294297934 0.01633333371331332
  5. 2 0.01699999999254942 0.033333333705862736
  6. 0 0.015999999828636646 0.016333333527048802
  7. 2 0.018000000156462193 0.03433333368351099
  8. 0 0.015999999828636646 0.016333333340784285
  9. 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/

评分

参与人数 5星屑 +600 +4 收起 理由
rfvtgbzxc + 1 精品文章
竹染蓝 + 1
cbi1990076 + 1
fux2 + 600 精品文章
马铃薯条 + 1

查看全部评分

[url=http://rpg.blue/thread-219730-1-1.html]http://unhero.sinaapp.com/wi.php[/url]
[color=Red]如你所见这是个死坑,没错这就是打我的脸用的[/color]

Lv1.梦旅人

梦石
0
星屑
55
在线时间
33 小时
注册时间
2021-3-28
帖子
14
16
发表于 2023-10-9 15:58:24 | 只看该作者
非常感谢大佬,好了很多
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
3591
在线时间
1890 小时
注册时间
2010-6-19
帖子
1211
15
发表于 2022-6-10 17:31:22 | 只看该作者
能否给Xp也写一个,最近就遇到这类情况
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
2389
在线时间
363 小时
注册时间
2021-2-8
帖子
149
14
发表于 2022-6-10 16:52:44 | 只看该作者
我把它和Liquidize那个一起用,希望不会有什么问题
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
130
在线时间
27 小时
注册时间
2022-6-4
帖子
26
13
发表于 2022-6-10 16:14:37 | 只看该作者
非常感谢,极大帮助了我这种代码小白
回复 支持 反对

使用道具 举报

Lv4.逐梦者

梦石
0
星屑
7649
在线时间
1437 小时
注册时间
2018-12-16
帖子
2022
12
发表于 2022-3-5 22:34:39 | 只看该作者
Bug Fix: Event Jitter / Display Rounding Error
2019年4月9日 — Just tried it out with a smooth scrolling plugin I had installed, ... found the fix and I just requested Liquidize to do it for MV as well.
刚刚用我安装的平滑滚动插件试了一下,......找到了修复,我只是要求 Liquidize 也为 MV 做这件事。
https://forums.rpgmakerweb.com/i ... nding-error.107752/

这东西相同的好象有2-3个... 之前我就是过全部的 平滑移动 不要卡卡的插件全部都试过了...

--------------------------
楼主这插件 害我又心动了一下 结果一样... 更卡... 跟其他的都一样 变得更卡...
--------------------------
我后来 找到解决这个问题的方法就是 换 MZ...
不是因为帮MZ广告 是因为问题是出在 pixi.js 的版本...

然而你要更新这个版本. 不是你下载一个新的(就象坦克大大做的...看起来更新了...结果变成更多问题...插件函数都不能跑 )

你要更新 pixi.js 然后完全没有问题 就只有MZ一个方法...
--------------------------------
所以后来我已经放弃了... 卡就卡吧....

反正 HTML5 的游戏 哪个不卡...

之后没多久就会出的 RPG MAKER UNITY... 就会完全解决这个问题....

期待吧...

回复 支持 反对

使用道具 举报

Lv2.观梦者

梦石
0
星屑
691
在线时间
50 小时
注册时间
2021-12-20
帖子
18
11
发表于 2022-3-5 21:52:19 | 只看该作者
留个眼
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
976
在线时间
107 小时
注册时间
2020-6-16
帖子
142
10
发表于 2022-3-4 21:58:24 | 只看该作者
最近在用MV3D插件做游戏,事件多一点地图大一点就变得相当卡。感觉可以试试
为什么P1论坛不能改名!
回复 支持 反对

使用道具 举报

Lv2.观梦者

梦石
0
星屑
799
在线时间
81 小时
注册时间
2018-3-27
帖子
36
9
发表于 2022-3-4 21:24:06 | 只看该作者
谢谢大佬,之前一直以为是灯光插件造成的原因。
之前了解到分辨率不能整除也会造成抖动,现在用上插件之后瞬间平滑起来。
回复 支持 反对

使用道具 举报

Lv3.寻梦者

宛若

梦石
0
星屑
1568
在线时间
526 小时
注册时间
2007-8-19
帖子
1493

极短24参与开拓者

8
 楼主| 发表于 2022-2-28 19:21:59 | 只看该作者
nhycs01 发表于 2022-2-28 19:14
感谢大佬解释,我确实目前没有感觉到这种抖动感,但不排除可能是我自己没有意识到的原因。
我想最后问一 ...

冲突的话……还是有产生的风险,毕竟我覆写了一些MV自带的函数。
不过添加这个脚本应该是不会引入效率问题的。
[url=http://rpg.blue/thread-219730-1-1.html]http://unhero.sinaapp.com/wi.php[/url]
[color=Red]如你所见这是个死坑,没错这就是打我的脸用的[/color]
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-11-17 06:20

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表