基于 Go 语言(Go 1.25+)开发的轻量级 Windows 自动化框架,支持键盘、鼠标(含 3D/VR 游戏视角)、虚拟 Xbox 手柄的精准录制与回放,内置 Lua 自动化脚本引擎与识图关键点对齐(录制时截图取样,回放时重新定位起点/终点并把路径旋转拉伸对齐)。
| 文档 | 面向 | 内容 |
|---|---|---|
| 本 README | 所有人 | 安装、录制、回放、打包快速上手 |
doc/LUA_API.md |
开发者 | Lua 接口完整手册(参数表、返回值、示例、陷阱) |
doc/LUA_API_AI.md |
AI / 代码生成 | 紧凑接口清单(精确签名、语义、失败模式) |
doc/ARCHITECTURE.md |
维护者 | 模块架构、打包机制、缓存布局、命名规范 |
通过一键编译后,bin/ 下的每个产物都与 Core/ 中的一个模块目录一一对应:
| Core 模块 | 产物 | 说明与功能定位 |
|---|---|---|
Core/GoRunner |
GoRunner.exe | 录制 + 回放。无参数双击进入智能录制模式;带参数回放 .script 或执行 .lua。 |
Core/GoPacker |
GoPacker.exe | 打包工具。把 .lua / .script 打包为单文件 EXE,或用 -unpak 生成明文调试目录。 |
Core/GoLua |
GoLua.apppak | Lua 运行时载荷(实为可执行程序,用 .apppak 后缀区别于工具、也避免与资源包 .pak 混淆)。 |
Core/GoPacker/SingleLoader |
SingleLoader.apppak | 单文件引导器载荷。打包时被拼到产物开头,双击产物时由它解压并启动真正程序。无需也无法手工运行。 |
用户实际只需要认识
GoRunner.exe和GoPacker.exe两个工具;两个.apppak是内部载荷。 其余Core/模块(GoInput、GoVision、GoFSM、GoPak)均为库,不产出可执行文件。
- 直接双击打开
bin/GoRunner.exe,程序进入录制待机状态。 - 切换至目标游戏或应用窗口。
- 全局热键控制与音频提示(物理级扫描,全屏游戏内同样有效):
- 首次按
PageUp:开始录制(双声上扬提示音) - 再次按
PageUp:暂停 / 继续 切换(暂停双低音 / 继续单高音) - 按
Pause:识图开启 / 关闭 切换(状态就地刷新,见下节) - 短按
Home:识图开启时预设槽前进一个(长按Home则开关光标绘图框) - 按
End:识图开启时预设槽后退一个 - 按
PageDown:结束录制并自动保存(两声下降提示音,同时退出识图与绘图框)
- 首次按
- 录制产物为
.script文件,自动保存在bin/script/,以时间戳命名。
录制内容涵盖:键盘、鼠标(含 3D/VR 视角的原始相对位移)、手柄全部轴向与按键。
也可指定输出路径:
bin\GoRunner.exe record
bin\GoRunner.exe record bin\script\MyTask.script录制过程中按 Pause 即可开启识图:此后每次鼠标左键/右键按下与松开,
都会以光标为中心截取一张样本图保存下来,回放时用这张图把该关键点重新定位到
"画面实际位置"上,并对关键点之间的鼠标路径做旋转 / 拉伸对齐。
控制台状态固定在同一行同一列就地刷新,不换行、不撑长控制台:
[Vision] 识图关闭
[Vision] 识图开启-图像32*32p 槽位1/6 绘图框:关 (短按Home切槽/长按开关框)
[Vision] 识图开启-颜色64*64p 槽位5/6 绘图框:开 (短按Home切槽/长按开关框)
-
6 个预设槽(
Home短按前进一个 /End后退一个,环形切换):槽位 1 2 3 4 5 6 方式 图像 图像 图像 颜色 颜色 颜色 尺寸 32 64 128 32 64 128 ——
图像走模板匹配,颜色走颜色卷积匹配。 -
长按
Home(≥450ms)开关绘图框:一个红色 3 像素边框的空心方框挂在光标上, 尺寸实时等于当前预设槽的采样尺寸 —— 也就是"这一枪会截取多大范围"的可视化。 详见下节"绘图框"。 -
样本命名:
状态+按键+序号,序号 3 位,各组合独立计数:md-left-001.png、md-right-001.png、mu-left-001.png… -
每次点击只存一张图(尺寸 = 当前预设槽)。加速靠回放端的识别流水线, 不需要额外多存样本(详见下面"识别流水线")。
-
样本尺寸恒定:每张样本图永远是
vz×vz(32/64/128)的正方形。 光标贴到屏幕边缘时不把图裁小,而是把整块采样区域向屏幕内平移 —— 这样样本尺寸、颜色网格描述、匹配尺度全都保持一致,便于后续统一处理; 代价是此时vx/vy(光标在图内的偏移)不再是vz/2,回放侧已按记录值还原。 -
样本图不含绘图框:红框画在采样区域外侧(见下节"绘图框"), 开框/关框截出来的样本逐像素完全一致,模板不会被红框污染。
-
样本目录:与脚本同级同名的
<脚本名>.vision\(例:bin\script\A.script→bin\script\A.vision\)。 -
脚本内会写入独立标记行,方便后续处理:
{"dt":0,"op":"vision","vm":"image","vz":32,"von":true,"vsl":1} {"dt":0,"op":"vision","von":false} {"dt":0,"op":"vslot","vm":"color","vz":64,"vsl":5} -
识图状态不跨轮保留:
PageDown结束录制即退出识图,下一轮录制需重新按Pause。
识图开启后**长按 Home(约 0.45 秒)**即可让一个方框挂在光标上:
-
红色边框、线宽 3 像素,方框尺寸 = 当前预设槽的采样尺寸 + 边框宽度×2(38/70/134), 也就是实时显示"这一枪会截取多大范围";短按
Home换槽时方框会同步改变大小。 -
边框画在采样区域之外:方框的空心内圈正好等于会被截取的
size×size, 所以红色边框永远不会被截进样本图里 —— 模板保持干净,不会污染后续匹配。 -
不改变鼠标指针:窗口过程处理
WM_SETCURSOR且不调用SetCursor, 因此方框在鼠标下方移动时不会把指针重置成系统箭头(不会出现 画笔↔箭头 反复闪烁)。 -
方框始终是完整满尺寸:光标贴到屏幕边缘时,方框会像采样区域一样贴住屏幕边而不是被裁小, 因此你看到的框永远等于实际会写到磁盘的那张图的尺寸。
-
跟随鼠标移动,只在光标位置变化时才移动窗口,不做任何全屏重绘。
-
采用标准的拖动虚影窗口做法(置顶
WS_POPUP+ 用窗口区域挖空内部只留边框):性质 取值 意义 WS_EX_TRANSPARENT+WM_NCHITTEST=HTTRANSPARENT是 鼠标完全穿透,不挡游戏里的点击与拖拽 WM_SETCURSOR返回 TRUE 且不调用SetCursor是 不改动鼠标指针形状,跟随移动时不会闪成系统箭头 WS_EX_TOPMOST是 始终浮在游戏画面之上 WS_EX_TOOLWINDOW是 不出现在 Alt+Tab / 任务栏 WS_EX_NOACTIVATE是 永不抢焦点,不会把游戏切到后台 -
再次长按
Home关闭;按Pause关闭识图、按PgDn结束录制时都会自动收起并销毁, 不会在下一轮残留。 -
短按(<450ms)仍是"切换预设槽",长按才开关绘图框 —— 两者互不干扰。
支持以下几种回放方式:
直接双击将 .script 脚本拖拽到 GoRunner.exe 上,或在命令行中运行:
bin\GoRunner.exe bin\script\MyTask.script- 按
PageUp开始执行(提供充足准备时间切换至目标窗口); - 回放过程中可按
PageUp随时暂停/恢复; - 遇到意外情况可按
PageDown紧急强制停止退出。
加上 -f 参数,程序启动后直接开始执行到脚本结束,无需按键等待:
bin\GoRunner.exe -f bin\script\MyTask.script加上 -t 参数指定时间缩放速率(如 1.5 倍速加快,0.8 倍速减慢):
bin\GoRunner.exe -t 1.5 bin\script\MyTask.script脚本中带识图样本时,回放会自动启用关键点对齐。启动横幅与每段的识别结果都会打印出来 (匹配度是百分比,越大越像):
[Vision] 检测到 6 个识图关键点,5 段路径将按识别结果旋转/拉伸对齐
[Vision] 样本目录: bin\script\A.vision (匹配度阈值 85%, 区域置信度 97%, 缩放 0.90~1.10, 预取窗口 5, 快速区域 4 倍边长)
[Vision] 路径段#1 帧108~109 纯平移对齐(起点可信) ← 放弃旋转/拉伸:两端录制位置重合(同一次点击) => 纯平移
起点(1868,613)->(1974,615) [匹配度 99.9%] 终点(1868,613)->(1974,614) [匹配度 98.8%]
[Vision] 路径段#2 帧109~177 旋转/拉伸对齐
(位移比例0.448x 超出常规 0.50~2.00,但两端都是高置信度命中 => 照做)
起点(1974,613)->(1968,613) [匹配度 99.7%] 终点(1975,610)->(1975,718) [匹配度 99.9%] 旋转91.1° 缩放0.981x
...
[Vision] 识别流水线统计: 全屏标准识别 2 次,同键对复用 1 次,受限区域命中 15/26 次(置信度不足扩大范围 4 次),后台预取 16 次(命中 16),复核 33 次(通过 33 / 重新定位 0)
参数一览
| 参数 | 默认 | 作用 |
|---|---|---|
-vs |
85 |
匹配度阈值(百分比,越大越严格) |
-vconf |
97 |
受限区域结果的置信度门槛(0 或负数关闭门控,见下文) |
-vblur |
3 |
匹配前高斯模糊核(≤1 关闭),抵消动态模糊/锐化/分辨率差异 |
-vmin / -vmax |
0.9 / 1.1 |
匹配时的缩放搜索范围(游戏分辨率与录制时不同就放宽) |
-vrot |
180 |
允许的最大旋转角(度);≥180 等于不限制(整整一圈) |
-vpair |
32 |
同键对阈值(像素):按下/松开位移不超过它时视为"同一处" |
-vsettle |
30 |
点击那一刻光标若还需再挪一下(位置刚被修正),挪过去后等这么久再按下/松开(0 关闭) |
-vfast |
5 |
预取窗口:提前识别后续几个关键点(0 关闭预取) |
-vcache |
4 |
快速区域边长 = 样本边长 × 该系数 |
-vtol |
6 |
识别结果复核的位置容差(像素) |
-vdir |
自动 | 样本目录,默认按 <脚本名>.vision 推导 |
匹配度是怎么算的
回放识别用的是 ZNCC(零均值归一化互相关):先各自减掉平均亮度再算相关,
因此对整体亮度、对比度,以及动态模糊、局部锐化、分辨率差异都不敏感 ——
它比的是结构/特征,而不是逐个像素的值;结果天然落在 0~100%。
实测(2560×1440,32×32 样本,同机同屏):
| 情形 | 匹配度 |
|---|---|
| 完全相同 | 100.0% |
| 画面被 5×5 模糊(分辨率偏软) | 99.1% ~ 99.8% |
| 画面被 9×9 重度模糊 | 98.2% ~ 99.7% |
| 画面被锐化 | 99.9% |
| 无关内容(随机噪声模板) | 约 10% |
所以默认阈值 85%:真实命中(哪怕画面糊成一片)都能过,无关内容一律挡掉。
匹配度只比阈值高一点点的会在日志里标注 偏低,提示这次属于"勉强通过"。
界面很糊、样本本身纹理又弱(例如只有一个笔画的数字)时,可以把
-vblur 0关掉预模糊: 模糊会把唯一的区分特征也一起抹平。
对齐规则
- 鼠标按下/松开的每一帧都是一个关键点;关键点之间的整段路径用一个
相似变换(旋转 + 等比拉伸 + 平移)对齐,该变换恰好把
录制起点→识别起点、录制终点→识别终点,因此两端严丝合缝、段内不跳变。 - 这一轮的结束点就是下一轮的起点(相邻段在关键点处共用同一个识别结果)。
- 中途关闭过识图时关键点链在此断开:关闭期间的路径按原坐标回放, 重新开启后的起点不沿用上一轮的终点。
- 宁可不对齐,也不乱跳。识别失败或两端结果互相矛盾时自动降级:
旋转/拉伸对齐→纯平移对齐(形状不变)→按原坐标回放, 降级原因会打印在段信息后面(例如← 放弃旋转/拉伸:旋转-159.1° 超出 ±15°)。 分级细则见下表。 - 某个关键点失败只回退它自己:其它关键点匹配成功的结果继续作为参考
(日志里就是
纯平移对齐(终点可信)—— 起点没用上,终点照样对齐)。
段变换的可信度分级
| 情形 | 行为 |
|---|---|
两端都命中、旋转在 -vrot 内,且两端都是高置信度命中(≥ -vconf) |
旋转/拉伸对齐,起终点精确对齐(位移比例多离谱都照做,见下) |
两端都命中、旋转在 -vrot 内,但至少一端只是勉强命中(< -vconf) |
位移比例落在 [0.5, 2.0] 才做旋转/拉伸;越界则降级为纯平移(取匹配度更高的一端) |
两端都命中但旋转超出 -vrot |
降级为纯平移(取匹配度更高的一端) |
| 两端录制位置重合(同一次点击的按下/松开) | 相似变换无定义 => 纯平移 |
| 只有一端命中 | 该端做纯平移,路径形状不变 |
| 两端都没命中 | 按原坐标回放 |
为什么位移比例不做门限
内容每轮重排的界面(舒尔特方格每轮随机打乱)里,同一段的两端在新一轮可能落在任意两格 —— 录制时相距 300px、这一轮只相距 100px(比例 0.45x)完全正常,这不是"两端矛盾", 而是这一轮的真实布局。以前拿
[0.5, 2.0]去卡它,会把这类段降级成"纯平移(起点可信)", 也就是按录制时的旧位置回放:路径朝下一个点击目标的反方向走,到点击那一刻才跳回来。 所以现在只有"至少一端是勉强命中"时才用几何比例兜误匹配。 相似变换本身也不做缩放限幅 —— 限幅会让端点对不齐(路径少走一截再跳过去)。关于旋转与"360 度":路径旋转的完整范围确实是一整圈,但"旋转 θ"与"旋转 θ−360°"是同一个旋转, 所以用有符号角表示时值域天然就是
(−180°, +180°],两端接起来正好是 360° 全圆。 段日志按这个约定打印:+90°= 顺时针四分之一圈,-159°= 逆时针 159°(等价顺时针 201°)。 因此限制值 ≥ 180 就等于不限制(-vrot 360同样)。
界面类型 该怎么做 原因 内容每轮重排(舒尔特方格每轮随机打乱 9 个数字) 保持默认 -vrot 180同一个数字回放时本来就可能跑到另一个格子,A→B 的方位翻转 180° 都正常;限死旋转会让端点落不到识别位置,点击偏格 固定布局(元素不会重排) -vrot 15之类收小大旋转往往意味着有一端打到了"长得几乎一样"的另一个元素;限死旋转会自动放弃旋转/拉伸、退回纯平移,避免路径被横拉过去
识别流水线(加速策略)
回放时整份脚本一次性读入内存,关键点、连通关系、每帧归属全部提前聚合完成; 识别本身则由一条流水线负责:
| 环节 | 做什么 |
|---|---|
| 同键对并发扫描 | 按下/松开两张图位置几乎相同时,让它们在同一个区域里同时找:谁先命中就用谁的结果,另一张按录制位移直接换算,另一侧还在跑的扫描立刻作罢("谁先亮灯用谁的") |
| 同键对一致性 | 位移 ≤ -vpair(默认 32px)时,按下与松开必须落在同一处:后来那张只在"位移+容差"的极小区里复核,极小区没命中就按录制位移与先命中那侧对齐 —— 绝不到更大范围另找一个位置(那会把一次点击拆成一次拖拽)。32px 样本、2px 位移时这块区域只有 60px 见方 |
| 快速区域缓存 | 某个关键点识别成功后,在它周围留下边长 = 样本边长 × -vcache 的区域;后续关键点优先只在这个区域里找 |
| 区域置信度门控 | 受限区域里的最优只是局部最优:区域外可能还有更像的目标。因此区域结果必须达到 -vconf(默认 97%),达不到就逐级扩大范围重找 |
| 逐级扩大范围 | 极小区(同键对复用)→ 快速区域 → 扩大区域(-vcache × 3)→ 全屏。只有全屏是全局最优,所以全屏只要求阈值 -vs |
| 后台预取 | 开启预取窗口(默认提前 5 个关键点),用协程在回放空档里把这些关键点的大概位置先标出来(候选) |
| 采用前复核 | 真正轮到某个关键点时,用实时画面在候选附近复核一次:通过就直接采用(加速完成),不通过立刻重新定位(逐级扩大范围,必要时全屏) |
| 窗口滑动 | 滑出窗口的在途预取协程被取消,不做无用功 |
加速策略只影响"在哪找":任何一级没接住或不够可信,都会完整回到常规路径 (同键对复用 → 快速区域 → 扩大区域 → 全屏),判定标准不受加速策略影响。 全屏识别最多同时跑 2 个(避免多个预取把 CPU 抢光),抓屏串行化。
点击与路径的正确性保证(回放主线)
识图对齐会修正关键点的位置,因此主线遵守三条硬规则,保证"点到的就是识别到的位置":
- 关键点帧的坐标 = 该关键点自己最终确认的识别位置,不经过"可能还是旧值的段变换"。
- 位置一旦变化,引用它的段变换立即作废重建(含最后一个关键点之后的收尾路径), 所以不会出现"点完又跳回旧位置"。
- 停顿开始前,光标就已经放到该关键点当前已知的位置上。录制的语义是
"鼠标先到位 → 停一下 → 点击";停顿期间光标就等在正确位置上,界面看到的悬停/焦点状态
也和录制时一致,点击因此不会被算在旧位置上。若点击那一刻光标还需要再挪一下(位置刚被修正),
挪过去后先等
-vsettle(默认 30ms)再按下/松开。
常见症状 → 原因 → 处理
| 症状 | 原因 | 现在的行为 / 参数 |
|---|---|---|
| 先朝错误方向走、停在错的地方,点击那一刻横跳到正确位置 | 提前标记是在受限区域里找的,区域里有相似目标时它会挑到"最不坏"的错误位置 | 区域置信度门控:区域结果必须 ≥ -vconf,否则扩大范围,直到全屏的全局最优。可把 -vconf 调高(如 99) |
| 点击被算在旧位置上("先点了再移动") | 位置在点击瞬间才被修正,光标是"瞬移"过去的,界面还没看到它到位 | 停顿前先把光标放到已知位置;点击瞬间被修正时等 -vsettle(默认 30ms) |
| 按下与松开落在两个地方(一次点击变成拖拽) | 松开那侧跑到更大范围里另找了一个位置 | 同键对一致性:-vpair 内只在极小区复核,没命中就按录制位移与按下侧对齐 |
| 两段之间朝目标反方向走,快到了才跳到正确位置 | 该段的位移比例越界被降级为"按录制旧坐标回放"(重排布局里比例本来就可以任意) | 两端都是高置信度命中时照做旋转/拉伸;只有勉强命中才用 [0.5, 2.0] 兜底 |
| 整段被横拉/乱转 | 有一端打到了"长得几乎一样"的另一个元素 | 固定布局界面把 -vrot 收小(如 15),旋转超限即退回纯平移 |
| 明明有样本却没走加速 | 匹配度不够、区域里没有、或预取没跑起来 | GOROBOT_VISION_DEBUG=1 跑一次,见下 |
排查与调试
$env:GOROBOT_VISION_DEBUG = "1"
bin\GoRunner.exe -f bin\script\A.script
# [Vision/debug] 锚点#0 无预取候选 => 标准识别
# [Vision/debug] 锚点#0 全屏识别命中 (1974,615) 匹配度=99.9%
# [Vision/debug] 启动预取 5 个关键点 (窗口 2~6)
# [Vision/debug] 预取成功 锚点#2 -> (1975,718) 匹配度=99.9%
# [Vision/debug] 锚点#1 同键对复用命中 (1974,614) 匹配度=98.8% <- 按下/松开复用
# [Vision/debug] 锚点#3 标准识别(快速区域 (1705,647)-(1865,807))命中 (1769,711) 匹配度=100.0%
# [Vision/debug] 锚点#7 快速区域 (616,432)-(776,592) 命中但置信度不足 (96.2% < 97%) => 扩大范围
# [Vision/debug] 锚点#9 就近复核未通过 (原标记 1866,604 存储匹配度=100.0%;复核 1866,604 匹配度=88.1% 命中=true,门槛 97%) => 重新定位现场实测(2560×1440,9 组点击 = 18 个关键点):
[Vision] 识别流水线统计: 全屏标准识别 2 次,同键对复用 1 次,受限区域命中 15/26 次(置信度不足扩大范围 4 次),后台预取 16 次(命中 16),复核 33 次(通过 33 / 重新定位 0)
即:只有没有预测来源的少数关键点走了全屏搜索,其余都由快速区域 / 同键对解决, 且采用前复核全部通过(0 次重新定位)。
另外两个已修掉的隐患: ① 匹配结果矩阵的极值定位原先在 Go 侧逐像素
GetFloatAt(全屏约 300 万次 cgo 调用), 改用 OpenCV 原生MinMaxLoc; ② 预取协程并发累加统计量存在数据竞争,改用原子计数(go test -race全绿)。
只读约定(重要)
录制时按正常方式录制,不做任何特殊处理;回放时把整份 .script 一次性读入内存,
在内存里完成关键点聚合与路径调整,原脚本文件全程只读、一字节都不会被改写
(回放前后哈希与修改时间完全一致)。调整结果只作用于当次回放的内存副本,
因此脚本可以放心留档、反复回放。
GoRunner.exe 内置完备的 Lua 运行时,支持调用 Go 原生封装的高性能键鼠控制、多尺度模板匹配、颜色匹配与有限状态机:
bin\GoRunner.exe MyScript.lua| 模块 | 能力 |
|---|---|
GoInput(别名 SuKey) |
键盘/组合键、鼠标移动与点击、后台窗口点击、滚轮;虚拟 Xbox 360 手柄(双摇杆 / 双扳机 / 14 个按键);全局热键检测;系统提示音 |
GoVision(别名 SuScreen) |
窗口截图、多尺度模板匹配、颜色卷积匹配、单点取色、多点比色 |
GoFSM |
有限状态机调度,适合复杂流程自动化 |
CallGo |
旧版兼容(系统对话框等) |
完整接口手册见
doc/LUA_API.md(含全部参数、返回值、错误约定与陷阱)。 AI / 代码生成请用doc/LUA_API_AI.md。
local input = require("GoInput")
local vision = require("GoVision")
-- 视觉:截窗口并匹配模板,命中则点击中心
vision.CaptureClient("MyGame", "shot.png")
local r = vision.MatchTemplate("shot.png", AssetPath("btn_start.png"), 0.9, 1.1, 0.05)
if r and r.percent >= 85 then -- percent = 匹配度百分比 (0~100,越大越像)
input.Move(r.centerX, r.centerY)
input.Click()
end
-- 键盘
input.KeyTap("e") -- 单键
input.KeyTap("s", "ctrl") -- Ctrl+S
input.Sleep(200)
-- 热键循环控制(PgUp 开始/暂停,PgDn 结束)
while not input.CheckPgDnTrigger() do
if input.CheckPgUpTrigger() then print("切换开始/暂停") end
input.Sleep(20) -- 热键是上升沿,需高频轮询
endlocal pad = require("GoInput")
pad.Gamepad{ ly = 32767 } -- 左摇杆推满向上
pad.Gamepad{ lx = 0, ly = -32768 } -- 左摇杆推满向下
pad.Gamepad{ buttons = pad.BTN.A } -- 按下 A
pad.Gamepad{ buttons = pad.BTN.START + pad.BTN.LB } -- 组合键(掩码相加)
pad.Gamepad{ buttons = pad.BTN.A, rt = 255, rx = 20000 } -- 按键 + 扳机 + 右摇杆
pad.Gamepad(0, 32767) -- 简写:仅左摇杆
pad.GamepadReset() -- 全通道归零
pad.GamepadSlots() -- 已连接手柄占用的 XInput 槽位
pad.GamepadClose() -- 释放虚拟手柄- 表内未给出的字段保持原值,可「按住按键的同时推摇杆」;摇杆/扳机是状态不是事件,暂停或退出前务必
GamepadReset()。 pad.BTN常量(可直接相加):A B X Y/LB RB(L1 R1)/LS RS(L3 R3)/START BACK/UP DOWN LEFT RIGHT。buttons支持三种写法:数字掩码 / 名称字符串"A"/ 名称数组{"A","LB"}。
虚拟手柄基于 ViGEmBus 总线驱动。系统未安装时
build.ps1或程序会自动静默安装。 注意虚拟手柄会占用最小空闲槽位:若物理手柄已占槽位 0,虚拟手柄会落到槽位 1, 只读取槽位 0 的游戏会忽略它 —— 此时回放手柄脚本需暂时拔掉物理手柄(可用GamepadSlots()确认)。
# 打包 Lua 自动化脚本
bin\GoPacker.exe Projects\MyProject\MyProject.lua
# 打包录制回放脚本(.script)
bin\GoPacker.exe bin\script\VRView3D_2026-09-10_21-59-15.scriptGoPacker 支持两类脚本,按扩展名自动分流:
| 脚本类型 | 打包后运行方式 | 说明 |
|---|---|---|
.lua |
Lua 引擎执行 | 自动化逻辑脚本;同目录有内容的 Asset\ 会自动压成 .pak 装入 |
.script |
回放引擎重放 | 录制下来的键鼠/手柄动作数据;同样支持 -f(直接播完)与 -t(倍速) |
打包后的单文件 EXE 对
.script只认-f、-t <倍率>、-vt <匹配度百分比>(-vt就是散装命令行里的-vs),其余识图参数尚未透传。 调试期想随便调参数,用GoPacker.exe -unpak生成明文目录后直接跑GoRunner.exe。
Asset\为空目录时视为无资源,不生成.pak。.script的识图样本<脚本名>.vision\会自动内嵌进单文件 EXE(负载 V3 格式), 运行时解包到%LOCALAPPDATA%\GoRobotScript\vision_<哈希>\复用,产物始终只有一个 exe。- 输出程序名默认取脚本文件名,可用脚本内
PackageInfo("MyBot.exe", "1.0.0")指定。 - 期望「独立文件夹模式」(EXE + .pak + DLL 同目录)时,在脚本里加
-- @pack_mode folder。
bin\GoPacker.exe Projects\gamepad_left_stick_loop\gamepad_left_stick_loop.lua -unpak
bin\GoPacker.exe bin\script\VRView3D_2026-09-10_21-59-15.script -unpak产出 bin\run\<名称>\,脚本与资源保持明文、不做任何封装:
Lua 脚本: 录制脚本:
bin\run\gamepad_left_stick_loop\ bin\run\VRView3D_2026-09-10_21-59-15\
├── gamepad_left_stick_loop.exe ├── GoRunner.exe
│ ← 由 GoLua.apppak 复制改名 │ ← 由 GoRunner.exe 复制(回放引擎)
├── gamepad_left_stick_loop.lua ├── VRView3D_..._21-59-15.script
├── Asset\ ├── VRView3D_..._21-59-15.vision\
└── *.dll │ ← 识图样本,改完直接重跑
└── *.dll
(运行:GoRunner.exe <脚本>.script)
- Lua 形态:双击
<名称>.exe即可运行(无参数时自动执行同目录同名.lua,其次main.lua,再其次目录内唯一的.lua)。 - 改完脚本、资源或识图样本 直接重跑,无需重新打包 —— 这就是它存在的意义。
录制产物是 JSON Lines(每行一个动作帧),纯文本、可直接手改:
{"dt":3,"op":"init","x":1122,"y":596}
{"dt":0,"op":"vision","vm":"image","vz":32,"von":true,"vsl":1}
{"dt":1455,"op":"gp","gp_ly":2258}
{"dt":15,"op":"rmv","dx":-3,"dy":1}
{"dt":16,"op":"md","btn":"left","x":1012,"y":256,"vi":"md-left-001","vx":16,"vy":16,"vm":"image","vz":32}
{"dt":0,"op":"vision","von":false}| 字段 | 说明 |
|---|---|
dt |
距上一帧的毫秒间隔(回放按此节流,受 -t 缩放;标记行恒为 0 且不占用时间轴) |
op |
动作类型:init 起点锚定 / mv 光标绝对移动 / rmv 3D视角相对位移 / kd·ku 键按下弹起 / md·mu 鼠标按下弹起 / mw 滚轮 / gp 手柄一帧 / vision·vslot 识图状态标记 |
vi vx vy vm vz |
识图样本名 / 光标在样本图内的偏移 / 匹配方式 / 采样边长(md·mu 帧携带)。样本尺寸恒为 vz×vz |
von vsl |
识图开关 / 槽位号(vision、vslot 标记行携带) |
手柄帧 gp 的字段与 Lua 手柄接口一一对应:
gp_b→buttons、gp_lt/gp_rt→lt/rt、gp_lx/gp_ly→lx/ly、gp_rx/gp_ry→rx/ry。
完整字段表见
doc/LUA_API.md第 9 节。
本项目采用标准的 PowerShell / Bat 一键构建脚本,支持依赖库自动还原与编译:
# 在项目根目录下执行
.\build.ps1
# 或直接双击运行 build.bat构建脚本会自动将 Core/libs 中的 16 个必要运行时动态库(OpenCV / MinGW / ViGEmClient)同步还原至 bin/ 目录,并编译生成全部核心模块产物。
build.ps1只生成最新产物,不清理任何历史文件(清理由人工决定)。
Core/ 下的关键逻辑(识图对齐变换、样本命名、打包负载)带有一组回归测试。
测试二进制同样依赖 bin/ 里的 OpenCV DLL,必须先把 bin 加进 PATH,否则会报 0xc0000135:
$env:PATH = "$PWD\bin;$env:PATH"
go test ./Core/...其中涉及真实抓屏的用例在没有屏幕的环境会自动跳过。
Core/libs/base.version 定义基础模块(DLL 集合)的版本号。打包产物运行时会把 DLL 解压到:
%LOCALAPPDATA%\GoRobotScript\base\b_<版本号>_<DLL内容哈希>\
同一基线的所有包共享同一份(约 56 MB,不再逐包重复占用);升级 DLL 后把版本号改成 1.0.1 再构建,新旧基线因哈希不同而并行共存、互不冲突,旧包继续使用它自己的基线正常运行。超过 7 天未使用的缓存目录会被自动回收(可用环境变量 GOROBOT_CACHE_KEEP_DAYS 调整)。
- robotn/gohook:系统底层全局键鼠事件监听。
- go-vgo/robotgo:跨平台键鼠模拟输入。
- gopher-lua:Go 原生 Lua 虚拟机与编译器。
- GoCV:OpenCV 4.13+ 计算机视觉与模板匹配。
- ViGEmBus / ViGEmClient:虚拟 Xbox 360 手柄总线驱动与客户端。
- License:MIT
- Author:Suceru