ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

无官方API训练皇室战争AI:基于YOLO与ADB的完整方案

无官方API训练皇室战争AI:基于YOLO与ADB的完整方案 当你想给皇室战争这类实时对战游戏训练一个 AI 时第一反应往往是“先找官方 API”。但现实是Supercell 提供的公开 API 主要面向玩家数据查询、部落信息、对战记录并不会给你实时对战画面、卡牌状态或者操作接口。也就是说官方 API 拿不到“你正在打的那一局”的完整游戏状态更不可能帮你直接出牌。那么问题来了没有官方 API还能不能训练一个属于自己的皇室战争 AI答案是能。本文就是一套完整可行的实操方案。我会围绕“无 API 条件下的游戏 AI 训练”展开从整体架构、数据采集、目标检测模型训练、自动化操作到常见踩坑一步步给你可落地的代码和配置。文章适合有一定 Python 基础、想拿游戏场景练手 AI 的开发者阅读。先说清楚本文的边界所有内容只用于本地学习、AI 算法研究和封闭测试环境验证不建议、也不支持在正式对战中使用任何自动化脚本。1. 没有官方 APIAI 的训练思路是什么1.1 皇室战争 AI 训练为什么不能依赖官方 API我们先理解官方 API 到底缺什么。目前 Supercell 的公开 API 能拿到的是“结果类”数据包括玩家基本资料、奖杯数、等级。历史对战记录、卡组信息。部落数据、成员列表。但它拿不到的是当前对战的实时画面。双方费用、卡牌冷却、圣水增长状态。部队的实时坐标、血量、移动路径。任何用于控制游戏操作的接口。换句话说官方 API 可以用来做数据统计和战绩分析却不能做“实时决策 操作执行”。所以我们要在“没有官方 API”的条件下重建这三样东西。1.2 AI 要解决的三个核心问题一个能打皇室战争的 AI本质上是下面三个问题的组合感知Perception从屏幕画面里识别出卡牌、部队、圣水、敌方单位。这一层我们可以用计算机视觉解决。决策Decision根据当前局面决定出什么牌、放在什么位置。这一层可以是规则系统也可以是机器学习模型。操作Action把决策转换成真实的触控操作。这一层通过 Android ADB 或者模拟器自动化来完成。所以没有官方 API我们就把“感知”替换成目标检测模型把“操作”替换成模拟触控。这样整个链路就通了。1.3 常见的三种技术路线在开始动手前我先帮你梳理一下可选技术路线避免走弯路。技术路线原理优点缺点适合人群纯 OpenCV 图像处理模板匹配、颜色识别、轮廓检测实现简单、不需要训练泛化能力差换分辨率和皮肤就失效新手快速体验目标检测 规则决策YOLO 检测卡牌和单位规则做决策感知稳定决策可控需要在数据集和标注上花时间本文推荐路线深度强化学习把游戏状态编码成状态用强化学习训练策略上限高能学到复杂策略训练成本大、奖励函数难设计、环境难搭建进阶研究者本文重点讲第二条路线因为它最适合“工程落地”也最容易让读者从零跑通。2. 整体方案设计2.1 系统架构先看整体流程游戏画面 ↓ 屏幕采集截图 ↓ 目标检测YOLO 识别卡牌、单位、圣水数字 ↓ 状态解析当前手牌、费用、场上单位坐标 ↓ 决策模块规则或模型决定出牌策略 ↓ 坐标转换屏幕坐标 → 触控坐标 ↓ ADB 模拟点击 ↓ 回到游戏画面循环这个链路里每个模块都可以独立替换。比如你以后想从规则决策升级成强化学习只需要替换“决策模块”前面的感知和后面的操作都不用改。2.2 模块拆分按照工程化思路我把项目拆成几个独立模块模块职责关键技术screen_capture.py截取模拟器或真机屏幕ADB、PIL、OpenCVdetect.py识别画面中的卡牌和单位YOLOv8、ONNX Runtimestate.py把检测结果整理成状态对象Python 数据结构strategy.py根据状态输出决策规则引擎 / 后续可换 RLaction.py把决策转换成触控操作ADB shell inputmain.py主循环调度Python这样的设计会让你后面改代码非常舒服。比如卡牌识别不准你只需要迭代 detect 模块决策太蠢你只需要改 strategy 模块。3. 环境准备与版本说明3.1 推荐环境下面的环境是本文示例用的组合你不需要完全一致但建议版本差距不要太大否则一些 API 会有变化。操作系统Windows 10/11 或 Ubuntu 20.04 Python3.8 ~ 3.10 模拟器MuMu 模拟器 或 LDPlayer支持 ADB 调试 Android 工具platform-tools含 adb.exe 图像处理opencv-python 4.x 深度学习框架PyTorch 2.x CUDA 11.8 / 12.x 目标检测框架ultralyticsYOLOv8 / YOLO11 标注工具X-AnyLabeling 或 labelImg下面给出建议的安装命令。3.2 Python 依赖安装创建独立环境是很重要的避免把系统 Python 搞乱。conda create -n cr_ai python3.10 -y conda activate cr_ai pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python pillow numpy pip install labelimg如果你不需要 GPU 训练只做推理也可以安装 CPU 版pip install torch torchvision但训练 YOLO 时CPU 会非常慢建议至少有一张 4GB 显存的 NVIDIA 显卡。3.3 启用模拟器 ADB 调试不同模拟器的 ADB 端口不一样下面列几个常见配置模拟器ADB 地址MuMu 模拟器127.0.0.1:7555LDPlayer 9127.0.0.1:5555BlueStacks 5127.0.0.1:5555夜神 Nox127.0.0.1:62001连接命令adb connect 127.0.0.1:7555 # 确认已连接 adb devices如果看到类似下面的输出说明连接成功List of devices attached 127.0.0.1:7555 device这里要注意模拟器必须先开启“ADB 调试”选项具体入口在模拟器设置的“其他设置”或“高级设置”里。4. 数据采集构建自己的训练数据集4.1 为什么不能直接下载现成数据集你可能搜到过公开的“猫狗数据集”“COCO 数据集”但皇室战争的卡牌和单位数据基本没有公开的、带标注的标准数据集。原因有两方面游戏内卡牌、单位、皮肤会随版本更新而变化。不同分辨率和屏幕比例下目标的位置分布差异很大。所以我们必须自己采集并标注数据集。这个过程虽然麻烦但它是整个 AI 效果的关键。4.2 屏幕采集脚本先用 Python 写一个屏幕采集脚本。这里用 ADB 截图再通过adb pull把图片拉回本地。# 文件路径screen_capture.py import subprocess import os import time class ScreenCapture: def __init__(self, device127.0.0.1:7555, save_dirdataset/raw): self.device device self.save_dir save_dir os.makedirs(save_dir, exist_okTrue) def get_frame(self) - str: 截取当前屏幕并保存为本地 PNG 文件返回文件路径。 remote_path /sdcard/screen.png local_path os.path.join(self.save_dir, fframe_{int(time.time())}.png) # 1. 在设备上截图 subprocess.run( [adb, -s, self.device, shell, screencap, -p, remote_path], checkTrue, ) # 2. 拉回本地 subprocess.run( [adb, -s, self.device, pull, remote_path, local_path], checkTrue, ) return local_path if __name__ __main__: cap ScreenCapture() # 按 1 秒间隔连续采集 20 张 for i in range(20): path cap.get_frame() print(saved:, path) time.sleep(1)运行后dataset/raw目录下会生成一批游戏截屏。为了让数据集质量更高建议按不同场景采集开局阶段的手牌画面。对战中的单位混战画面。圣水溢出、费用不足的画面。不同卡组、不同皮肤的画面。4.3 标注工具X-AnyLabeling采集到原始图片后下一步就是标注。推荐使用 X-AnyLabeling它比老牌的 labelImg 更现代而且支持 YOLO 格式导出。标注时建议定义下面几个类别类别名称含义card底部手牌区的卡牌图标tower己方或敌方公主塔troop场上的部队单位elixir圣水数字区域注意如果你只做“出牌决策”优先标好 card 和 elixir 就够用了如果你想做更复杂的战场态势感知再标 troop 和 tower。X-AnyLabeling 安装方式pip install x-anylabeling x-anylabeling标注完成后导出为 YOLO 格式。目录结构应该是这样dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml其中data.yaml是 YOLO 训练时的配置文件# 文件路径dataset/data.yaml path: dataset train: images/train val: images/val names: 0: card 1: tower 2: troop 3: elixir4.4 数据增强训练数据不够时可以用数据增强扩大样本量。YOLOv8 内置了增强策略在训练参数里就可以开启hsv_h、hsv_s颜色扰动。translate平移。scale缩放。fliplr水平翻转。但这里有一个容易忽略的坑皇室战争的手牌位置可能在屏幕左下角或右下角翻转图片会改变卡牌的左右位置。如果你把“手牌在右下角”的图翻转成“手牌在左下角”而你的决策逻辑没有适配会导致坐标错乱。所以针对性做法是目标检测阶段可以使用适度翻转增强。但如果你依赖“固定 UI 位置”做决策建议关闭翻转增强或者把翻转后的坐标映射也加入决策逻辑。5. 目标检测模型训练5.1 数据划分在开始训练前先把数据集按比例划分python - EOF import os import random import shutil random.seed(2024) images os.listdir(dataset/images/train) random.shuffle(images) # 取 80% 作为训练集20% 作为验证集 split int(len(images) * 0.8) train_imgs images[:split] val_imgs images[split:] for img in train_imgs: shutil.move(fdataset/images/train/{img}, dataset/images/train/) for img in val_imgs: shutil.move(fdataset/images/train/{img}, dataset/images/val/) EOF实际操作时我建议你在采集阶段就把训练图和验证图分开存放避免后面写脚本搬移。标注好的图片手动把约 20% 放到images/val对应目录即可。5.2 训练 YOLOv8 模型YOLOv8 的训练入口很简单但参数需要解释一下。# 文件路径train_yolo.py from ultralytics import YOLO def main(): # 加载预训练权重可以加快收敛 model YOLO(yolov8n.pt) results model.train( datadataset/data.yaml, epochs100, imgsz640, batch16, device0, workers4, patience20, namecr_detect, verboseTrue, ) print(训练完成模型保存在 runs/detect/cr_detect/weights/ 目录下) if __name__ __main__: main()关键参数说明参数作用建议值epochs训练轮数100 起步看损失曲线再调整imgsz输入图片尺寸640batch批大小根据显存调整8 ~ 32device使用 GPU 还是 CPU0 表示第一张显卡patience早停耐心值20防止过拟合name实验名称每次实验用一个新名字方便对比训练过程中你可以在终端看到类似这样的输出Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 49/100 3.2G 0.96 0.52 1.18 9 640这里的 box_loss 是边框回归损失cls_loss 是分类损失。如果训练集损失持续下降但验证集损失不再下降说明开始过拟合可以提前停止或增加数据增强。5.3 评估与可视化训练结束后用验证集评估效果from ultralytics import YOLO model YOLO(runs/detect/cr_detect/weights/best.pt) metrics model.val(datadataset/data.yaml) print(metrics.box.map) # mAP50-95 print(metrics.box.map50) # mAP50其中map50IoU 阈值 0.5 时的平均精度检测任务里更常用。map50-95多个 IoU 阈值下的平均精度更严格。对于皇室战争这种固定 UI 场景map50达到 0.85 以上一般就够用了。因为卡牌图标是静态贴图识别难度远低于自然图像。5.4 推理验证训练完成后先拿一张截图做推理from ultralytics import YOLO model YOLO(runs/detect/cr_detect/weights/best.pt) results model.predict(dataset/images/val/frame_1700000000.png, conf0.5, saveTrue)推理结果会保存到runs/detect/predict/目录。打开图片检查手牌卡牌有没有全部框出来。圣水数字有没有被识别。是否存在误检比如把塔楼识别成部队。如果发现误检多常见做法是增加对应类别的训练图片而不是盲目调低置信度阈值。6. 状态解析与决策模块6.1 状态解析检测模型输出的是“类别 边框坐标”我们需要把坐标转换成对决策有意义的“状态”。这里先要明确坐标系YOLO 输出的坐标是归一化坐标范围 0 ~ 1。ADB 触控需要的是设备绝对像素坐标。模拟器窗口分辨率可能和游戏渲染分辨率不同。建议统一约定所有逻辑内部使用“游戏分辨率坐标”只在最后一步转成 ADB 坐标。# 文件路径state.py import numpy as np class CardState: def __init__(self, name: str, center_x: float, center_y: float): self.name name self.center_x center_x self.center_y center_y class GameState: def __init__(self): self.elixir 0.0 # 当前圣水 self.hand_cards [] # 手牌列表 self.troops [] # 场上单位列表 self.towers [] # 塔列表 def to_dict(self): return { elixir: self.elixir, hand_cards: [c.__dict__ for c in self.hand_cards], troops: len(self.troops), towers: len(self.towers), } def parse_yolo_result(results, img_w: int, img_h: int) - GameState: 把 YOLO 预测结果转换成 GameState。 state GameState() for box in results[0].boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) if conf 0.5: continue # 归一化坐标转像素坐标 x1, y1, x2, y2 box.xyxy[0].tolist() cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h if cls_id 0: state.hand_cards.append(CardState(card, cx, cy)) elif cls_id 2: state.troops.append(CardState(troop, cx, cy)) elif cls_id 1: state.towers.append(CardState(tower, cx, cy)) return state6.2 规则决策决策模块先用规则系统实现。规则的好处是可控、可解释。下面是一个简单示例# 文件路径strategy.py class RuleStrategy: def __init__(self): self.plan [ {elixir_min: 5, troop_name: card, position: (0.5, 0.4)}, {elixir_min: 8, troop_name: card, position: (0.6, 0.4)}, ] def decide(self, state: GameState): 根据当前圣水和手牌数量决定是否出牌。 if state.elixir 5: return None if len(state.hand_cards) 0: return None # 这里简化处理选取第一个手牌放在中场偏后位置 card state.hand_cards[0] return { action: deploy, card: card, target: (0.5, 0.45), }这个决策很粗糙实际项目中你可以按卡牌类型写更细的规则防守型敌方部队过了桥用低费单位接。进攻型己方圣水高于敌方差值时下高费单位推塔。拉扯型用建筑或单位拉扯敌方单位到国王塔附近。每一条规则都要配合“圣水消耗”“部署冷却”“单位射程”这些因素。初版你可以只实现核心规则后面再逐个补。6.3 为什么先做规则再上强化学习很多读者一上来就想做强化学习。但我建议先把规则系统跑通原因有三个强化学习需要交互环境。皇室战争没有官方模拟环境你要自己做环境封装这会花掉大量时间。奖励函数难设计。胜利 1、失败 -1 太稀疏等模型自己摸索出策略训练量非常大。规则系统能帮你验证感知层和操作层是否可靠。如果连屏幕识别都错了强化学习拿到错误状态学到的策略毫无意义。等你拥有了稳定的规则系统再考虑把策略模块替换成 DQN 或 PPO会顺利得多。7. 自动化操作ADB 模拟触控7.1 模拟点击ADB 提供了input tap命令来模拟点击adb -s 127.0.0.1:7555 shell input tap 540 960这条命令会在设备坐标 (540, 960) 处执行一次点击。7.2 模拟拖动出牌时需要“按住卡牌 → 拖到目标位置 → 松手”对应 ADB 的input swipeadb -s 127.0.0.1:7555 shell input swipe 540 960 800 600 120参数含义前两个数字起点 x、y。中间两个数字终点 x、y。最后一个数字持续时间单位毫秒这里 120ms 模拟快速拖放。7.3 坐标转换如果检测模型输出的是游戏分辨率坐标而 ADB 操作需要设备坐标需要先在配置里约定映射。# 文件路径action.py import subprocess class ActionExecutor: def __init__(self, device: str, game_size: tuple, device_size: tuple): self.device device self.game_w, self.game_h game_size self.device_w, self.device_h device_size def _to_device_coord(self, x: float, y: float): 把归一化坐标转换为设备绝对坐标。 dx int(x * self.device_w) dy int(y * self.device_h) return dx, dy def tap(self, x: float, y: float): dx, dy self._to_device_coord(x, y) subprocess.run( [adb, -s, self.device, shell, input, tap, str(dx), str(dy)], checkTrue, ) def drag_card(self, card_x: float, card_y: float, target_x: float, target_y: float): sx, sy self._to_device_coord(card_x, card_y) tx, ty self._to_device_coord(target_x, target_y) subprocess.run( [ adb, -s, self.device, shell, input, swipe, str(sx), str(sy), str(tx), str(ty), 120, ], checkTrue, )这里的game_size是游戏画面在逻辑层的分辨率device_size是模拟器窗口对应的 Android 设备分辨率。两者不一致时必须做映射。一个常见问题是模拟器窗口大小改了设备分辨率可能没改或者两者关联变化。建议把坐标系配置单独放到一个 config 文件里方便随时调整。# 文件路径config.py DEVICE 127.0.0.1:7555 GAME_SIZE (1280, 720) DEVICE_SIZE (1920, 1080)8. 主循环让 AI 跑起来把前面的模块串起来就是主循环。# 文件路径main.py import time from screen_capture import ScreenCapture from detect import load_model, detect_frame from state import parse_yolo_result from strategy import RuleStrategy from action import ActionExecutor from config import DEVICE, GAME_SIZE, DEVICE_SIZE def main(): # 1. 初始化模块 cap ScreenCapture(deviceDEVICE) model load_model(runs/detect/cr_detect/weights/best.pt) strategy RuleStrategy() executor ActionExecutor(DEVICE, GAME_SIZE, DEVICE_SIZE) # 2. 主循环 for _ in range(60): # 先跑 60 个循环做演示 frame_path cap.get_frame() # 识别 results detect_frame(model, frame_path) state parse_yolo_result(results, GAME_SIZE[0], GAME_SIZE[1]) # 决策 decision strategy.decide(state) # 执行 if decision is not None: card decision[card] target decision[target] executor.drag_card(card.center_x, card.center_y, target[0], target[1]) print(执行出牌:, state.to_dict()) time.sleep(0.5) if __name__ __main__: main()其中detect.py里的核心函数可以这样写# 文件路径detect.py from ultralytics import YOLO def load_model(weight_path: str): return YOLO(weight_path) def detect_frame(model, image_path: str): return model.predict(image_path, conf0.5, verboseFalse)运行主循环时你的模拟器里应该能看到 AI 自动打开卡牌、拖放到场上。如果一切正常恭喜你你已经成功跑通了一个“没有官方 API 的皇室战争 AI”最小闭环。9. 常见问题与排查思路在整理这份教程时我把自己遇到的典型问题都列出来了按排查优先级排序。问题现象常见原因解决思路adb devices 看不到模拟器模拟器 ADB 调试未开启或端口不对检查模拟器设置确认端口后重新 connect截图全黑或为空模拟器前台不是游戏界面或截屏权限异常先手动切到游戏画面再截图测试模型识别不到卡牌训练数据过少或标注类别错误增加训练集数量检查 data.yaml 类别顺序手牌坐标偏移游戏分辨率和设备分辨率不一致确认 GAME_SIZE 和 DEVICE_SIZE 配置出牌拖不动swipe 持续时间太短或目标坐标错误把持续时间调到 150~200ms打印坐标日志游戏更新后识别率下降卡牌图标或 UI 布局变化重新采集数据并增量训练训练时显存不足batch 或 imgsz 太大降低 batch 为 8 或 4或减小 imgsz验证 loss 降不下去数据集标注噪声大检查标注框是否漏标、错标清理脏数据9.1 ADB 连接失败的排查顺序如果你发现adb devices下面空无一物按下面顺序排查模拟器是否正在运行。是否手动执行过adb kill-server后再连接。端口是否被占用可以换一个连接端口。模拟器是否允许 ADB 连接比如 MuMu 需要在设置里打开“ADB 调试”。9.2 模型训练效果差的几个原因模型效果差绝大多数不是网络结构问题而是数据和标注问题标注框位置不准确导致边框回归不稳定。样本类别不均衡比如 card 图片 1000 张troop 只有 50 张。验证集和训练集分布差异大比如训练集都来自同一张地图背景验证集则是新地图。解决办法是增加数据多样性并反复检查标注。10. 最佳实践与工程建议10.1 数据集管理数据集是游戏 AI 项目里最宝贵的资产。建议遵循下面几项原则每次采集原始图片后先做一次去重避免相邻帧几乎一样导致过拟合。为每次版本更新保留独立的“版本目录”比如dataset_v1、dataset_v2。标注文件和数据图像成对存放任何手动修改都要记录。定期用模型在验证集上做回归测试确认新增样本没有破坏已有识别效果。10.2 模型版本与增量训练游戏版本更新后UI 变化会导致旧模型失效。这时候不需要全部重采可以走“增量训练”路线把旧数据集和新增样本合并。加载旧模型权重作为预训练权重继续训练。用小学习率比如 0.0001防止旧知识被快速破坏。model YOLO(runs/detect/cr_detect/weights/best.pt) model.train( datadataset/data.yaml, epochs30, lr00.0001, resumeFalse, )增量训练的核心是控制学习率学习率太大会产生“灾难性遗忘”导致旧样本识别率下降。10.3 日志与可观测性调试游戏 AI 时最大的问题是你不知道 AI 每一步在想什么。建议在关键节点加日志每一帧识别出了哪些卡牌和单位。决策模块为什么选择出这张牌。执行模块最终触控的坐标。一条好的日志应该能让你回放整个决策过程[09:12:01] 圣水6.5, 手牌[card, card, troop], 场上单位3 [09:12:01] 决策deploy card - target(0.5, 0.45) [09:12:01] 执行input swipe 540 960 - 960 486 120ms10.4 安全与合规边界这一点必须单独强调。游戏 AI 自动化训练需要注意几个边界只在本地模拟器或测试环境中运行不要在正式账号、正式对战中开启自动化。遵守游戏用户协议不要利用自动化脚本修改游戏数据、影响其他玩家。不要把自动化能力封装成付费工具或外挂进行传播。技术研究本身有价值但必须确保用途合法合规。10.5 性能优化建议如果你发现 AI 的响应速度跟不上游戏节奏优先优化这几个点优化点说明截图耗时使用screencap完整截图可能较慢可考虑降低截图频率模型推理导出 ONNX 后用 ONNX Runtime 推理比 PyTorch 更快坐标缓存手牌区域是固定的不需要每帧都全屏检测决策频率不一定每帧都决策可以按 0.3~0.5 秒间隔触发一个常用技巧是把屏幕分为“手牌区域”“战场区域”“圣水区域”训练三个小模型分别检测而不是一个大模型全屏检测。这样推理耗时更低而且每个区域的特征更集中识别更稳定。11. 总结与下一步进阶方向这篇文章的核心是解决“没有官方 API 时如何训练游戏 AI”的问题。整体方案可以概括为一句话用目标检测模型替代官方状态接口用 ADB 模拟触控替代官方操作接口中间用规则系统把感知和操作串起来。你已经掌握的技能包括用 Python ADB 采集游戏屏幕数据。用 X-AnyLabeling 构建自己的目标检测数据集。用 YOLOv8 训练卡牌、单位、圣水的检测模型。用 ADB 模拟点击和滑动完成游戏操作。搭建一个可扩展的主循环和模块化代码结构。如果你的目标是继续深入下一步可以按这个顺序尝试把规则决策替换成 DQN 强化学习设计简单的奖励函数比如“对敌方塔造成伤害 1己方塔掉血 -1”。引入更多的状态特征比如单位血量、卡牌剩余数量、圣水增长速度。对比不同的目标检测模型比如 YOLO11、RT-DETR在精度和速度之间做取舍。把模型导出成 ONNX接入 C 或其他语言提高推理性能。最后给你一个实用建议不要一开始就追求“打赢所有对手”。先把“能识别、能出牌、能跑完整局”跑通再逐步优化决策策略。每条规则的改动都单独跑一场测试记录胜率和平均耗时用数据说话。如果这篇文章对你有帮助可以收藏备用。如果你在数据采集、YOLO 训练或 ADB 操作环节遇到问题欢迎在评论区带上报错信息和环境版本一起讨论。
返回列表