瞄准训练

靶心在 300×300 的活动区里随机出现,点中它就算一次射击。判定用 Math.hypot 算圆心距与半径比较,内环环带分别计分;反应时用 performance.now() 取毫秒,超 2000 ms 判为离开窗口并从均值里剔除,但保留在命中率分母中。

点「开始训练」后靶心会出现在这里。
用鼠标点或手指按靶心,越快越准。

序号反应时(ms)距靶心(px)判定本发用时

提示:数字框里按裸 Enter 生效。靶心可用鼠标点、也可用 Tab 聚焦后按 Enter 或空格射击(键盘射击按屏幕中心与靶心的距离算,不依赖指针位置)。

使用方法

  1. 先定参数:「每轮射击次数」示例 10(允许 3 ~ 30),「靶心半径」示例 28 px,「内环半径」示例 12 px,「超时判定」示例 2000 ms。
  2. 点「开始训练」,靶心立刻出现在 300×300 的活动区里,位置用极坐标均匀取点(r = R × √U,不是 r = R × U,原因见原理一节)。
  3. 点中靶心即记一次射击,表格立刻多出一行:反应时(ms)、距靶心(px)、判定(内环 / 环带 / 脱靶 / 超时)、本发用时。
  4. 打完 10 次自动结算,结果区给出命中率、中位反应时、截尾均值与标准差四个数;中途想停就点「结束本轮」,用已打的次数结算。
  5. 想直接看统计口径的效果,点「载入示例」再点「结束本轮」,会载入一组含 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,就是为了让这个计时口径下的噪声远小于阈值。

统计口径是本页的核心,四个指标各有分工:

超时的处理方式是:记 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,形状贴近真实训练)如下:

这组数字与下方对照表、以及页面「载入示例」后点「结束本轮」的结果区是同一套代码算出来的,三处应当逐位一致。注意有效样本的截尾均值 419.4 与中位数 412.0 只差 7.4 ms(占均值 1.8%),说明剔除超时之后这组数据本身是集中一致的 —— 这才是能拿来训练的正信号。以上数值仅作演示,不是任何人的真实训练成绩。

输入范围与对照表

下表逐档列出 10 个示例反应时的统计结果,让你看清三种口径的差别。行数由「每轮射击次数」决定(3 ~ 30),表格默认渲染 12 行,点「加载更多」展开到全部行,也可导出 CSV(带 BOM)。表格本身在 JS 未执行时也有这 10 行固定示意数据,结构与真实输出一致。

序号反应时(ms)距靶心(px)判定本发用时
14128.2内环 1.0412 ms
238821.5环带 0.5388 ms
345514.7环带 0.5455 ms
440126.3环带 0.5401 ms
543031.2脱靶 0430 ms
637910.4内环 1.0379 ms
746819.8环带 0.5468 ms
82402—超时 0(已剔除)2402 ms
93976.1内环 1.0397 ms
1044524.9环带 0.5445 ms

三种统计口径的横向对照(这组是本页最该记住的表):

口径样本数本组结果适用与不适用
算术均值10617.7 ms口径最常见,但对 1 个离群值毫无抵抗力
中位数10421.0 ms典型水平首选,完全不受离群值影响
截尾均值(1.5×IQR)9419.4 ms兼顾稳健与信息量,本页默认展示
四分位距 IQR1054.5 ms中间 50% 的散布,不受两端极值影响

读表要点:算术均值与中位数差 196.7 ms,这个差值本身就是诊断信息 —— 差得越多说明组内离群值越明显,而离群值往往不是「手慢」而是「没看准」或「分神」。注意前两行的样本数是 10(全部值),后两行的有效样本是 9(仅剔除超时那一条),脱靶那一发在四行里都算进去了。

输入范围、边界与精度说明

常见问题

为什么我把离群值剔掉了,均值和中位数还差 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 里带序号列,方便横向比每一轮的进步。关于脱靶:它是一发完整体现的反应 —— 用户看到靶、反应了、也点击了,只是结果没点中。剔掉它等于只统计「打得住的人反应快」,反应时会系统性偏乐观。脱靶影响的是命中率,不是时序统计;只有超时(人根本没进入瞄准状态,比如手滑去点别处)才同时退出两个统计,并在表里标注「已剔除」。

相关工具