音游判定实测:Perfect 窗口只有 40ms,差 120ms 连击就断
音游里那个「Perfect」不是手感是几个毫秒区间。我把判定规则做成一个能玩的网页偏差落在 ±40ms 内算 PERFECT41 到 80ms 算 GREAT81 到 120ms 算 GOOD超过 120ms 算 MISS连击就断在这里。在线案例与源码https://atomgit.com/deli007/demo_park/tree/main/codearts-rhythm-tap-game页面是一个 3.8 万字符的单文件判定函数、计分公式、连击引擎和 25 条自检都在里面跑不发任何网络请求。一、为什么值得搞清楚判定窗口是手感的总开关打音游的时候你总觉得自己「明明按准了」。显示器、键盘、蓝牙耳机每一段都在偷走几毫秒。判定窗口就是把这些误差折算成成绩的那把尺子。尺子松人人都 PERFECT爽但没成就感尺子紧连击碎成渣玩两把就想摔手机。我更关心另一件事这把尺子到底怎么算边界在哪连击什么时候断。网上一搜全是「换个机械键盘」「记得校准延迟」的经验贴很少有人把判定函数摊开给你看。所以我把这件事做成了一个网页四档区间、判定函数、准确率公式全部写在页面上随便点随便改。二、准备环境进入码道 Web把需求一次说清浏览器打开码道 Web 版https://devcloud.cn-north-4.huaweicloud.com/chat?sourcedmzntgwsfsourceaddmzntgwwbwz 登录后就能在对话窗口输入需求不需要装软件。码道有三种使用方式WebUI浏览器对话、TUI终端命令行和桌面 IDEIDE 插件。本文用 WebUI 版演示。这一轮我把需求拆成 13 条一次性贴进对话单文件 index.html纯前端零依赖双击打开即用四条轨道 D / F / J / K音符从上方落下抵达判定线时按键判定判定四档写死偏差绝对值不超过 40ms 判 PERFECT41 到 80ms 判 GREAT81 到 120ms 判 GOOD超过 120ms 判 MISS判定线下方画一条判定窗口带用竖线标出 ±40 / ±80 / ±120ms 三档边界并写明毫秒数连击规则只有 MISS 断连击PERFECT、GREAT、GOOD 都续连击实时显示当前连击与最大连击准确率按权重算PERFECT 300 分、GREAT 200 分、GOOD 100 分、MISS 0 分准确率 累计得分 ÷ 300 × 音符总数节拍用 performance.now() 计时音符间隔 60000 ÷ BPM ÷ 4也就是十六分音符设置区三项BPM 60~240、下落速度 1~10、全局偏移校准 -200ms 到 200ms自动试打按钮按 -200ms 到 200ms、步长 5ms 的固定序列击打一遍逐条打印落档并统计各档数量结算页显示各档数量、准确率、最大连击、用时、平均偏差页面底部至少 14 条自检判定函数、准确率公式、连击规则用同一套逻辑复算期望值必须现算参数非法时给中文提示并保留上一次的有效值配色区分四档四档用四种颜色第十一条是我特意加进去的。它决定了后面所有数字能不能自证。三、四档判定怎么算判定函数一共四行核心就是把偏差取绝对值再比区间functionjudgeOffset(dev){constaMath.abs(dev);if(a40)returnPERFECT;if(a80)returnGREAT;if(a120)returnGOOD;returnMISS;}边界是闭区间40ms 整还算 PERFECT41ms 掉到 GREAT。档位偏差范围计分权重连击PERFECT绝对值 ≤ 40ms300续GREAT41 ~ 80ms200续GOOD81 ~ 120ms100续MISS大于 120ms或整拍没打0断准确率不是「命中数 ÷ 总数」而是加权得分除以满分准确率(PERFECT*300GREAT*200GOOD*100)/(300*音符总数)*100%这个公式解释了一个反直觉的现象。打 10 个全 GREAT准确率只有 66.67%虽然你一个都没漏。反过来漏掉一半但另一半全 PERFECT准确率就是 50%。它奖励的是「准」不是「不漏」这也是音游排行榜上所有人都在追 PERFECT 的原因。连击规则单独抽了出来只有 MISS 归零functionapply(j){if(jMISS)combo0;else{combocombo1;if(combomax)maxcombo;}}GOOD 也续连击这一点很多游戏不一样我在页面的判定规格里写清了免得读者按自己的习惯理解。四、实测81 个偏差落在哪一档窗口边界光看代码不过瘾我让页面自己数了一遍。自动试打从 -200ms 打到 200ms每 5ms 取一个点一共 81 个偏差逐个过判定函数读数如下档位命中偏差个数期望公式校验PERFECT172 × floor(40 ÷ 5) 1一致GREAT162 × (floor(80 ÷ 5) − floor(40 ÷ 5))一致GOOD162 × (floor(120 ÷ 5) − floor(80 ÷ 5))一致MISS3281 − 17 − 16 − 16一致17 个 PERFECT 对应 -40ms 到 40ms 之间的 17 个采样点两侧各 8 个再加中间的 0ms。期望值那一列不是我算好写死在页面里的是页面用同一套边界现算出来的。页面底部那 25 条自检就是这么比的左侧是现算的期望右侧是实跑的读数两边对不上就标红。图 2 下半部分就是这批自检左侧是现算的期望值右侧是实跑的读数。跑完这一轮自检 25/25 通过没有一条是写死的常量。五、把整局跑一遍64 个音符、平均偏差 -0.05ms单看 81 个静态偏差还不够得让判定函数在真实计时下跑一局。我用脚本按音符时刻逐个击打一局 64 个音符结算页结果如下指标读数PERFECT64GREAT / GOOD / MISS0 / 0 / 0准确率100.00%最大连击64用时9.7 s平均偏差-0.05 ms这一局是机器按节拍打的不是手打的成绩本身没有参考价值。有价值的是平均偏差那一格-0.05ms。它说明 performance.now() 这条计时链没有系统性偏差音符时刻换算和判定扣偏差这两步是能对上的。如果时间戳换算写错了半拍或者全局偏移扣错了方向这一格不会是 0 附近的小数。手打的实际分布长这样图 4 里飘出来的 0.5ms、1.2ms、2.0ms 是每一击的实际偏差正数代表延后负数代表提前。同一个玩家、同一首曲子这一串数字会明显散开那才是人的手感。六、参数动了判定会怎么变页面里能调的有三样我把边界值都试了一遍。参数范围影响实测例子BPM60 ~ 240音符间隔 60000 ÷ BPM ÷ 4BPM 60 → 250.0ms120 → 125.0ms240 → 62.5ms下落速度1 ~ 10音符从顶部落到判定线的时长 3600 ÷ 速度速度 5 → 720ms 落到判定线全局偏移-200 ~ 200ms从击打偏差里扣掉这个值再判档击打偏差 85ms、偏移填 50ms → 按 35ms 判档全局偏移那一行值得单独说。很多人以为校准是「让判定变松」其实它只是把偏差平移一个固定量。85ms 减掉 50ms 得到 35ms落回 PERFECT要是偏移填成 -50ms同一个 85ms 就变成 135ms直接 MISS。它治的是设备延迟不是手感。参数填错属于非法输入页面会拦下来给出中文错误提示并退回上一次的有效值提示原文是「BPM需为 60 ~ 240 之间的数字已保留上一次有效值 120」输入框同时回填成 120。七、验证与踩坑页面跑出来的每个数字我都用 Python 另写了一份来对拍。python verify_rhythm.py脚本不读 index.html把判定函数、准确率公式、连击引擎、偏差序列全部重写一遍再和页面读数逐项比判定函数 9 个边界点0 / ±40 / ±41 / ±80 / ±81 / ±120 / ±121可击窗口 ±120 可击、±121 不可击十六分音符间隔 BPM 60 / 120 / 240 → 250.0 / 125.0 / 62.5准确率的四组取值2P1G1GD1M、全 PERFECT、全 GREAT、全 MISS连击的三组序列81 个偏差的四档统计整局 64 音符准确率与最大连击结果 27/27 通过逐项都对得上。交付之后我又逐张放大截图发现了一个不太好意思的毛病。判定飘字直接把浮点原值打了出来屏幕上出现的是「48.19999980926514ms」这种数字。原因就一行飘字模板里写的是原始偏差没有做舍入。修法是给这一处加上一位小数的舍入同一个位置的状态栏「上一击」也一起改改完读数变成 48.2ms。这是一处显示问题判定结果没受影响——46ms 和 48.2ms 都落在 41~80ms 那档都是 GREAT。页面还有两处我如实记下来。一是自动试打只跑判定函数不参与当时那一局的统计所以它不会污染你的成绩。二是平均偏差只统计实际击打MISS 没有击打偏差不进平均。八、本地复现三条命令就能自己跑一遍gitclone https://atomgit.com/deli007/demo_park.gitcddemo_park/codearts-rhythm-tap-game python verify_rhythm.py习惯看页面的直接双击 index.html 也行打开就自动跑一次 25 条自检。想自己量判定边界点页面里的「自动试打」81 个偏差逐个落档日志区会一行行打出来。两条边界得说清楚。判定窗口 ±40 / ±80 / ±120ms 是我在需求里定死的真实商业音游各家窗口不一样这里量的是「这套规则」而不是「所有音游」。页面也没有测键盘和显示器的真实延迟偏移校准要你自己填一个数试。九、使用码道体会这一轮需求里最值钱的两句是「期望值必须现算」和「至少 14 条自检判定函数、准确率、连击用同一套逻辑复算」。它们把「看起来对」和「验过是对的」分开了。81 个偏差那张统计表之所以能当证据就是因为期望值那一列是我写完之后页面自己算的不是我抄进去的。另一句是「参数非法时给中文提示并保留上一次的有效值」。非法输入的处理直接抓得到提示原文我不用去猜填错之后页面是崩掉还是装作没看见。13 条需求全写出来确实费时间但交付后我只提了一轮修改点省下来的返工时间比写需求多。十、总结判定窗口就是一把尺子四档区间加一个权重公式音游的手感差别大半来自这里。连击断在 MISS 而不是 GOOD是让「打得准」比「打得全」更值钱。全局偏移修正只治病不治手感填错方向反而更糟。想让自己的判定更稳先量三样东西每击偏差落在哪一档、平均偏差偏正还是偏负、连击一般断在第几个音。源码和在线案例在 https://atomgit.com/deli007/demo_park/tree/main/codearts-rhythm-tap-game判定函数和自检都写在页面上改几个数就能自己量一遍。