
开篇先用一段从业者口吻引出项目背景点明“向僵尸开炮”这游戏的肝度问题以及为什么需要一个“全自动挂机助手”。这个项目我前后断断续续写了大概两周起因特别简单向僵尸开炮这游戏爽是爽但刷关卡、选技能、领奖励这套重复动作做到后期实在让人犯困。明明主线打到最后拼的是数值累积可每一局都得手动进图、手动选技能、手动点结算一晚上刷下来手指头倒是没停过脑子早就放空了。所以我就琢磨着能不能写一个全自动挂机助手把“进图—打怪—升级选技能—通关—领奖励”这条循环完全托管出去让游戏自己在后台跑我只需要偶尔瞄一眼有没有卡住。现在这套助手已经在我手机上稳定跑了两周平均每天晚上挂机三四个小时能自动刷几十关技能选择逻辑也基本符合我自己的手动习惯。这篇文章就把整个项目的设计思路、核心代码、踩坑记录完整拆一遍适合两类人看一是玩这个游戏刷腻了、想搞个挂机方案省点精力的玩家二是想拿真实项目练手、把图像识别和自动化点击这套技术栈跑通的初学者。整个过程不需要改游戏文件、不碰内存数据只靠截屏识别加模拟点击门槛不高但涉及的知识点很全。1. 项目整体设计与方案选型1.1 目标拆解挂机助手到底要解决什么问题先说清楚这游戏手动刷关的重复流程只有把这个问题拆透了才知道自动化要覆盖哪些环节。向僵尸开炮的核心玩法是一局一局的闯关战斗每局开始前你站在主界面点击“开始战斗”进入关卡角色自动攻击你只需要在升级时选择技能、偶尔捡补给打完之后进入结算界面拿到奖励再点“下一关”或“再来一局”。整个过程听起来简单但实际的操作频率很高。一局时间短的几十秒长的一两分钟每次都要手动完成“点开始—等加载—选技能—看结算—点继续”这套动作。素材刷得多了以后我发现一个更具体的问题技能选择不是乱选的前期要拿范围伤害清怪中期要把主力输出技能强化到高级后期还要兼顾生存技能这个决策本身需要观察当前波次和已有技能不是简单点同一个位置。所以这个助手要做的其实是一个闭环。我把它拆成四个子任务自动进图、自动战斗内决策、自动结算领奖、自动处理异常。前三个是主流程第四个是兜底。1.2 技术方案对比为什么选 ADB 加图像识别做一个游戏自动化工具方案其实有好几条路。第一条是找游戏的内存数据、直接修改数值这种叫内存修改效率最高但风险也最大现在主流游戏都有反作弊检测而且这种做法明显越界。第二条是用网络抓包模拟客户端请求表面上可行但游戏服务端对大多数关键操作都有校验封号概率不低调试成本也高。第三条就是我现在用的方案通过 ADB 工具控制手机截屏用图像识别分析当前界面再用模拟点击的方式操控游戏。选第三条路的理由很实在。首先它不碰游戏本身的数据游戏端感知到的只是正常的触摸操作原理上跟用人手点没有区别。其次是调试方便每一轮操作之前你都可以先看截图再决定点哪里出了错一目了然。最后是这套技术栈通用性很强以后想自动化其他游戏或者手机上的重复操作改改模板图就行。方案定下来以后整个工作量就清晰了一份运行主循环一个截图处理模块一个模板匹配识别模块外加一套技能选择策略。主循环负责流程状态流转截图模块负责从手机拿到当前画面识别模块负责判断现在处于什么界面技能选择策略负责在战斗内做出决策。1.3 整体架构状态机驱动的挂机逻辑挂机助手不是简单地写一个死循环然后一直点更稳妥的写法是把整个挂机过程设计成一个状态机。我一开始写的第一版就是不管三七二十一每两秒点一次屏幕中间结果经常在结算界面还没出来的时候就已经点了“跳过”奖励都没领到就跑到下一关了白白浪费效率。后来改成状态机驱动之后所有问题都理顺了。状态机说白了就是把游戏流程拆成几个固定状态每个状态对应一套判断逻辑和操作逻辑。我定义了七个状态主界面、关卡加载中、战斗内、升级选技弹窗、结算界面、异常弹窗、未知状态。主循环每跑一圈就截一张图先通过图像识别判断当前处于哪个状态然后执行该状态对应的操作。状态之间的跳转是唯一的不会再出现“明明在结算却去点开始战斗”这种错乱。这七个状态的判断依据分别是主界面看有没有“开始战斗”四个字关卡加载中看有没有转圈图标战斗内看有没有技能栏和僵尸选技弹窗看有没有三个技能图标结算界面看有没有“领取奖励”按钮异常弹窗看有没有“确认”或“知道了”按钮都不匹配就归于未知状态。判断顺序很关键优先级从高到低排因为有些界面的局部特征会和其他界面重叠。整个架构确定之后后面所有开发都变得很顺。识别不到就加模板状态跳转不对就调条件所有逻辑都可以在一张截图上反复验证不至于像玄学调参一样瞎试。2. 核心功能模块与识别策略详解2.1 截图与 ADB 连接整个自动化的地基整个助手跑起来的第一步是稳定拿到手机屏幕画面。我用的是 ADB安卓调试桥这是安卓官方提供的调试工具可以通过电脑向手机发送各种指令。连接方式有两种一种是手机用USB线连电脑需要打开开发者模式里的USB调试另一种是手机和电脑在同一个局域网通过无线连接命令格式是adb connect 手机IP:端口我日常用的是无线连接因为挂机时手机不用一直插着线。连接成功之后截图的命令就一句话adb exec-out screencap -p screen.png。这条命令会获取当前屏幕的原始图像数据然后重定向到电脑上的一个文件里。实测下来一张1080p的截图大概2到4兆传输时间在200毫秒左右如果觉得慢可以先把图片压缩再传但为了开发方便我只在需要保存日志时才压缩平时直接读取原始数据。这里有一个实际坑要提醒不同手机厂商对 ADB 命令的实现有一些差异特别是华为、小米这些定制系统可能会限制screencap命令或需要额外授权。遇到截图全黑或者失败的情况先检查手机屏幕是否亮着再看看是否弹出了“允许USB调试吗”的授权框这个授权框会挡住后续所有截图操作必须在开发时手动点掉。截下来的图要交给图像识别模块处理我直接用OpenCV库读进来转成灰度图做模板匹配。从截图到识别完成整个流程在普通电脑上大约耗时300毫秒也就是一秒钟能跑三轮状态判断对于这种慢节奏的游戏挂机来说完全够用。2.2 模板匹配识别游戏界面的核心手段图像识别方案里我最终选用的是最老但最可靠的模板匹配方法。原理很简单就是把一张小图作为模板在大图里滑动搜索找出匹配度最高的位置。OpenCV里对应的函数是cv2.matchTemplate配合cv2.minMaxLoc就能拿到最佳匹配位置和分数。这个方案对游戏自动化的适配性很好。游戏界面是固定渲染的同一个按钮在不同时间、不同设备上的外观基本一致不像自然场景那样光照多变。我在1920x1080和2400x1080两种分辨率下分别截了模板图用的时候按照设备分辨率自动选择对应的模板库。如果游戏更新换了UI风格只需要重新截几张图替换模板就行维护成本很低。具体到本项目的界面识别我设计了四个核心模板开始战斗按钮、技能栏图标、结算界面的领取奖励按钮、异常弹窗的确认按钮。模板匹配返回的置信度阈值设在0.8左右低于这个值就不认为匹配成功。调这个阈值有个讲究太高了容易漏识别游戏画面稍微有点动态变化就找不到太低了容易误识别把相似图案当成目标。实际调参的时候我在真实游戏画面上跑了几百轮把模板和阈值调到了在两种分辨率下都能稳定识别。2.3 技能选择策略从“每次都点同一个”到“按局势决策”战斗内最核心的决策点就是升级选技能。向僵尸开炮的技能系统是这样的战斗中打怪会积累经验升级时弹出三个技能选项玩家从中选一个。如果已选的技能再次出现在选项里选择后就能升级这个技能技能强度会提升。如果只是做“随便点一个”代码非常简单从三个选项中随机点一个就行但这样挂机效率很低。我发现这游戏的技能有很明显的优先级差异前期需要群攻技能清僵尸潮中期需要强化主输出技能后期需要至少一个控场或回复。手动玩的时候我基本上是看到群攻就拿群攻没有群攻就选已有技能升星实在没有可用的才拿新技能。为了让挂机助手也遵循这套策略我给技能做了分级。首先是群攻类我识别技能图标旁边的文字匹配到“扩散”“连环”这类关键词时技能优先级设为最高。其次是单体强化类如果在选项里出现了和已选技能相同的图标就按升级处理优先级排第二。最后是新增的辅助类技能只在前两类都没有时才选择。整个决策逻辑写在decide_skill函数里输入是三个技能的识别结果输出是点击哪个位置。实际玩下来这个策略的效果和手动操作差距不大虽然偶尔会在前几关遇到技能选择不理想的情况但整体挂机通关效率提升了大概三成。后续如果想做到更智能可以加入对当前波次和僵尸类型的识别比如Boss关优先选单体高伤普通关优先群攻这种功能也完全可以在现有框架上扩展。2.4 多分辨率适配同一套代码在不同手机上都能用刚开始开发时我只在自己的手机上测试分辨率是1080p。后来想把助手分享给朋友用发现他手机分辨率是2400x1080截图之后所有的坐标和模板都失效了点击全都点偏。这个问题的本质是模板匹配本身对分辨率敏感大图尺寸变了匹配结果的坐标也会变而且按钮的相对位置也会有细微差别。我解决这个问题的方法是写了一个分辨率适配层。所有的坐标点都不直接写死而是按比例存。比如“开始战斗”按钮在1080p屏幕上的坐标是(540, 1850)那么我先算出它相对宽高的比例是(0.5, 0.856)在2400x1080屏幕上就反推成(540, 1850)对应的实际像素公式是坐标x乘以宽度缩放比、坐标y乘以高度缩放比。模板图也按照同样的思路处理。我每种模板都存了多分辨率版本匹配时根据当前设备分辨率选择对应的模板而不是用同一个模板在缩放后的截图上匹配。虽然多存几张图会增加一点内存占用但换来的是稳定性和开发效率非常值。实测下来1080p和2400x1080两种主流分辨率都能稳定挂机其他分辨率只需要按这个模式加一套模板就行。3. 从零搭建挂机助手的完整实现3.1 开发环境准备开发环境这块很简单我用了三样东西Python 3.10、ADB工具、OpenCV库。这里不推荐什么花里胡哨的框架因为整个项目本身就是一个可以放在后台跑的服务型脚本越轻量越不容易出问题。安装依赖的时候有个坑OpenCV的默认版本是opencv-python但这个包对Python版本有要求太老的Python装不上新版建议直接用Python 3.8以上版本。装完之后先跑一句python -c import cv2; print(cv2.__version__)确认安装成功能打印版本号就说明环境没问题。ADB工具的安装看系统。Windows用户去官网下载平台工具包解压后把目录加到环境变量PATH里macOS用户直接brew install android-platform-toolsLinux用户用各自的包管理器装android-tools-adb就行。装完以后接上手机执行adb devices能看到设备编号就说明连接正常。# 安装Python依赖 pip install opencv-python numpy # 验证ADB连接能看到设备编号就正常 adb devices3.2 主循环设计每一轮都在做什么挂机助手的核心是一个run_loop循环每一轮按固定顺序执行四步截图、识别状态、执行操作、等待。截图的命令我用的是os.popen调用ADB然后把输出写到本地文件。识别状态就把这张图分别和所有模板做匹配根据匹配结果返回状态名。执行操作是根据状态调用对应的点击函数。等待这一步特别关键不是每轮结束都固定等同样时间而是根据当前状态动态调整。战斗中的状态等待时间要短一些因为技能弹窗出现后如果不快速选择会影响战斗效率。结算界面的等待时间可以稍长一点等特效动画播完再点确定。我前期写得比较粗糙所有状态统一等一秒结果经常出现点太快导致漏领奖励后来改成按状态分配时间就好了。整个主循环的写法参考下面这个伪代码实际代码里我加了日志输出每一轮把状态、识别分数、执行动作记录到一个日志文件里这样挂机挂出问题时可以复盘。def run_loop(): while not stop_flag: screenshot take_screenshot() state recognize_state(screenshot) action act_by_state(state, screenshot) delay get_delay(state) print(f[{time.time()}] state{state}, action{action}) time.sleep(delay)3.3 关键代码拆解截图、识别、点击先看截图函数。这里我用了一个小技巧不直接保存到本地再读取而是用subprocess.check_output拿到截图命令的原始字节流再在内存里用OpenCV解码这样少一次磁盘IO速度更快。import subprocess import cv2 import numpy as np def take_screenshot(): raw subprocess.check_output( [adb, exec-out, screencap, -p] ) img np.frombuffer(raw, dtypenp.uint8) return cv2.imdecode(img, cv2.IMREAD_COLOR)识别函数里我维护了一个模板列表每个模板包含名称、匹配阈值、对应的操作类型。匹配时遍历所有模板找到置信度最高的那个如果它的分数超过对应阈值就返回这个模板的名称和位置。这里注意所有匹配操作都用灰度图来算彩色图转灰度可以降低计算量而且对模板匹配的精度几乎没有影响。templates [ {name: start_battle, img: load_cv(tpl_start_battle.png), threshold: 0.80}, {name: skill_panel, img: load_cv(tpl_skill_panel.png), threshold: 0.75}, {name: reward, img: load_cv(tpl_reward.png), threshold: 0.80}, {name: confirm, img: load_cv(tpl_confirm.png), threshold: 0.80}, ] def recognize_state(screenshot): gray cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY) best_name unknown best_score 0.0 best_loc None for tpl in templates: result cv2.matchTemplate(gray, tpl[img], cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(result) if max_val tpl[threshold] and max_val best_score: best_score max_val best_name tpl[name] best_loc max_loc return best_name, best_score, best_loc点击函数的实现方式很多我用的是adb shell input tap x y命令。这个命令延迟低、稳定不需要手机端装任何App也省去了写安卓自动化框架的麻烦。点击坐标计算时要把模板匹配得到的位置换算成屏幕中心坐标一般是模板左上角坐标加上模板宽高的一半。def tap(x, y): subprocess.call([adb, shell, input, tap, str(x), str(y)])3.4 战斗内技能选择的实现细节战斗内状态识别出来后下一步就是判断当前是否出现了升级选技弹窗。弹窗的标志是屏幕下方出现三个技能图标我在这个区域设置了一个专门的检测框只在这个区域里做模板匹配可以避免把战斗界面里的普通图标误识别为技能选项。识别到技能弹窗后调用decide_skill函数获取要点的技能序号。这个函数里我用一个字典记录技能名称和它对应的优先级然后遍历三个选项计算每个选项的优先级分数选分数最高的那个。如果三个选项都是新技能且分数相同就随机点一个保证不卡住。def decide_skill(option_names): priority_score { aoe: 3, upgrade_existing: 2, new_skill: 1, unknown: 0, } best_idx 0 best_score -1 for i, name in enumerate(option_names): score priority_score.get(name, 0) if score best_score: best_score score best_idx i return best_idx技能名称的识别我用的是提取技能图标附近的文字然后用简单的OCR做关键词匹配。这个环节容易受字体渲染影响所以我做了容错处理如果三个选项里有一个图标和已选技能的图标轮廓相似度超过0.75就认为它是升级项如果没识别出任何有效信息就按“全部未知”处理点中间的选项。3.5 参数调节延迟、阈值、重试次数整个挂机助手的稳定性很大程度上取决于三个参数操作延迟、识别阈值、重试次数。操作延迟太短会导致游戏端跟不上频繁点击会被判定为异常行为太长又影响效率。我通过大量实测确定了一组比较稳妥的数值主界面操作间隔2秒战斗内决策间隔0.5秒结算界面间隔3秒。识别阈值方面开始战斗按钮的阈值设为0.80因为它在主界面非常显眼技能弹窗的阈值设为0.75因为三个选项可能有一两个会因为特效遮挡而模糊结算奖励的阈值设为0.85宁可漏检也不要误点。重试次数是用来兜底的每个操作最多尝试三次如果三次都失败就进入“未知状态”等待直到画面恢复正常。参数调节没有银弹不同手机上同样的阈值可能表现不同。我建议在开发阶段加一个调试参数--debug启动后每轮把截图、识别结果、动作都打印出来方便现场调参。等跑稳了再关掉调试模式减少日志IO。4. 实战中的常见问题与排查技巧4.1 识别不准模板一堆但匹配失败这是我前期调优时最头疼的问题。明明按钮就在那里模板也截得很准但匹配分数就是上不了0.7。后来我发现问题出在色彩模式上。手机截图默认是RGB三通道游戏界面的按钮有各种渐变、阴影直接用彩色图匹配细微的颜色差异就会把分数拉低。换成灰度图之后匹配受颜色的影响小了很多分数普遍提升了0.05到0.1。另一个原因是加载动画挡住了按钮。比如“开始战斗”按钮会在主界面加载完成前显示半透明的加载图层这时候匹配分数会被拉低。我的解决办法是给每个关键操作增加前置判断先判断当前是否在加载中。如果检测到加载图标就等半秒再重新识别。实测这个方案能把误识别率降到1%以下。如果还是匹配不上就需要重新截模板了。截模板的时候注意不要截到动态特效尽量在按钮完全静止、背景色最简单的时候截图。每一个模板图截好后我用一个小脚本在十张不同的真实游戏截图里跑一遍匹配记录最低分和最高分确保最低分仍然高于阈值。# 建议在模板图上增加mask排除按钮周边透明区域对比度影响 # OpenCV的matchTemplate支持mask参数4.2 点击无效识别到了但没反应识别到了按钮坐标也算对了但点击下去游戏没反应这种问题通常出现在坐标换算环节。最典型的情况是手机分辨率变化了比如从横屏游戏切到竖屏界面或者系统导航栏弹出导致可用区域高度变化。ADB的input tap使用的是绝对坐标一旦屏幕尺寸和开发时不一致坐标就会偏移。我在代码里做了一个动态分辨率校准。每次启动挂机助手时先读取一次当前屏幕分辨率再根据预设的基准分辨率计算缩放比例所有坐标都乘上这个比例。这个方法虽然不能解决所有问题但已经覆盖了我遇到的大部分场景。如果校准之后还是点不中就手动adb shell input tap x y逐个坐标测试找到实际生效的坐标范围。还有一种可能就是模拟点击被系统安全机制拦截了。安卓系统默认不允许第三方程序通过ADB进行点击操作但正常授权后是可以的。如果input tap没反应先看设备是否弹出的“允许模拟点击”的授权弹窗。4.3 卡在结算界面循环中断的元凶挂机过程中最怕的就是脚本停在某个界面不动了尤其是结算界面。向僵尸开炮的结算动画会持续好几秒期间屏幕上会依次出现经验条增长、奖励道具展示、掉落物品滚动等效果。如果在“领取奖励”按钮真正可点击之前点了它自然不会生效而我的代码又认为已经点过了直接跳下一轮判断于是就一直卡在结算界面。解决方案是在结算状态增加一个阶段判断。先检查是否出现了“领取奖励”按钮没有出现就继续等待出现了但不可点击时按钮的透明度会略有变化我可以再加一个子模板专门识别可点击状态的按钮。实在不行就加一个兜底逻辑如果结算界面持续超过10秒就默认点击屏幕中央的奖励区域然后重新走状态识别流程。4.4 设备相关坑锁屏、电量、后台进程被杀挂机时手机不能锁屏锁屏后截屏就是黑的而且游戏也会被系统挂起。我一开始手动把息屏时间调到永不但这样耗电快还容易让手机发热。后来我用ADB命令强制保持屏幕常亮不依赖系统设置代码里直接执行adb shell svc power stayon true就行挂机结束后再执行adb shell svc power stayon false恢复。手机发热是另一个大问题。长时间挂机、屏幕常亮、CPU高占用手机温度很容易上去而很多手机系统在温度过高时会限制游戏帧率或者杀掉后台进程导致挂机中断。解决办法是在挂机脚本里增加一个温控检测每10分钟读一次手机电池温度。如果超过42度就暂停5分钟等温度降下来再继续。这个温度值是我实测摸索出来的不同手机容限不同建议先观察自己手机的发热情况再设置。后台被杀的问题主要在模拟器上出现过实体手机上概率较低。我的解决方案是把ADB连接改为无线连接同时给手机端设置不清理后台。模拟器用户建议每次挂机前检查一下模拟器的CPU和内存占用尽量只跑一个实例。4.5 问题速查表现象可能原因解决办法匹配分数低识别不到按钮截图包含动态特效、模板有阴影环境换灰度匹配、重截模板、调低阈值点击坐标偏了手机分辨率与开发机型不一致启动时动态校准分辨率、用比例坐标点击没反应未授权模拟点击、坐标超出有效范围检查ADB授权、手动测试坐标卡在结算界面奖励按钮出现前就被点击增加阶段判断、增加10秒兜底手机锁屏导致挂机中断系统息屏时间太短使用svc power stayon保持常亮手机发热被杀进程长时间高负载运行增加温度监控超温时暂停识别到技能弹窗但选不了弹窗未完全展开、还在动画中增加300毫秒等待再重新识别模拟器里运行卡顿模拟器性能不足、多开占用高降低模拟器分辨率、关闭其他实例5. 自动化工具的边界与使用建议5.1 能做什么不该做什么这个挂机助手的价值在于把高频重复操作接管过来让你不用一直盯屏幕。它的实现原理建立在“模拟人工操作”之上不管截屏识别也好模拟点击也好游戏端看到的都是正常的触摸事件。它不修改游戏数据不读内存不注入代码从技术层面上讲它更接近一个“替你手指头干活的机器人”。但这不意味着没有风险。任何自动化工具在游戏里都属于灰色地带即便原理上是模拟人工异常的操作频率和固定的行为模式仍然可能被检测到。我自己使用的原则有两条一是只在自己的小号上测试主力账号从来不用二是控制单次挂机时长不超过4小时避免长时间不间断操作的特征。我这里不主张用这个工具做影响游戏平衡的事情。如果你是抱着“完全解放、无限刷资源、超越正常玩家进度”的目的那风险完全由自己承担。我更推荐的做法是把这类工具当做一个技术练习项目在了解游戏机制的过程中体会图像识别和自动化的实现过程。5.2 防检测与行为仿真的几点心得既然要长期挂机让行为看起来更接近真人会有帮助。我的代码里实现了几种行为仿真一是每次点击前随机等待0.5到1.5秒而不是固定间隔二是在主界面偶尔做无用滑动模拟玩家浏览三是设置一个最大连续运行时间到点后强制休息10分钟再继续。这些措施不能完全保证安全但至少让行为特征不那么“机器化”。从实测来看保持每天挂机时长不超过4小时连续挂机不超过一周我的小号没有遇到任何异常提示。如果你只是想验证技术建议用模拟器加新注册的账号这样可以完全隔离风险。5.3 后续可以怎么扩展这套架构不只能用于向僵尸开炮。换一套模板和状态定义它就能变成其他游戏的挂机助手甚至是任何安卓App的自动化测试工具。我有朋友把这套代码改成了一个自动打卡的脚本每天早上自动打开企业微信、点打卡、关掉跑了几个月都很稳。如果你想往深了研究有几个方向可以试试。第一个方向是把模板匹配换成深度学习目标检测训练一个YOLO模型来识别游戏UI元素识别准确率和泛化能力会比模板匹配高不少。第二个方向是引入强化学习让技能选择策略根据每局通关速度自动优化。第三个方向是把ADB替换成安卓原生无障碍服务做成一个不依赖电脑的纯手机端工具整个体验会完整得多。从个人经验来看模板匹配加状态机的方案更适合入门因为它直观、稳定、容易调遇到问题能快速定位。等把整个流程跑通以后再考虑上更高阶的方案也不迟。毕竟项目本身的目的不是炫耀技术而是真正解决问题。