使用方法
- 先定参数:「每轮射击次数」示例 10(允许 3 ~ 30),「靶心半径」示例 28 px,「内环半径」示例 12 px,「超时判定」示例 2000 ms。
- 点「开始训练」,靶心立刻出现在 300×300 的活动区里,位置用极坐标均匀取点(
r = R × √U,不是r = R × U,原因见原理一节)。 - 点中靶心即记一次射击,表格立刻多出一行:反应时(ms)、距靶心(px)、判定(内环 / 环带 / 脱靶 / 超时)、本发用时。
- 打完 10 次自动结算,结果区给出命中率、中位反应时、截尾均值与标准差四个数;中途想停就点「结束本轮」,用已打的次数结算。
- 想直接看统计口径的效果,点「载入示例」再点「结束本轮」,会载入一组含 1 次离群值的标准样本(10 个值、其中 1 个 2402 ms),对照正文里那张统计表逐位核对。
这个瞄准训练能解决什么问题
它解决的不是「打得更准」,而是把「准不准」和「快不快」拆成两个可测量的数。瞄准训练做 FPS 精准度练习时有个常见混淆:打偏了是瞄得不好,还是手慢导致靶移动了才打偏?这两者的改进方式完全不同 —— 前者要练稳定性,后者要练预瞄提前量。混在一起看只有一个「命中率」,没法指导训练。
第二个用途是看清自己的反应时分布,而不是一个平均数。同一组 10 次射击,均值可能是 618 ms,中位数只有 421 ms,差 197 ms、近 47%。如果你按均值给自己定训练目标,会以为自己在「一分钟 600 毫秒」这个水平,实际上你的典型水平是 420 ms,只是偶尔有一次手滑把平均拉高了。基于被拉高的数字做判断,是训练类工具最常见的问题。
第三个用途是验证难度递增是否真的造成了压力。瞄准训练让靶心每 3 次收缩一定像素:28 → 22 → 16 px。面积比是 1.62 倍和 1.89 倍,最后一档 28 → 16 px 面积缩到 1/3.06。如果你的反应时在靶心缩到 16 px 之前稳定、之后明显变长,说明你的瓶颈在视觉搜索而不是手部控制 —— 这时该练的是预瞄,而不是继续加快点击速度。
本页不做的事同样要说清。它不是竞技精度评测:浏览器拿不到鼠标 DPI、驱动固件、显示器刷新率和系统输入延迟,测出来的绝对值和你在游戏里的 ms 有系统性差异,只能用于同一浏览器、同一台机器、自己和自己比。它也不记录弹道与后坐力(没有枪械模型),不做灵敏度换算(1 审言还是 40 灵敏度是另一套 DPI 公式,本页不做),更不替代专业靶场。前端唯一的例外是它能提供别处得不到的东西:逐发数据,而不只是一个总分。
原理与规则说明
命中判定是圆,不是方形。判定条件只有一条:Math.hypot(dx, dy) <= r,其中 dx、dy 是点击点与靶心的横纵向距离,r 是外半径。用闭区间 <= 而不是 <,是为了让边界像素算命中,避免用户觉得「明明碰到了却没算」。这个选择有实际代价:靶心半径 28 px 时,圆判定面积是 π×28² = 2463 平方像素,占 300×300 活动区的 2.74%;如果图省事用方形判定 |dx| <= r && |dy| <= r,面积是 (2×28)² = 3136,是圆形的 1.2732 倍。多出来的那块正是四个角的月牙区 —— 在 dx = dy = 25.2 px(0.9r)处,hypot 算出 35.64 大于 28,圆形判脱靶,而方形会算命中。方形判定让边缘更容易蒙中,成绩会系统性偏好,是常见但错误的实现。
内环与环带分开计分。内环半径 r1 = 12、外环 r2 = 28 时,内环面积 452、环带面积 2011,环带占靶面 81.6%。这个比例解释了为什么只记「命中 / 脱靶」信息量不够:81.6% 的靶面都算命中,但打在外圈和打在正中心的技术难度完全不同。本页的规则是内环记 1.0 分、环带记 0.5 分、脱靶与超时都记 0 分,且都保留在射击次数分母里。
靶心位置用面积口径均匀取点,这是本页第二个容易被写错的地方。极坐标取点写成 r = R × √U、θ = 2π × V(U、V 独立均匀),而不是看起来更直观的 r = R × U。原因是面积随 r² 增长:r = R × U 会让圆心附近的密度是边缘的 2 倍,靶老是往中间挤,一眼就能看出不对。实测 100 万次取点,把单位圆按 r ∈ [0, 0.5] 与 (0.5, 1] 分成两半统计,r = R × U 得到内半区 51.21%(面积上应占 25%),r = R × √U 得到 25.81%,与面积口径吻合。
反应时用 performance.now(),不用 Date.now()。前者返回 DOMHighResTimeStamp,单位毫秒且带小数,精度在 0.1 ms 量级;后者只精确到 1 ms,更麻烦的是它受系统时间调整影响 —— NTP 校时或用户手动改时间会让两次调用的差值变成负数。本页把超时判定的下界设在 800 ms,就是为了让这个计时口径下的噪声远小于阈值。
统计口径是本页的核心,四个指标各有分工:
- 中位数:排序后取中间那个,偶数个取中间两个的均值。它对离群值完全不敏感,是「典型水平」的唯一诚实度量。
- 算术均值:最常用,但对离群值敏感。本页标准样本 10 个值(412 / 388 / 455 / 401 / 430 / 379 / 468 / 2402 / 397 / 445 ms)的均值是 617.7 ms,中位数是 421.0 ms,差 196.7 ms、即 46.7%。
- 截尾均值:先用 1.5 × IQR 规则剔除离群值再求均值。上面这组数据的 Q1 = 398.0、Q3 = 452.5、IQR = 54.5,上界是 452.5 + 1.5×54.5 = 534.3 ms,2402 被准确剔除,剩下 9 个的均值是 419.4 ms、中位数 412.0 ms。三种口径的结论是:中位数 421 最稳,截尾均值 419 次之,算术均值 618 被拉得最狠。
- 标准差:看稳定度。把全部 10 个值(含 2402)算进来,总体标准差是 595.4 ms、CV 96.4%;只用 9 个有效值算是 29.6 ms、CV 7.1%。前者说明这组数据里有个异常值,后者才是你真实的稳定性。
超时的处理方式是:记 0 分、从反应时统计里剔除、但保留在命中率分母里。2402 ms 那一发如果计入均值,训练页面就会把「手滑」当成「手慢」汇报给你。三个地方的口径必须一致,否则命中率会和反应时互相矛盾。
还要说清一件容易搞混的事:脱靶的那一发仍然计入反应时统计。用户看到靶、反应了、也点了,只是没点中 —— 这是一发完整体现的反应时间,把它剔掉等于只统计「打得准的人的反应快」,会让反应时数据系统性偏乐观。脱靶影响的是命中率,不是时序统计。只有超时(根本没进入瞄准状态)才同时退出两个统计。
遵循的规范与口径:判定用 ECMA-262 定义的 Math.hypot(它内部做了溢出安全的平方和计算,比手写 Math.sqrt(dx*dx+dy*dy) 在极大坐标下更稳);计时用 W3C Performance Timeline 规范里的 performance.now();位置取点用极坐标面积口径;离群值剔除用 Tukey 的 1.5 × IQR 四分位距规则(这是统计学里最常用的离群判定惯例,不是本页自创的)。
完整示例(数值仅作演示)
示例条件:每轮 10 次,靶心半径 28 px,内环 12 px,每 3 次收缩 3 px,超时 2000 ms。载入的标准样本(单位 ms,形状贴近真实训练)如下:
- 逐发反应时:412 / 388 / 455 / 401 / 430 / 379 / 468 / 2402 / 397 / 445;
- 第 8 发的 2402 ms 超过 2000 ms 阈值,判为「离开窗口」,记 0 分并从反应时统计中剔除;
- 命中率 =(内环 3 + 环带 5)÷ 10 = 80%;分母是 10,含那 1 次超时;
- 内环 3 / 环带 5 / 脱靶 1 / 超时 1,得分 = 3×1.0 + 5×0.5 = 5.5;
- 有效反应时样本 = 9 个(只有超时被剔除,脱靶那一发仍计入,因为它确实是一发真实反应);
- 中位反应时 = 排序后第 5 位的 412.0 ms(n=9 为奇数取正中一个);
- 截尾均值 = (412+388+455+401+430+379+468+397+445) ÷ 9 = 419.4 ms;
- 若不剔除超时,均值会从 419.4 涨到 617.7 ms(含脱靶那一发),多出 198.3 ms;
- 总体标准差 29.6 ms,CV = 7.1%;
- 难度递增:第 1~3 发 28 px、第 4~6 发 22 px、第 7~10 发 16 px,面积比 1.62 与 1.89,首尾 3.06 倍。
这组数字与下方对照表、以及页面「载入示例」后点「结束本轮」的结果区是同一套代码算出来的,三处应当逐位一致。注意有效样本的截尾均值 419.4 与中位数 412.0 只差 7.4 ms(占均值 1.8%),说明剔除超时之后这组数据本身是集中一致的 —— 这才是能拿来训练的正信号。以上数值仅作演示,不是任何人的真实训练成绩。
输入范围与对照表
下表逐档列出 10 个示例反应时的统计结果,让你看清三种口径的差别。行数由「每轮射击次数」决定(3 ~ 30),表格默认渲染 12 行,点「加载更多」展开到全部行,也可导出 CSV(带 BOM)。表格本身在 JS 未执行时也有这 10 行固定示意数据,结构与真实输出一致。
| 序号 | 反应时(ms) | 距靶心(px) | 判定 | 本发用时 |
|---|---|---|---|---|
| 1 | 412 | 8.2 | 内环 1.0 | 412 ms |
| 2 | 388 | 21.5 | 环带 0.5 | 388 ms |
| 3 | 455 | 14.7 | 环带 0.5 | 455 ms |
| 4 | 401 | 26.3 | 环带 0.5 | 401 ms |
| 5 | 430 | 31.2 | 脱靶 0 | 430 ms |
| 6 | 379 | 10.4 | 内环 1.0 | 379 ms |
| 7 | 468 | 19.8 | 环带 0.5 | 468 ms |
| 8 | 2402 | — | 超时 0(已剔除) | 2402 ms |
| 9 | 397 | 6.1 | 内环 1.0 | 397 ms |
| 10 | 445 | 24.9 | 环带 0.5 | 445 ms |
三种统计口径的横向对照(这组是本页最该记住的表):
| 口径 | 样本数 | 本组结果 | 适用与不适用 |
|---|---|---|---|
| 算术均值 | 10 | 617.7 ms | 口径最常见,但对 1 个离群值毫无抵抗力 |
| 中位数 | 10 | 421.0 ms | 典型水平首选,完全不受离群值影响 |
| 截尾均值(1.5×IQR) | 9 | 419.4 ms | 兼顾稳健与信息量,本页默认展示 |
| 四分位距 IQR | 10 | 54.5 ms | 中间 50% 的散布,不受两端极值影响 |
读表要点:算术均值与中位数差 196.7 ms,这个差值本身就是诊断信息 —— 差得越多说明组内离群值越明显,而离群值往往不是「手慢」而是「没看准」或「分神」。注意前两行的样本数是 10(全部值),后两行的有效样本是 9(仅剔除超时那一条),脱靶那一发在四行里都算进去了。
输入范围、边界与精度说明
- 每轮 3 ~ 30 次。少于 3 次算不出中位数的稳定值,多于 30 次用户会疲劳、后半程数据被「随便点点」污染,而疲劳本身会抬高反应时,测出来的就不是你想测的东西。默认 10 次。
- 靶心半径 10 ~ 40 px,内环 4 ~ 20 px。下限 10 px 是刻意的:再小的话,浏览器里 (0,0) 处的亚像素抖动和指针轨迹抖动开始主导,你测到的是设备而不是自己。收缩量为 0 ~ 6 px / 每 3 发,允许关掉难度递增做纯反应时测试。
- 内环半径必须小于外半径,否则环带面积为负、判定逻辑没有意义。页面会直接拦住并说明「内环半径要小于靶心半径」。
- 超时阈值 800 ~ 5000 ms,步进 100。默认 2000 ms 是「明显不是在瞄准」的判断线:正常人类从看到靶到点中在 200 ~ 600 ms,2000 ms 已经不是慢而是走神了。下限设 800 是因为再低会把紧张导致的正常慢反应误判成超时,把数据搞脏。
- 命中率的分母恒等于本轮射击次数,超时那一发也留在分母里;分子是内环加环带。10 次里内环 3、环带 5、脱靶 1、超时 1,命中率是 8 ÷ 10 = 80%;如果把脱靶和超时从分母里剔掉,分母变 8,算出来是 100%,完全失真。
- 只有超时退出反应时统计,脱靶不退。理由见原理一节:脱靶是一发真实反应,只是结果没中;剔除它会让反应时偏乐观。有效样本数这一格会明确告诉你「9 / 10」以及剔除了几条。
- 键盘射击没有指针位置可用,判定基准改成「屏幕中心与靶心的距离」。这不是缺陷而是两种不同的输入设备,页面在提示里写明了;纯键盘玩家的成绩与鼠标玩家不可直接比较。
- 活动区固定 420×420 CSS px(窄屏按 100% 宽自适应),靶心取点范围留 4 px 边距,保证整个靶(含 2 px 描边)不会被裁掉。窗口尺寸变化会重排活动区,因此训练过程中不要旋转手机或调整窗口,否则正在打的那一发距离会算错。
- 不做灵敏度与 DPI 换算。你在此页的绝对 ms 值不能换算到游戏内,因为浏览器拿不到驱动层的原始输入时间。要做跨设备对比,只能固定同一台机器、同一浏览器、同一窗口尺寸。
常见问题
为什么我把离群值剔掉了,均值和中位数还差 7.4 ms?
这 7.4 ms 是正常残差,不是 bug。1.5 × IQR 规则只保证剔除「极端离群值」,并不保证剩下的数据完全对称。标准样本剔除 2402 之后,9 个值是 379 ~ 468,均值 419.4、中位数 412.0,差 7.4 ms,占均值的 1.8%。经验判据是:两者差在 5% 以内说明分布基本对称,超过 10% 才需要回头查是不是还有别的离群值没剔掉。同一组数据如果不剔除,均值 617.7 与中位数 421.0 差 196.7 ms(46.7%),那才是真正该警觉的信号。
我用键盘打出来的成绩比鼠标差很多,是不是我菜?
不是菜,是两种不同的输入路径。鼠标射击用 event.offsetX/offsetY 取点击位置,你的指尖本来就在移动,可以微调;键盘射击只能用屏幕中心做基准,你必须等靶心飘到中心附近才能按,天然多出一个等待误差。同一页里两种方式的分数不可比,训练时请固定用一种。想比的话,就把两种方式的成绩分开记录、分开看趋势,别混在一张表里。
页面刷新后我的成绩全没了,能保存吗?
不能,也不该存。本页只把数据放在当前页面的内存里,刷新即清空,导出 CSV 是唯一的落地方式。这是有意的设计:训练数据记录在浏览器里容易被当成「可以慢慢刷出好成绩」的凭据,而它本来只是你和自己比的临时基线。真的需要长期追踪,把每次导出的 CSV 存到本地,用电子表格自己加一列趋势线。
为什么靶心不是每次都出现在正中间?
那正是用对了极坐标面积取点的结果。如果每次都往中间挤,那是 r = R × U 的经典 bug —— 圆心附近密度是边缘的 2 倍,实测 100 万次取点里内半区会占 51.21% 而不是面积上的 25%。本页用 r = R × √U,实测内半区 25.81%,与面积口径吻合。你应该看到靶心在活动区里大致均匀地撒开,靠近四角也照样出现。
超时那一发为什么还留在命中率分母里?
因为「离开窗口」本身就是训练结果的一部分,它反映你在这一发上失焦了。如果从分母剔除,命中率会虚高,而反应时统计又已经把它剔除了,两个指标的口径就不一致了 —— 看起来命中率高、平均反应时又快,实际是漏掉了一段真实表现。本页的处理是:超时记 0 分、参与命中率分母、不进反应时统计,并在这个格子里明确标注「已剔除」,让你一眼看到这一发的存在。
我把靶心半径调到 10 px 就稳定 95% 命中,是不是可以更小?
不建议。下限 10 px 已经是浏览器环境的有效边界,再小测到的是设备而不是你:同一个像素位置,不同浏览器的取整方式可能差 1 px,Chrome 在 devicePixelRatio = 1.25 这类缩放下还会引入半像素偏移,这些误差在 10 px 靶上占比 10%,在 6 px 靶上就占 17%,把你的真实差异淹掉。想继续练小目标,靠的是把外径维持在 12 ~ 16 px、把内环从 6 px 缩到 5 px(保持难度连续),而不是把外径压到 6 px。
这页的反应时能跟游戏里比吗?
不能。浏览器里 performance.now() 的起点是 timeOrigin(导航开始),不含输入设备的 USB 轮询延迟、不含系统合成延迟、不含显示器刷新等待;游戏引擎测的是从输入事件进队列到帧呈现的延迟。系统性差异通常在几十毫秒量级,且各平台不同。所以本页的绝对值只在「同一台机器、同一浏览器、同一窗口尺寸、自己和自己比」这个前提下有意义。要看你在游戏里的真实延迟,得用浏览器扩展或系统级工具,那超出了本页范围。
10 次就够了吗?会不会样本太少?脱靶那一发又为什么不被剔掉?
两个问题分开答。关于样本量:10 个样本算中位数是稳的(排序位置明确),但算标准差时样本标准差与总体标准差会差得明显(分母 9 还是 10),对 1 个离群值仍然很敏感;想让标准差可参考,建议跑 3 轮取 30 个样本,CSV 里带序号列,方便横向比每一轮的进步。关于脱靶:它是一发完整体现的反应 —— 用户看到靶、反应了、也点击了,只是结果没点中。剔掉它等于只统计「打得住的人反应快」,反应时会系统性偏乐观。脱靶影响的是命中率,不是时序统计;只有超时(人根本没进入瞄准状态,比如手滑去点别处)才同时退出两个统计,并在表里标注「已剔除」。