ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python+ADB+OpenCV实战:安卓游戏自动化挂机方案全解析

Python+ADB+OpenCV实战:安卓游戏自动化挂机方案全解析 最近把阴阳师的日常任务自动化跑了起来用 Python 加 ADB 加图像识别搭了一套“全自动任务助手”连续挂机三周每天稳定运行六到八小时基本不需要人工介入。这套方案的核心就是让电脑替我在手机上重复“点击-识别-点击”这套循环把刷御魂、探索、结界这些体力搬运工作全部接管。写这篇东西的目的是把整个设计思路、技术选型、踩过的坑和最终的实测数据都交代清楚给同样想解放双手的朋友一条可以照着做的路线也顺便聊聊那些常规教程里不会写的细节。先说明一点这套东西本质上是安卓端的自动化操作框架并不针对游戏本身做任何修改也不涉及破解、注入之类的灰色操作。它的原理就是一个机器人按设定的流程来操作手机界面所以只要是想把手动点击、重复刷本这类机械劳动交给程序的人都可以参考这套思路。1. 这个助手到底做了什么需求拆解与方案选型1.1 我需要它做什么阴阳师这个游戏的日常任务有个特点量大、重复、固定。每天上线清体力、刷御魂、打探索、领奖励操作节奏几乎不变。手动刷两个小时无非就是“开始挑战-等待结算-再次挑战”这三板斧。体力多的时候甚至要连续刷几十把整个人跟个没有感情的点击机器一样。这个需求拆到最底层其实只有几个核心诉求识别当前界面状态是在主界面、战斗结算界面、还是体力不足弹窗。按目标去完成指定操作点击开始挑战、点击结算、点击接受邀请。循环执行直到条件不满足体力耗尽、奖励领完、任务完成。长期无人值守时能自恢复掉线重连、弹窗处理、异常重启。这套东西如果靠按键精灵这类模拟器内置工具也能做但我当时评估下来有个很现实的问题绑定分辨率、依赖模拟器环境、脚本语言比较封闭、跟外部系统的联动能力弱。我要的是一个跨设备、可以自己控制全部逻辑、识别能力跟得上界面变化的方案于是技术路线就落在了 ADB 控制加 OpenCV 图像识别加 Python 调度这三件套上。1.2 为什么是 ADB OpenCV而不是别的方案可能有人会问Appium 不也能做自动化吗Playwright 不是也有移动端方案为什么不用这些现成框架这个问题我有过实际对比。Appium 的优势在于可以读取控件树直接按 resource-id 定位元素逻辑写起来就像写 Web 自动化一样干净。但它的前提是 App 暴露了可访问性接口游戏这种用引擎自绘画面的应用控件树基本是空的你用 UIAutomator 去 dump 出来全是同一个 View根本无法通过 id 定位。即使强行加一个图像识别层框架本身也变得更重。Playwright 的移动端思路类似核心还是依赖控件树和 WebView 上下文对原生游戏界面的覆盖能力同样有限。所以结论很直接游戏自动化本质就是图像自动化绕不开截图、识别、点击这三步。ADB 提供最底层的截图和坐标点击能力OpenCV 负责在截图里找到目标图案的坐标Python 把这两件事串成业务流程。这套组合的好处是不依赖模拟器品牌安卓手机和模拟器通用。游戏更新导致 UI 小改换模板图就行不用改代码。所有操作都在设备外部完成对设备无侵入。可以随时扩展进 OCR、状态机、统计报表这些能力。后面各节的细节都围绕这三件套展开。2. 环境准备与设备接入2.1 电脑端需要装什么我用的主力环境是 Windows 11Python 3.10。虽然这套方案在 macOS 和 Linux 上同样可以跑但 Windows 在模拟器和手机驱动方面最省心新手推荐先在这个平台上跑通。需要安装的东西如下组件用途说明Python 3.10主调度逻辑建议直接装 Anaconda后续依赖管理省心ADB Platform Tools设备通信Google 官方提供Windows 版解压即用OpenCV-Python图像识别pip 安装 opencv-contrib-python 更完整PyAutoGUI桌面辅助可选只做二次保障核心还是用 ADBTesseract-OCR文本识别可选用于识别数字、文字状态Pillow图像处理截图裁剪、色彩转换的常用库ADB 安装之后记得把 platform-tools 的路径加到系统 PATH 环境变量里否则后续每次调用 adb 都要写完整路径。Python 依赖直接用 pip 装pip install opencv-contrib-python pillow numpy pip install pytesseractOpenCV 装起来比较重但值得后面模板匹配全靠它。Tesseract 是独立安装包不是纯 Python 库装完还要在代码里指定它的执行路径。2.2 ADB 连接手机或模拟器手机端需要开启开发者选项里的 USB 调试。连接方式有两种一种是用数据线直连稳定且延迟低适合长时间挂机另一种是无线 ADB适合已经放在角落的旧手机不用一直拖着线。有线连接后执行以下命令验证adb devices看到设备编号加 device 状态就说明连接成功。如果显示 unauthorized需要在手机弹窗上点一下允许调试。建议勾选“始终允许来自此计算机的调试”否则每次重启都要重新授权。模拟器连接更简单。我这里以 MuMu 模拟器为例模拟器设置中开启 ADB 调试后通过它自带的 adb 端口连接adb connect 127.0.0.1:7555不同模拟器的端口不一样夜神是 62001雷电是 5554MuMu 12 是 16384。可以在模拟器设置里查到。连接成功后同样用 adb devices 确认。2.3 无线 ADB 与多设备管理手机和电脑不在同一个局域网时可以先把手机用数据线连接一次然后执行adb tcpip 5555 adb connect 手机IP:5555之后拔掉数据线也能保持连接。要注意的是手机休眠时 Wi-Fi 可能断连建议在系统设置里把 Wi-Fi 调整为不休眠或者插着充电器跑。设备多了以后每台设备都要加 -s 参数指定设备adb -s 127.0.0.1:7555 shell screencap -p /sdcard/screen.png adb -s 设备序列号 shell input swipe x1 y1 x2 y2 500这一步在写多开脚本时非常重要。我之前就是忘了加设备序列号导致两台模拟器同时往同一个屏幕上点击结果一个跑了御魂一个跑了探索乱成一团。3. 让脚本“看见”界面图像识别核心细节3.1 OpenCV 模板匹配的原理与阈值模板匹配是这套方案识别界面的基础手段。原理非常简单先截一张游戏界面的图把这张大图作为搜索范围然后拿一张预先截好的小图也就是模板图片在大图里滑动比对找到最相似的位置。OpenCV 里最常用的是 TM_CCOEFF_NORMED也就是归一化相关系数匹配输出值越接近 1说明匹配度越高。代码写起来就这几行import cv2 import numpy as np def find_template(screen, template, threshold0.8): result cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc cv2.minMaxLoc(result) if max_val threshold: h, w template.shape[:2] cx max_loc[0] w // 2 cy max_loc[1] h // 2 return cx, cy, max_val return Nonethreshold 的选择非常重要。设太高比如 0.95模板图稍有模糊或背景变化就匹配不上设太低比如 0.6就会把图案相似的按钮误识别。我在实际项目里采用分场景阈值状态图标类模板用 0.8按钮类模板用 0.85带文字的界面基准点用 0.9。这个值不是一次性定死的跑一段时间后根据日志里的匹配分数再微调。另一个关键点是模板图的截取。模板图必须和目标界面完全同尺寸分辨率不同则匹配不上。比如手机是 1080p模拟器是 1280x720同一张按钮小图在两台设备上需要分别截取。这个问题我会在后面的多分辨率适配一节细说。3.2 用模板匹配实现“找按钮”的基本流程实际操作中我的识别流程是这样跑的先用 ADB 截取屏幕把截图拉到本地然后依次尝试多个模板找到当前界面匹配的元素再根据元素决定点击还是等待。import subprocess import os import cv2 def capture_screen(device_id, output_pathscreen.png): subprocess.run( [adb, -s, device_id, shell, screencap, -p, /sdcard/screen.png], checkTrue ) subprocess.run( [adb, -s, device_id, pull, /sdcard/screen.png, output_path], checkTrue ) return cv2.imread(output_path) def tap(device_id, x, y): subprocess.run( [adb, -s, device_id, shell, input, tap, str(x), str(y)], checkTrue )这里有个小问题需要处理screencap 截出来的 PNG 是 RGBA 格式直接用 cv2.imread 读出来颜色通道顺序是 BGR做模板匹配时如果模板图和屏幕图的通道顺序不一致匹配效果会打折扣。所以我在加载模板时也统一用 cv2.imread保证两边格式一致。截图流程还有一种更快的做法不把截图拉到电脑上直接在设备端做识别。但设备端运行 OpenCV 需要额外的环境配置复杂度高不少电脑端处理完全够用传输一张 1080p 截图也就几十毫秒。3.3 OCR 用于数字与文本识别模板匹配适合找图形按钮但对文本和数字很无力。举个例子判断还剩多少体力、扫荡次数是否跑完、或者某个弹窗标题写的是什么模板匹配就没有用武之地了。这时候我引入了 Tesseract OCR。原理是把截图中的指定区域裁剪出来做灰度化、二值化、放大然后交给 OCR 引擎识别成文字或数字。from PIL import Image import pytesseract def ocr_number(screen, region): x, y, w, h region crop Image.fromarray(screen[y:yh, x:xw]) crop crop.convert(L) crop crop.resize((crop.width * 3, crop.height * 3), Image.LANCZOS) text pytesseract.image_to_string(crop, config--psm 7 -c tessedit_char_whitelist0123456789) return text.strip()这里有几个参数很关键。psm 7 表示把图片当成单行文本处理对单个数字串识别率很高tessedit_char_whitelist 限定了只识别数字避免把体力数字识别成字母。3 倍放大是为了让小字号数字在 OCR 时更清楚。OCR 的识别率在游戏场景中大概能到 95% 以上但不会 100% 准确。所以我在读取体力剩余量时会加一个数字解析函数剔除异常值和超范围判断。比如识别出 1245 这种说明体力还很充足识别出 0 或者 OCR 返回空才触发体力不足的逻辑。宁可多等一下重新截图识别也不要随便判定。3.4 多分辨率适配方案不同设备的分辨率是这套方案最大的变量。模板匹配失败八成是分辨率不匹配而不是模板不匹配。我实测下来屏幕截图是一张 1080x1920 的图模板却是从 1280x720 的图上截的匹配分数会直接跌到 0.1 以下。多分辨率适配有两条路线把模板做成多套代码里按设备分辨率自动选择。把屏幕截图统一缩放到一个基准分辨率比如 1080x1920再做匹配。第一条路线最准确但维护成本高每台新设备都要重新截模板。第二条路线省事代价是缩放后模板边缘的模糊会导致匹配分数略降。我实际采用的是混合方案系统维护一个设备配置表里面记录每台设备的分辨率和缩放比例。截到屏幕图后先判断设备分辨率如果和基准不同就缩放到基准再接下去跑。模板只维护一套 1080p 的图新增设备时只改配置不重截模板。缩放代码看起来是这样def resize_screen(screen, target_width1080): h, w screen.shape[:2] scale target_width / w if scale 1.0: return screen, 1.0 new_w target_width new_h int(h * scale) resized cv2.resize(screen, (new_w, new_h)) return resized, scale注意一点点击的时候要把缩放后的坐标换算回原分辨率。缩放比例 scale 必须保留点击公式是 int(识别坐标 / scale)。这个坑我踩过识别点坐标没反向换算结果在大屏手机上点的位置偏了一百多像素。4. 任务状态机与调度设计从一次点击到一套流程4.1 一次任务循环的正确写法游戏自动化的核心不是“能识别界面”而是“知道当前在哪个界面、下一步应该做什么”。这种逻辑如果写成 if-else 脚本很快会在复杂分支里崩溃。用状态机来建模整个任务的生命周期就会清晰很多。以刷御魂为例我把状态拆成六种ST_MAIN游戏主界面准备进入副本。ST_TEAM队伍选择界面确认开始挑战。ST_BATTLE_LOADING加载中等待进入战斗。ST_BATTLE战斗中等待结算。ST_RESULT结算界面点击继续。ST_ERROR异常状态需要排查或重启。每次循环先截屏识别当前状态然后执行对应状态的动作def run_fuben(screen): state detect_state(screen) if state MAIN: return tap_button(挑战) elif state TEAM: return tap_button(开始) elif state LOADING: return wait(5) elif state BATTLE: return wait(10) elif state RESULT: return tap_button(结算) else: return handle_error(screen)状态检测和上一节讲的目标模板匹配是同一个东西。每个状态对应一个或多个判定模板只要有一个模板匹配分数超过阈值就认为处于该状态。我维护了一个状态模板配置表按梯次匹配先匹配界面基准图标再匹配浮动按钮这样能避免多个状态同时匹配。状态机最大的好处是逻辑可以扩展。活动更新后新增一个“活动副本”状态只需加一个状态枚举和对应的处理函数不需要动其他代码。4.2 任务队列、失败重试与异常恢复实际运行不会永远一帆风顺。网络波动导致断线、体力不足弹窗、系统维护通知、甚至手机弹了个输入法遮住按钮这些都会让状态机陷入陌生画面。我的处理思路是任何无法识别的画面都视为异常进入异常处理组件而不是原地等待。异常处理组件做的事包括多次截屏并重试识别连续 3 次都失败才认定异常。收集当前截图存档方便事后排查。尝试返回键走回主界面。触发全局重启流程。这里要特别强调重试机制。游戏偶尔会有半秒一闪而过的加载动画截图截到那一帧自然识别不了任何模板。直接判定异常会误伤。我用的是“3 次宽松重试 单次等待”策略第一次识别失败等 1 秒再截第二次失败等 2 秒第三次还失败才真正走异常流程。三次等待时间不同避免固定节奏让某些固定加载画面反复卡住。全局重启流程是最后一道防线。连续识别失败达到一定次数脚本会执行“拉回主界面”的操作先按 Home 键再重新打开游戏等加载完成后回到主界面继续任务队列。4.3 体力管理、定时调度与通知推送体力管理是长挂的核心问题。体力刷完了还在重复点挑战会被游戏弹窗拦住体力满着不刷又浪费自然回复。我用 OCR 读取当前体力数字并设置在阈值逻辑来决定是否继续刷。最简单的策略是这样的开刷前读一次体力如果体力小于 30转为刷探索这样的低体力任务或者直接结束本次挂机。刷完一轮后再读一次动态决定下一轮任务类别。定时调度的实现也不复杂。用 Python 的 schedule 库或者 APScheduler定时触发每日任务的几个关键时间点比如早上清一次日常、中午刷新后刷一次御魂、晚上再集中清一次体力。配上企业微信机器人或 Server酱推送手机挂完时能收到通知。我当时的通知逻辑是任务结束后把当天刷了多少把、消耗了多少体力、有没有报错汇总成一条文本消息推送到微信。这样白天上班完全不用看屏幕晚上回来扫一眼消息就知道一天的战果。5. 长时间稳定运行的关键防呆与自愈5.1 随机化、拟人化与点击偏移长时间跑同一套点击动作如果不做随机化处理会带来两个问题一是动作规律太明显容易被识别二是某些固定位置的点击坐标可能落在 UI 动画的边缘偶尔点击无效。随机化我做了三个层面点击坐标偏移。在识别出的中心点上随机加减 2 到 5 像素让每次点击位置不完全一致。等待时间抖动。两个动作之间随机等待 1 到 3 秒避免固定间隔。点击路径变化。有时用 tap有时用长按有时先滑动再点击模拟不同操作习惯。坐标偏移的代码很简单import random def tap_with_random(device_id, x, y, offset4): dx random.randint(-offset, offset) dy random.randint(-offset, offset) tap(device_id, x dx, y dy)这里要注意偏移量不能太大否则会点到相邻按钮。4 到 6 像素是比较安全的范围在 1080p 分辨率下大概对应一个按钮面积的 2%不影响点击命中。拟人化的另一个细节是滑动操作。有些游戏界面需要左右滑动才能选中目标模拟器或手机如果用 input swipe 100 200 300 200 100滑动轨迹是一条直线太机械。我在滑动路径里加了一些中间点让轨迹呈轻微曲线看起来更接近真人手指滑动。5.2 看门狗与自愈机制连续跑几小时后ADB 偶尔会假死截图拉不下来指令执行没响应。这时候单纯在业务流程里重试已经不够需要上更大尺度的看门狗。我的看门狗分两层第一层是进程级看门狗监控 ADB server 状态。定期执行一次 adb devices如果返回异常就杀掉 adb 进程重新启动adb kill-server adb start-server第二层是任务级看门狗记录每次循环的完成时间。如果连续 5 分钟没有完成任何一次有效循环就判定流程卡死强制重启游戏。这里判断“有效循环”不能只数点击次数要判定为进入过结算界面才算完整跑通一次。日志系统是看门狗的关键配套。每轮循环我都在本地记录以下信息时间戳、设备号、当前识别状态。各模板匹配分数最高的三个候选及分数。截图文件名列表异常时保留现场。跑一段时间后翻日志能发现很多奇怪现象某个按钮在满体力时不出现、某个活动页每隔五分钟弹一次广告、结算动画比平时多了一帧。这些规律反过来又指导状态机优化。我最初没有日志系统脚本卡了就瞎猜原因。加了结构化日志之后定位问题的速度提升了十倍以上。强烈建议不要省这一步。6. 实测结果、常见问题与避坑记录6.1 常见问题排查速查表下面这些是我实际调试过程中遇到的典型问题整理成速查表照方抓药即可。现象可能原因处理方式点击没有反应设备分辨率与模板不匹配导致识别坐标偏移检查缩放比例按比例反向换算点击坐标识别成功率降低游戏界面更新按钮纹理变了重新截取模板图更新模板库ADB 连接掉线USB 松动或手机休眠断连改用无线 ADBWi-Fi 设为不休眠脚本长时间不动状态机进入未知状态查看日志和现场截图补状态模板OCR 读取体力异常背景色干扰或字太小裁剪更精准区域增大放大倍数同时跑多个设备时操作错乱命令行缺设备序列号所有 adb 指令加 -s 参数指定设备截图拉取失败设备端空间不足或 ADB 假死adb kill-server 后重启 ADB识别率下降是运营期最容易遇到的问题。游戏每次大版本更新按钮样式都会微调模板识别分数会从 0.9 掉到 0.7 以下。我的处理习惯是更新后先手动截几张图跑一遍模板库的自动评测脚本把每家低于阈值的模板揪出来重新截。6.2 实测数据与后续扩展跑通这套系统的第一周我记录了比较完整的运行数据。以 1080p 分辨率的旧手机为例连续运行 6 小时平均每轮副本循环耗时约 45 秒完整任务循环约 480 轮异常中断 2 次均能在 1 分钟内自动恢复极少需要人工介入。整体识别成功率在 96% 左右失败的 4% 主要集中在新活动界面的首次接触。后面随着模板库补齐成功率逐步上升到 98% 以上。这套方案还可以继续扩展的方向有很多。比如加入异常数据的机器学习分类根据截图特征自动判断是网络问题还是活动弹窗比如把任务流程配置化不写死代码通过一个 JSON 文件描述每日任务顺序再比如加入多线程调度让一台电脑同时控制多台设备跑不同任务真正把挂机从“一台设备”升级成“一个挂机农场”。我在后续实际使用中逐渐发现ADB 加图像识别这套组合的想象力远不止游戏这一块任何需要 GUI 自动化操作的场景比如旧版软件批量处理、无人值守的测试任务都可以用同样的思路搭建。这套方案我自己已经连续跑了三个多月现在最深的体会是自动化不是因为人手不够才需要而是因为人应该把时间花在更有价值的事情上。
返回列表