- 柘旻成 的博客
两个「看不见的像素」:我修好了 florr.io 自动刷怪脚本的两个致命 bug
- @ 2026-9-12 18:48:00
背景
florr-auto-pathing 是一个开源的 florr.io 自动寻路/刷怪脚本(GitHub 上游地址,GPL-3.0)。它写得其实挺完整:GUI 调度、专用 Chrome 实例、小地图像素定位、A* 寻路、卡住脱困、死亡重开,一应俱全。
但我在自己机器上跑的时候,它基本没法用。两个症状看起来毫不相关,最后发现是同一个原因:florr 更新后改了两个颜色,而脚本里把这些颜色写死了。
⚠️ 提前说明:自动化操作违反 florr.io 服务条款,有封号风险,请自行判断要不要用。本文只聊代码和像素调试。
症状一:一直刷「无法检测玩家位置」
日志里无限重复这一行:
⚠️ 无法检测玩家位置,持续重试中 (第1次)
⚠️ 无法检测玩家位置,持续重试中 (第2次)
...
排查:先排掉"分辨率"这个嫌疑人
脚本定位玩家的方式是:截屏幕右上角一块固定区域,在里面找小地图上的玩家标记。它所有坐标常量都是照 1920×1080 量出来的,所以我先怀疑分辨率/缩放不对。
实测两种配置下代码算出来的截图区:
- 2560×1600 + 缩放 150% →
[2086, 30, 444, 444] - 1920×1080 + 缩放 100% →
[1600, 20, 300, 300]
两种情况下这块区域都正好框住了小地图(拿小地图模板做匹配,得分 0.966)。也就是说位置没错,但区域里就是找不到标记。
于是把这块区域的像素摊开,逐个色块打印颜色和形状:
玩家标记实际渲染颜色: #f4e476 / #f6e578 (R244 G228 B118)
代码要求的颜色: #f8de60 (R248 G222 B 96)
判定方式: ±20 容差 → B 通道允许 76~116
B 通道 118 对上限 116 —— 只差 2。
结论很清楚:标记一直都在小地图上,只是它渲染出来的颜色比脚本标定的值"淡"了 2~4 个色阶,于是整个色块被当成噪声丢掉。偶尔某一帧抗锯齿混合后 B 掉进 116 以内,就"侥幸命中"一次——所以现象是时好时坏,而不是一直失败。
改法
# utils.py
_MARKER_COLOR_TOLERANCE = 35 # 原来是写死的 20
def get_player_location_on_map(opencv_img, target_color, map, precise=False):
target_color_bgr = tuple(int(target_color[i:i+2], 16) for i in (4, 2, 0))
_tol = _MARKER_COLOR_TOLERANCE
lower_bound = np.array([max(0, c - _tol) for c in target_color_bgr])
upper_bound = np.array([min(255, c + _tol) for c in target_color_bgr])
效果(同一块区域、同一帧对比)
- 容差 ±20(原版):命中 1 个像素、0 个合格色块 → 返回 None,继续刷"无法检测"
- 容差 ±25:21 个像素、色块半径 2.55、位置稳定 → 正常
- 容差 ±35(改后):22 个像素、色块半径 2.55、位置稳定 → 正常
放宽到 35 也不会误吞小地图里其它元素:白色 #fbfaf4、深色 #201f1a、绿色花丛 #b0ec80 的 R 通道都差得很远。
症状二:死了不点「继续」,到菜单点不了「开始」
这两个其实是同一个 bug 的两面。脚本的工作流程是全自动的:
死亡 → 点「继续」→ 回到开局菜单 → 选生态区 → 点「开始」→ 重新进场
而这两个按钮的判定方式,同样是写死的颜色:
_BUTTON_GREEN_RGB = (27, 203, 37) # 采样按钮位置,算这个颜色的像素占比
# 死亡页「继续」按钮要求绿色占比 > 0.15;开局页「开始」按钮 > 0.10
我把屏幕上所有"绿色系 + 按钮形状"的色块全扫出来,实测结果是:
死亡结算页的「继续」按钮
- 代码标的点:
(1279, 939) - 实测按钮框:
(1218, 907)大小 125×69,中心(1279, 941) - 实测填充色:
#7eb35a
开局菜单的「开始」按钮
- 代码标的点:
(1412, 781) - 实测按钮框:
(1372, 739)大小 175×84,中心(1463, 780) - 实测平均色:
#7ab94e
位置全对(代码标的点甚至就落在按钮框内部),颜色全错:#7eb35a 的 R 通道是 126,而 (27,203,37) ± 25 要求 R ≤ 52。
于是三个按钮(继续 / 开始 / 以游客身份游玩)的判定同时恒为 0。链条就变成了:
死了点不了「继续」→ 死人在小地图上没有标记 → 又回到"无法检测玩家位置"的死循环里出不来。
两个看起来毫不相干的症状,其实是同一个根因。
改法:不再依赖精确 RGB,改用「色相 + 形状」
_BUTTON_HUE_RANGE = (35, 85) # OpenCV 色相
_BUTTON_MIN_SAT = 70 # 饱和度下限
_BUTTON_MIN_FILL = 0.45 # 色块占外接矩形的比例(按钮是实心的)
_BUTTON_ASPECT_RANGE = (1.2, 8.0) # 按钮是扁的
_BUTTON_MAX_DIST_REF = 120 # 色块中心离预期位置的容忍距离
_BUTTON_SCAN_REF = (760, 440, 1160, 700) # 三个按钮位置的包围盒,只扫这一块
判定逻辑是四条同时满足才认:
- 是绿色系(色相 35~85、饱和度 ≥70)—— 饱和绿、柔和绿都能过,换配色也不失效
- 形状像按钮(宽高比 1.2~8、填充率 ≥0.45、尺寸有下限)
- 离预期位置近(参照分辨率换算后 120px 以内)
- 归属唯一 —— 只接受"离本按钮比离另外两个按钮都近"的色块
第 4 条是被坑出来的:开局菜单上有一块绿离"继续"的位置只有 74px,光按"离谁近"会把两个页面搞混。所以又补了一条规则:屏幕上看得见「开始」按钮时,直接判定"这不是死亡页面",绝不误点「继续」。
另外,点击动作也改成点实际找到的色块中心(找不到才退回写死的坐标),这样以后按钮再挪几十像素也点得中。
关键一点:老的精确颜色判定原样保留在最前面当快路径,只加不删——对"按钮没变色"的环境,行为完全不变,不引入回归。
验证:不靠感觉,靠数据
① 按钮点击落点(现场盯屏记录)
18:06:25 点 CONTINUE (1280,940) → 18:06:26 回到开局菜单 → 点 START (1463,780) → 18:06:32 进场
18:07:32 点 CONTINUE (1279,941) → 18:07:33 回到菜单 → 点 START (1463,780) → 18:07:36 进场
18:08:09 点 CONTINUE (1279,941) → 18:08:10 回到菜单 → 点 START (1463,780) → 18:08:15 进场
注意「开始」的落点是 (1463,780) —— 正是新逻辑算出来的按钮真实中心(老代码写死的是 (1412,781)),这说明补丁确实在生效。死亡 → 复活 → 重开,每轮 5~8 秒,全程自动。
② 玩家标记
修好后连测 12/12 命中;连续采样位置平滑移动 (160,164) → (160,149),说明角色真的在朝目标点走,不再满地图乱跳。
③ 长时间运行
之后连续 2.5 分钟以上存活,一路从地图南部走到北部,到位后原地索敌输出。
④ 回归测试
py_compile 通过;test_utils.py + test_main_worker.py 共 77 passed;另外在一个全新 venv(照 requirements.txt 从零装依赖)里跑 187 passed。
怎么用
-
已经有源码:直接用改好的 utils.py覆盖同名文件即可,不用改别的任何文件
-
完整包(含地图模板、说明文档、一键启动脚本):链接
-
环境:Windows + Chrome;Python 3.12 或更低(依赖锁了
numpy<2,3.13 装不上);游戏窗口必须全屏;分辨率推荐 1920×1080 + 缩放 100%(实测 2560×1600 + 缩放 150% 也可以,唯一要避开的是 1920×1080 + 缩放 150%)
写在最后
这两个 bug 有个很典型的共同点:把"渲染出来的颜色"当成"稳定的常量"写进了代码。游戏一改配色,代码不会报错、不会崩溃,只会安静地失效——然后表现为看起来毫不相干的业务症状("找不到玩家"、"点了没反应")。
调试上真正管用的也就两件事:
- 把那一小块像素摊开来看——颜色、尺寸、形状、占比,全部打印出来,结论自己会浮出来
- 别急着改,先做对照——同一帧下 ±20 / ±25 / ±35 的结果并排摆着,谁对谁错就没有争议了
上游是 GPL-3.0,转发请保留 LICENSE。最后再提醒一次:自动化违反 florr.io 服务条款,有封号风险,自己判断。