很多人做游戏自动化第一反应就是录一个坐标脚本把点按顺序回放一遍。这套思路在最简单的场景下确实能跑但只要游戏稍微有点变化——弹窗位置随机、怪物刷新点漂移、分辨率不一致——立刻崩给你看。我在本地挂机某款横版推图游戏时就反复栽在这种地方后来换了个思路让设备先看懂屏幕再决定怎么操作。整个链路跑通之后效果比预想的好很多也就是今天要分享的这套轻量端侧方案YOLOv8负责视觉识别AirScript负责动作执行两者配合起来针对常见游戏的通用挂机场景基本能做到换游戏只换模型和逻辑脚本。1. 先说清楚这个方案到底解决什么问题1.1 传统坐标脚本为什么扛不住老式自动化脚本的问题本质上在于它把看到什么和做什么完全割裂了。坐标脚本只关心在哪里点完全不关心屏幕上现在是什么。听起来好像无所谓实际上游戏里的状态千变万化弹窗不是每次都会出现而且出现的位置可能因为游戏版本更新发生偏移怪物的刷新点位看似固定实际上会随着角色移动、摄像机角度发生几个像素到几十个像素的偏移不同分辨率下同一按钮的相对位置完全变掉一套坐标在模拟器上能用换到真机直接失效游戏节奏一变化脚本还按照老时序执行要么空点要么把一次点击落在了错误的对象上。这类问题不只是增加维护成本而是会让整个自动化流程变成定时炸弹。我最早那版脚本一个晚上能卡死三四次每次都要手动复位挂机的意义大打折扣。后来我意识到游戏自动化真正需要的是一个视觉闭环先知道屏幕上有什么再决定下一步动作。1.2 目标检测带来的范式切换从坐标脚本升级到以目标检测为核心的方案改变的不仅仅是技术栈而是整个决策模型。原来脚本的逻辑是时间到了我就点某个坐标现在变成检测到某个目标出现我才对它执行特定操作。举个例子原来处理弹窗的方式是等两秒然后点右上角关闭。现在的方式是模型检测到屏幕上出现了弹窗关闭按钮这个对象脚本层拿到坐标后去点击它。这两者的差别在于前者是盲人摸象后者是看到什么打什么。哪怕弹窗位置变了、弹窗迟到了只要模型还能识别出这个按钮操作就不会被打断。而且目标检测输出的是目标类别 边界框结构。这个结构天然适合游戏UI识别的需求你需要的并不是识别整张画面像什么而是知道画面里每个可交互对象在哪里、是什么。YOLOv8这类单阶段检测网络的输出形式恰好就是这种而且推理开销远小于两阶段方案。1.3 适用场景与不适用场景这套方案最适合的是那些重复性高、规则固定、但画面元素位置有浮动的刷图/采集/日常场景。比如推图刷材料、重复做日常任务、挂机打怪这类怪物位置虽然会变但怪物永远是怪物弹窗按钮永远长那个样子交给视觉识别非常顺手。但不适合的场景我也得先说清楚。如果是需要快速反应的竞技对抗类游戏或者反作弊检测极其敏感的在线对战游戏这套方案就不该碰。一是延迟未必扛得住二是风险实在太大。我后面也会专门讲合规的问题但前提是这套方案请用在个人学习研究和单机/离线场景里。2. 技术栈拆解YOLOv8负责看AirScript负责做2.1 为什么选YOLOv8而不是模板匹配或更重的模型在做目标检测选型之前我其实先试过OpenCV的模板匹配。模板匹配的原理是把一张小图当模板在截图里滑动搜索最相似的区域。听起来很适合游戏按钮识别但实际用了几天就放弃了。原因很直接模板匹配对光照变化、按钮透明度、背景纹理极为敏感同一张按钮截图在该游戏的新手村识别率很高换一张地图就疯狂漏检。而且游戏UI经常有小幅动画按钮会发光、有粒子特效、半透明浮动模板匹配在这种场景下基本是灾难。后来也对比过PP-YOLO和YOLOv5。PP-YOLO精度不错但当时在端侧导出的工具链让我比较头疼尤其要转换到NCNN这类轻量推理引擎时流程比YOLOv8繁琐不少。YOLOv5和YOLOv8之间的差距其实没那么大但YOLOv8官方把导出ONNX和各个部署框架的流程做得太顺滑了ultralytics仓库一条命令就能导出极大减少了我在部署环节的折腾时间。再从端侧角度说一句YOLOv8提供n/s/m/l/x五个规格端侧设备算力差异很大nano版本在CPU上推640分辨率能跑到几十毫秒s/m版本则可以按需求往上加精度。这种梯度选择很适合先跑通再优化的思路我第一个版本直接用nano就能看到完整效果后续再根据实际帧率换大一点的模型。2.2 为什么把推理放在端侧而不是云端游戏自动化的识别环节完全可以把截图发到云端服务器做推理再把结果传回来。技术上完全可行但我从头就不打算这么干原因有三第一是延迟。挂机场景虽然不需要实时响应每一帧但截图→上传→推理→回传→执行这条链路里网络波动带来的不确定性会把整个状态机打乱。你永远不知道这次识别结果是200毫秒前还是2秒前的画面逻辑编排会变得非常别扭。第二是隐私和稳定性。截图内容可能包含账号信息、设备信息等个人数据走云服务等于把隐私交出去。而且一旦断网整个自动化立刻停摆这就违背了挂机的初衷——挂机追求的就是无人值守。第三是成本。云推理按量计费跑一晚上挂机可能比游戏内的收益还贵完全违背了轻量这个目标。端侧推理的硬件成本是一次性的软件层面用开源推理引擎一分钱不花。所以我最终把识别压倒本地整条链路完全离线运行。2.3 AirScript在整个链路中扮演什么角色AirScript这类自动化工具在整套方案里并不是视觉识别的承担者它的角色是执行器。它的职责非常集中周期性获取屏幕截图把截图提交给识别进程从识别进程读取返回的目标列表也就是类别坐标根据预设的业务规则对目标执行点击、滑动、等待等操作处理超时、异常和目标丢失等情况确保流程不会卡死在一个动作上。最关键的是把识别进程和脚本进程解耦。我的做法是让AirScript只负责截图和动作Python进程负责模型推理两者之间通过本地Socket交换数据。简单说AirScript把当前画面存成一张图推理进程监听到新图就做检测把结果打包成JSON返回脚本拿到JSON再决定点哪里。这个设计的好处是就算以后不想用AirScript了换任何一款支持截图和触摸的自动化工具脚本层的改动成本都很低。3. 数据集构建与模型训练决定方案上限的环节3.1 数据采集先打足量再谈精度目标检测模型的上限基本在标注数据阶段就确定了。训练阶段调参只是把模型推向这个上限数据质量差的话怎么调都白搭。我在采集阶段定了三条纪律第一覆盖游戏内的所有常见画面状态。刷图过程中的普通怪物、精英怪、BOSS出场、弹窗确认、网络重连提示、体力不足提示、背包已满提示这些状态都要截图。宁可多截几千张也不要漏状态因为漏一个状态模型在真实运行时就会在该状态下瞎猜。第二覆盖不同分辨率和画质设置。我的测试环境有模拟器和真机截图分辨率从1280x720到2340x1080都有。同一场景在不同分辨率下放大缩小UI元素的比例会发生明显变化。如果只用一个分辨率训练换个设备就可能漏检。第三覆盖不同亮度风格的截图。游戏里有日夜循环或者不同地图色调同一按钮在不同色调背景下的对比度差异很大。我后来直接在训练时加了亮度和对比度增强但采集时有意横跨各个地图风格仍然是最稳妥的做法。我的经验是跑通第一版最少准备1500到3000张有效截图。听起来多但实际上开几个小时的挂机流程每个状态变化都截一张很快就攒够了。关键是别在采集阶段偷懒模型在真实场景漏检一次带来的麻烦远大于采集时多花两小时。3.2 标注策略类别别贪多只标会触发逻辑的标注阶段最容易犯的错误是恨不得把画面里所有东西都标出来。我的原则很简单只标注那些脚本逻辑需要响应的对象。如果一个目标出现了但脚本对它没有任何动作那它对该项目来说就是噪声。我在某款横版推图游戏用的类别不多控制在10个以内monster普通怪物自动刷怪时的主要目标boss精英/BOSS需要特殊技能优先级treasure可拾取掉落物dialog_confirm弹窗确认按钮dialog_close弹窗关闭按钮npc任务NPC做日常任务时需要交互hero角色自身偶尔需要判断角色是否被击倒skill_ready技能就绪状态决定是否释放技能这里有个很关键的教训类别粒度不是越细越好而是越可区分越好。比如弹窗确认按钮和弹窗关闭按钮如果它们的视觉外观非常相似模型分错的概率会很高。这时候要么把它们合并成一个类别由脚本逻辑通过位置判断具体执行什么动作要么在标注时严格保证两类样本的数量平衡。我实际选择的是合并位置区分的策略效果稳定得多。3.3 训练配置与参数选择训练代码我直接用了ultralytics官方库配置文件指向自己标注的数据集。from ultralytics import YOLO model YOLO(yolov8n.pt) # 基于预训练权重微调收敛更快 model.train( datagame_ui.yaml, epochs100, imgsz640, batch8, device0, patience15, projectruns/train, namegame_det )输入分辨率imgsz建议根据你的目标大小来定。如果游戏UI里有大量小按钮、小图标640分辨率下这些小目标可能只有十几个像素模型很难学。这时候我建议测试768甚至896。代价是推理耗时增加端侧未必扛得住。所以通用做法是先在端侧实测640的帧率如果TensorRT或NCNN环境下有余量再加大分辨率提升小目标召回率。batch和epochs维持在比较保守的数值配合patience早停。我用8的batch是因为训练机器显存不大epochs设到100但通常跑六十几个epoch就早停了。训练结束后别只看最终mAP还要打开验证集的可视化结果逐张查看漏检和误检。这部分工作花不了多少时间但比盯着loss曲线管用得多。置信度阈值也需要根据实际运行场景去设。我的经验是0.25偏激进误检率高游戏里到处乱点0.45偏保守漏检率上升弹窗偶尔点不掉。最终我是根据一晚上的实测数据调出来的先跑低阈值收集误检样本再逐步往上提找到漏检和误检的平衡点通常在0.32到0.38之间。4. 模型导出与端侧部署从能识别到跑得动4.1 从PyTorch权重到ONNX格式训练出来的模型是一套PyTorch权重端侧推理引擎没法直接吃。第一步是把权重导出成ONNX格式这也是目前端侧部署最通用的中间格式。model.export(formatonnx, imgsz640, opset12, simplifyTrue)导出之后一定要做一步验证确认ONNX模型的输出和PyTorch模型的输出在数值上一致。这一步我吃过亏直接用ONNX跑结果完全不对后来发现是输入输出的张量形状没有对齐。验证方法很简单拿同一张截图分别用两种方式推理比对输出的目标框坐标偏差在一个像素以内才算正常。如果模型里有自定义操作导出失败优先检查是不是用了不常见的后处理层。YOLOv8n这种纯卷积结构导出一般不会遇到什么障碍后处理在推理端重新实现更保险。4.2 推理引擎怎么选端侧推理引擎我实际对比过ONNXRuntime和NCNN。如果你的设备是带GPU的x86小主机直接用ONNXRuntime的CUDA后端最简单一条pip命令装完加载ONNX就能跑。瓶颈主要在CPU和内存选nano或small模型通常都能在几十毫秒内完成一帧推理。如果目标是安卓设备或者ARM开发板NCNN是更好的选择。NCNN在ARM平台上有大量针对性的算子优化内存占用比ONNXRuntime低不少。转换流程是ONNX到NCNN的专用转换工具转换后得到.bin和.param两个文件推理时加载这两个文件即可。我的实际选择是开发调试阶段用ONNXRuntime部署到测试设备上时用NCNN。这意味着同一套YOLOv8模型要维护两条推理链路。为了不让工作量翻倍我在推理封装层做了统一的接口对外只看返回的目标列表底层用哪个引擎完全不影响脚本层。4.3 帧率和耗时的取舍逻辑端侧部署最容易掉进的坑是什么都想实时 。实际挂机场景根本不需要每帧检测一次我最终把检测频率限定在每300到500毫秒一次。理由很简单游戏界面的状态变化不可能快到几十毫秒一次一次点击动作从出发到画面反馈需要几百毫秒识别太频繁只会浪费算力并且让脚本因为截图状态不一致而误判。性能数据我实测下来大概是这样的部署环境模型规格输入分辨率单帧推理耗时x86小主机 CPUYOLOv8n64020-35msx86小主机 GPUYOLOv8s64010-15ms安卓中端机 CPUYOLOv8n41660-100ms如果用安卓设备输入分辨率可以降到416或者512换取更快的推理速度。小目标确实会损失一些识别率但配合检测两次确认再操作的容错逻辑整体挂机稳定性不会受影响。4.4 工程部署的两个加分项第一个加分项是把截图预处理做成队列。AirScript截图频率和推理进程的处理速度不一定完全同步如果每截一张图就阻塞等待推理完成整体效率会大大降低。我在中间加了一个容量为3的队列脚本只管往队列里塞图推理进程按自己的节奏消费这样两边都不互相拖后腿。第二个加分项是启用模型的动态输入分辨率。YOLOv8的ONNX导出可以允许动态宽高虽然推理速度会略慢但适配不同分辨率截图时不用重新导出模型。如果只在固定的几台设备上跑静态分辨率就够用了省下的那点推理延迟对挂机场景没有本质影响。5. AirScript脚本层把识别结果变成游戏操作5.1 脚本的整体运行框架AirScript在这一层要解决的核心问题是拿到识别结果之后怎么干活。我设计的脚本框架是一个大循环每一轮循环执行四个步骤截图、提交识别、读取结果、执行动作。整个逻辑用伪码表达大概是这样的while true: screenshot_to_local() submit_to_infer_queue() result fetch_result() if result is empty: usleep(200) continue if result has boss: tap(boss.center) cast_skill() elif result has treasure: tap(treasure.center) elif result has dialog_confirm: tap(confirm.center) elif result has npc: tap(npc.center) else: perform_default_battle() end注意识别结果为空这个分支必须处理。模型偶尔漏检是正常的脚本要做的不是强行执行动作而是等待下一轮识别。如果连续多次结果为空就需要触发全局状态检查比如返回主界面重新进入副本。5.2 坐标映射模型坐标系和屏幕坐标系必须对齐这是整个脚本层最容易出错、也最容易忽视的环节。YOLOv8输出的边界框坐标基准是送入推理引擎的图像分辨率而游戏屏幕的分辨率往往和推理图不一样。假设你截图是2340x1080推理时压缩到640x640那么模型输出的坐标必须经过缩放换算才能对应到真实屏幕。换算逻辑不复杂但必须在代码里写对input_width, input_height 640, 640 screen_width, screen_height 2340, 1080 scale_x screen_width / input_width scale_y screen_height / input_height center_x ((x1 x2) / 2) * scale_x center_y ((y1 y2) / 2) * scale_y这里还有一个隐藏细节YOLO类模型输入如果是方形640x640而截图不是正方形预处理时会有letterbox补边或者拉伸变形。如果做letterbox那么推理图上的坐标和原始截图的对应关系还会多一个偏移量换算。我直接选择了拉伸到640x640虽然目标会有轻微形变但模型训练时也加了形变增强实测影响可以忽略坐标换算反而简单很多。5.3 状态机与容错让脚本不会一步错步步错游戏自动化的核心稳定性策略是让单次识别结果不能直接决定单次动作。我给脚本层加了三个防线第一是确认机制。同一个目标必须连续两轮识别都出现且置信度超过阈值才执行点击动作。这个机制可以过滤掉模型的偶发误检代价是最多多等几百毫秒对挂机场景完全可接受。第二是点击冷却。每次执行点击后无论点中什么都强制设置1到2秒的冷却时间。游戏里很多弹窗动画是渐入的按钮真正可点击的时机比识别结果出现晚如果没有冷却脚本可能在按钮刚出现时就疯狂连点反而触发游戏的反连点保护。第三是全局超时。如果当前场景超过设定时间仍然没有出现任何可操作目标脚本不能无限等下去必须执行复位逻辑——比如退回主界面、重新进图。这一步是为了处理那些模型完全没见过的异常状态。另外我强烈建议给脚本打日志并输出到本地文件。日志内容至少包括每轮识别的目标列表、点击的坐标、置信度、该轮循环耗时。没日志的时候挂机出问题只能靠猜有日志之后基本看一眼最后几十行就能定位问题在哪。6. 实测效果、换机适配与踩坑记录6.1 一个通宵的实测数据整套方案搭好之后我在一台x86小主机上挂机某款横版推图游戏跑了一个通宵统计了一下关键指标指标实测结果连续挂机时长8小时识别准确率抽查200帧96.5%漏检导致的流程卡死2次误检导致的错误点击1次模型单帧推理耗时约32ms最终稳定通关副本显著优于纯坐标脚本对比我之前的坐标脚本方案一晚上卡死三四次已经是常态换成视觉识别方案之后卡死率大幅下降而且卡死原因基本都能通过日志定位到具体的识别失败场景属于可修复问题而非随机故障。6.2 踩过的四个典型坑第一个坑是模型在模拟器和真机上的识别率差异很大。模拟器的按钮渲染偏锐利真机上有系统级的色彩增强和对比度调整同样的截图送进模型置信度能差十几个点。解决方式是采集数据时模拟器和真机截图都要有且在训练时把饱和度、对比度、亮度的增强参数调大。第二个坑是游戏更新后UI变化导致模型大面积失效。某次小更新把弹窗按钮的圆角改成了方形边框颜色也换了模型识别的置信度直接掉到阈值以下。这种问题没有一个模型能预知只能靠定期回归测试。我的做法是每周抽几张新截图跑一次推理发现识别率明显下降就补标训练。第三个坑是脚本点击过快导致游戏画面卡死。一开始没有加点击冷却脚本对弹窗按钮疯狂重试服务器端响应不过来客户端直接进入白屏状态。加冷却和确认机制之后此问题基本消失。第四个坑是模型的类别混淆。像dialog_confirm和dialog_close如果外观相近模型经常搞反。我的办法是合并类别只标一个dialog_button在脚本层通过按钮在屏幕上的相对位置决定执行确认还是关闭逻辑反而更稳。6.3 换游戏时怎么低成本迁移这类方案最大的优势就是迁移成本低。换一个游戏时要做的事其实就三件重新采集合适的数据集并训练模型修改脚本层的业务规则比如各种目标对应什么动作重新测试各状态下的识别置信度和容错逻辑。模型结构、推理引擎、坐标映射、状态机框架完全不用变。我甚至做了一个简单的规则配置文件把类别→操作动作→优先级→冷却时间全部外置成JSON。换游戏时只需要修改这个配置文件和重新训练模型AirScript脚本主体一行都不用动。6.4 关于使用边界的最后提醒这套方案的初衷是解决刷图、日常任务这类重复性操作把它当成学习目标检测部署和自动化编排的技术项目。实际使用中请务必遵守对应游戏的服务条款只把自动化用在合规范围允许的场景里比如单机游戏、个人学习研究或者离线调试环境。我更推荐大家把这套方案的注意力放在技术链路上——数据采集、模型训练、端侧部署、脚本编排这些能力迁移到其他图像识别项目里一样好用。我把自己在完整链路里踩过的坑都写出来了照着走能省下不少弯路但真正跑通之后还是会有你自己专属的坑要填。那就这样动手试吧。