很多人以为给皇室战争训练一个 AI,第一件事是去找官方 API。但现实中,官方并没有提供面向普通玩家的公共对战接口。你既不能直接调用它获取实时战局,也不能通过它下发卡牌指令。于是问题就变成了:没有官方 API,我到底能不能训练出自己的皇室战争 AI?
结论是能,但真正要解决的难点,不是“训练模型”,而是“在没有接口的前提下,把一场游戏变成一个 AI 能够理解的闭环”。换句话说,你需要自己补上三个官方接口原本会提供的能力:游戏状态从哪来、动作如何下发、模型如何验证效果。
这篇文章会从问题边界、技术路线、环境搭建、状态提取、决策模型、训练验证到排错清单,完整拆解一条无官方 API 的实践路径。如果你正卡在“不知道从哪里开始”,这篇文章应该能帮你把思路理顺。
1. 没有官方 API,问题边界在哪
1.1 官方 API 缺失的真正影响
先想清楚一个事实:AI 对战类项目,通常依赖三个基础能力。
第一,感知能力。AI 需要知道场上发生了什么:我方和敌方的塔血量、圣水数量、双方卡牌剩余情况、当前场上的单位分布。如果走常规方案,这些数据通常由 API 直接返回,开发者只需要解析 JSON。
第二,决策能力。模型根据当前状态选择动作。卡牌游戏的动作空间虽然是离散的,但组合起来依然很大:出哪张牌、放在哪个坐标、什么时候放、是否等待。
第三,执行能力。决策之后,AI 需要把动作落实到游戏客户端里,比如在特定坐标完成一次拖拽放卡。
没有官方 API,第一项和第三项都需要你自己解决。感知层你只能从画面里提取,执行层你只能通过模拟点击或无障碍注入完成。这正是“训练自己的皇室战争 AI”和“训练一个围棋 AI”最大的不同:围棋有干净的棋盘状态,而皇室战争只有一帧帧带有渲染特效的画面。
1.2 需要重构的三层能力
所以,你的项目实际上被拆成了三个子项目:
- 画面采集与状态提取:从屏幕截图里识别卡牌、圣水、塔血,并转成数值向量。
- 决策模型训练:用数值向量训练一个能选择动作的策略模型。
- 动作执行闭环:把模型输出的动作位置映射成屏幕坐标并触发点击。
很多新手只盯着第二步,结果发现模型训练半天没有效果。原因很可能不是模型问题,而是第一步的状态提取太粗糙,AI 根本看不到完整的战局信息。看清这一点,整个项目的复杂度预期就正常了。
2. 三条技术路线对比
没有官方 API,不代表只有一条路。从实践角度看,主流路线有三条。
| 技术路线 | 感知方式 | 执行方式 | 开发成本 | 稳定性 | 合规风险 |
|---|---|---|---|---|---|
| 图像识别 + 模拟点击 | 截图 + 目标检测/模板匹配 | 模拟鼠标/触摸点击 | 中 | 中,受画面分辨率渲染影响 | 中高,需确认游戏条款 |
| 内存数据读取 | 读取游戏进程内存 | 直接修改或注入操作 | 高 | 中,版本更新易失效 | 极高,明确不推荐 |
| 离线录像训练 | 录像帧或人工标注数据 | 不需要实时对局 | 较低 | 高 | 低,适合算法验证 |
2.1 路线A:图像识别 + 模拟点击
这是多数个人开发者会选的方向。它思路直观:把手机或模拟器画面截图,用 OpenCV 模板匹配或者 YOLO 目标检测识别卡牌和单位位置,再通过 pyautogui 或 adb 完成点击。
优点是通用性强。只要你能看到画面,理论上就能提取状态。缺点是画面变化会影响识别结果,比如屏幕分辨率不同、卡牌皮肤不同、特效遮挡严重时,识别率会下降。
2.2 路线B:内存数据读取
部分人会考虑直接读游戏内存,把圣水、塔血这些数值“抠出来”。这种方案数据最准确,但问题也最多。
首先,游戏版本一旦更新,内存布局可能变化,你的解析代码就会失效。其次,读取进程内存、修改游戏数据,在多数网游的服务条款里都属于明确禁止行为,风险很大。从工程角度和个人安全角度,我都不建议选择这条路。即使你只是用来学习,也应该把注意力放在图像方案上。
2.3 路线C:离线录像训练
如果你暂时不追求“实时控制游戏”,可以先用对战录像构建训练集。比如保存一批对战过程,然后用标注工具标注每帧的卡牌位置和选择动作,训练一个能在“模拟状态”下做决策的模型。
这种方式的优势是把数据采集和在线执行解耦。你不需要一边打游戏一边调试脚本,也不用担心自动化点击导致账号风险。很多做游戏 AI 的研究项目,第一步都是从录屏数据构建离线环境开始的。
2.4 路线选型结论
从“训练自己的 AI”这个目标出发,更稳妥的路径是 B 路线不要碰,A 路线用于最终实现实时对战,C 路线用于冷启动和数据验证。如果你只是想验证强化学习算法,优先从离线录像或自建模拟器环境开始;如果你确实想做实时控制,再把图像识别和模拟点击串起来。
3. 系统架构:感知-决策-执行闭环
3.1 三个核心概念先对齐
在进入代码之前,先统一几个术语,避免后面理解偏差。
状态空间:AI 每步能看到的信息集合。在皇室战争里,至少应该包含手牌卡牌类型、当前圣水值、双方公主塔血量、国王塔状态、场上单位数量和位置。一个常见做法是把这些信息编码成定长向量或一组帧图像。
动作空间:AI 可以执行的操作集合。最小动作可以定义为“不出牌 + 对每个格子出某张牌”。动作空间越大,训练难度越高,所以早期建议做离散化,比如把场地划分成 6 到 9 个区域,而不是让模型输出像素级坐标。
奖励设计:强化学习里告诉模型“这一步做得好不好”的信号。对战类游戏最简单的方式是胜利 +1、失败 -1,但这种稀疏奖励训练效率很低。更实际的做法是加上塔伤害、圣水利用率等中间奖励,但要小心奖励失衡带来的负面策略。
3.2 系统包含四个模块
一个完整的无 API 训练系统,通常包含:
- 采集模块:定时截图,保存当前帧。
- 识别模块:从帧中提取卡牌、圣水、塔血等状态,并编码为数值向量。
- 决策模块:接收状态向量,输出动作。
- 执行模块:把动作转换成点击坐标,并调用自动化工具执行。
这四个模块最好解耦。采集和识别可以单独调试,决策模型可以在离线数据上训练,执行模块可以先用人工脚本验证。不要一开始就写一个“全自动打游戏”的巨型脚本,否则任何一个环节出问题,你都会很难定位。
4. 环境准备与前置条件
4.1 环境清单
从实践角度,推荐以下环境组合。版本请以实际安装时为准,本文重点演示通用思路。
- 操作系统:Windows 10/11 或 Ubuntu 20.04 以上
- Python:3.10 或 3.11
- 依赖库:OpenCV、NumPy、PyTorch、pyautogui
- 运行载体:Android 模拟器或一台可连接电脑的测试手机
- 可选工具:LabelImg 或 X-AnyLabeling(用于标注卡牌数据集)
如果你选择在模拟器里运行,推荐使用支持 adb 的模拟器,因为 adb 的截图和点击接口比屏幕坐标更稳定。真机也可以,但需要开启开发者调试选项,并且注意不同手机的屏幕分辨率适配。
4.2 安装步骤
创建虚拟环境并安装依赖:
python -m venv cr-ai-env source cr-ai-env/bin/activate # Windows 下使用 cr-ai-env\Scripts\activate pip install opencv-python numpy torch pyautogui如果你打算用 yolo 系列做卡牌目标检测,可以再安装 ultralytics:
pip install ultralytics以上命令只安装基础依赖。实际项目中,你可能还需要 mss 来提升截图性能。mss 比 pyautogui.screenshot 更快,适合需要连续截图的场景:
pip install mss4.3 运行环境验证
在开始写识别逻辑前,先确认三件事。
第一,截图函数能正常工作;第二,模拟点击能触发游戏响应;第三,不同分辨率下坐标是否一致。
先运行一个最小截图程序:
import cv2 import numpy as np import pyautogui img = pyautogui.screenshot() frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) cv2.imwrite("screen.png", frame) print("screenshot saved")如果你能正常生成 screen.png,说明画面采集链路是通的。这一步很关键,它会排除很多底层环境问题。
5. 从屏幕到状态:状态提取示例
5.1 裁剪并预处理截图
游戏画面通常包含大量无关信息。我们不需要把整张图交给模型,只需要裁剪出卡牌栏、圣水条、两方塔血这几个区域。
import cv2 import numpy as np # 文件路径:capture_utils.py class ScreenRegion: def __init__(self, x, y, w, h): self.x = x self.y = y self.w = w self.h = h # 假设游戏窗口为 1280x720,不同分辨率需要重新标定 CARD_BAR_REGION = ScreenRegion(40, 630, 1200, 80) ELIXIR_REGION = ScreenRegion(560, 600, 160, 30) TOWER_HP_REGION = ScreenRegion(150, 80, 300, 30) def crop_region(frame, region): return frame[region.y:region.y + region.h, region.x:region.x + region.w]这段代码的核心是“区域标定”。你要先截一张游戏截图,然后手动确认卡牌栏、圣水条、塔血条在画面中的像素范围。不同设备的分辨率不同,所以区域标定参数需要单独调整。
5.2 用模板匹配识别卡牌
当你只是想快速验证闭环时,模板匹配比训练 YOLO 模型更省事。把每张卡牌的图标截图保存为模板,再用 OpenCV 的 matchTemplate 找到当前手牌里有哪些卡。
import cv2 # 文件路径:card_detector.py def find_cards(frame, card_templates, threshold=0.75): found = [] for card_id, template_path in enumerate(card_templates): template = cv2.imread(template_path) if template is None: continue th, tw = template.shape[:2] res = cv2.matchTemplate(frame, template, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(res) if max_val >= threshold: x, y = max_loc found.append({ "card_id": card_id, "center": (x + tw // 2, y + th // 2), "confidence": max_val }) return found模板匹配的优点是代码量少、不需要标注和训练;缺点是它对卡牌的缩放、旋转和渲染特效比较敏感。如果你的手牌区域是固定大小,直接用模板匹配通常足够跑通第一版。
5.3 把识别结果转成状态向量
模型不能直接吃“卡牌名称”这种文本,需要转换成数值向量。下面用一个简化例子演示状态编码。
# 文件路径:build_state.py def build_state(card_ids, elixir, my_tower_hp, enemy_tower_hp, deck_size=4): # 手牌编码:one-hot 风格,简化成前几号位的占位 hand = [0.0] * deck_size for idx, card_id in enumerate(card_ids[:deck_size]): hand[idx] = card_id / 20.0 # 假设最多 20 种卡牌,做归一化 # 圣水、塔血都做归一化 elixir_norm = elixir / 10.0 my_hp_norm = my_tower_hp / 4000.0 enemy_hp_norm = enemy_tower_hp / 4000.0 return hand + [elixir_norm, my_hp_norm, enemy_hp_norm]这里的状态向量是一个纯数值列表。实际项目中,你可能还会加入时间戳、双方场上单位数量、距离上一次出牌的间隔等特征。特征越多,模型越容易学到复杂策略,但训练难度也越高。建议从最小特征集开始,跑通闭环后再逐步增加。
6. 决策模型:先模仿后强化
6.1 为什么要先做模仿学习
直接上手强化学习训练,你会遇到两个现实问题:一是环境交互速度慢,AI 要从零开始探索“出牌”策略,早期基本是乱打;二是奖励稀疏,一场几分钟的对局最终只有胜负,模型很难从单个样本中学会有效动作。
更务实的方案是先做模仿学习。你可以先录制几场自己打的或高手打的录像,标注出关键帧的卡牌选择,然后用这些数据训练一个初始策略模型。这个模型不需要很强,只要能学会“在什么情况下正常出牌”,就可以作为强化学习的初始化权重。
模仿学习本质上把问题从“空房间找路”变成了“基于已有轨迹做监督学习”。训练门槛低很多,也更容易暴露状态提取和动作映射的问题。
6.2 最小决策循环示例
在实现强化学习之前,先用一个极简的决策循环验证“状态到动作”的通路。下面这段代码随机选择一个可执行动作,但结构上已经包含了实际的执行模块调用点。
import random import pyautogui import time # 文件路径:decision_loop.py class SimpleAgent: def __init__(self, card_positions, board_positions): self.card_positions = card_positions # 手牌区域对应的屏幕坐标 self.board_positions = board_positions # 场地离散化后的坐标 def predict(self, state): # 先用随机策略验证闭环 card_idx = random.randint(0, len(self.card_positions) - 1) board_idx = random.randint(0, len(self.board_positions) - 1) return card_idx, board_idx def execute(self, action): card_idx, board_idx = action card_x, card_y = self.card_positions[card_idx] board_x, board_y = self.board_positions[board_idx] pyautogui.moveTo(card_x, card_y) pyautogui.mouseDown() pyautogui.moveTo(board_x, board_y, duration=0.2) pyautogui.mouseUp()这里的 card_positions 是卡牌栏中每一张卡的屏幕坐标,board_positions 是把战场网格化后的落点坐标。随机策略不能赢,但它能帮你确认“点击动作是否真的能被游戏正确识别”,这是后续训练一切模型的前提。
6.3 从 DQN 开始的强化学习框架
当模仿学习已经有一个能出牌的初始策略后,就可以切换到强化学习。我们以 DQN 为例,因为它实现简单,适合离散动作空间。
神经网络结构部分:
import torch import torch.nn as nn # 文件路径:dqn_model.py class DQN(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.net = nn.Sequential( nn.Linear(state_dim, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU(), nn.Linear(128, action_dim) ) def forward(self, x): return self.net(x)训练循环部分:
import random from collections import deque # 文件路径:train_dqn.py class DQNTrainer: def __init__(self, state_dim, action_dim, lr=1e-3): self.q_net = DQN(state_dim, action_dim) self.target_net = DQN(state_dim, action_dim) self.target_net.load_state_dict(self.q_net.state_dict()) self.optimizer = torch.optim.Adam(self.q_net.parameters(), lr=lr) self.memory = deque(maxlen=10000) self.action_dim = action_dim def remember(self, state, action, reward, next_state, done): self.memory.append((state, action, reward, next_state, done)) def train_step(self, batch_size=64, gamma=0.99): if len(self.memory) < batch_size: return 0.0 batch = random.sample(self.memory, batch_size) states, actions, rewards, next_states, dones = zip(*batch) states = torch.tensor(states, dtype=torch.float32) actions = torch.tensor(actions, dtype=torch.long).unsqueeze(1) rewards = torch.tensor(rewards, dtype=torch.float32).unsqueeze(1) next_states = torch.tensor(next_states, dtype=torch.float32) dones = torch.tensor(dones, dtype=torch.float32).unsqueeze(1) q_values = self.q_net(states).gather(1, actions) with torch.no_grad(): max_next_q = self.target_net(next_states).max(1, keepdim=True)[0] target_q = rewards + gamma * max_next_q * (1 - dones) loss = nn.MSELoss()(q_values, target_q) self.optimizer.zero_grad() loss.backward() self.optimizer.step() return loss.item() def sync_target(self): self.target_net.load_state_dict(self.q_net.state_dict()) def act(self, state, epsilon=0.1): if random.random() < epsilon: return random.randint(0, self.action_dim - 1) with torch.no_grad(): state_t = torch.tensor(state, dtype=torch.float32).unsqueeze(0) q = self.q_net(state_t) return int(q.argmax(dim=1).item())这段代码是一个标准的 DQN 训练器。真正到项目中,你还需要把它和前面的采集、识别、执行模块串起来,定义 reward 函数,比如每次对敌方公主塔造成伤害时给予正奖励,自己掉血时给予负奖励。
这里要特别提醒:DQN 只是技术路线的一种。如果你的运行速度很慢,一局对战需要几分钟,那你需要用离线回放或模拟器加速来降低采样成本。否则模型收敛会非常缓慢。
7. 训练卡关排查:常见问题与解决思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 截图正常但识别不到卡牌 | 模板图像与实际画面分辨率不一致 | 检查模板尺寸和游戏截图尺寸 | 重新截取模板,或对截图进行缩放匹配 |
| 点击执行后游戏无反应 | 坐标偏移,或点击发生太快被系统忽略 | 打印实际点击坐标并人工核对 | 调整 region 标定,点击前增加延时 |
| 状态向量全部是 0 或固定值 | 识别模块返回结果为空,区域裁剪错误 | 单独输出裁剪后的图片查看 | 确认裁剪区域坐标,检查识别阈值 |
| 模型训练 loss 不下降 | 状态编码噪声太大,或奖励太稀疏 | 统计状态分布、打印中间奖励 | 简化状态空间,增加中间奖励,先做模仿学习 |
| 训练很久但胜率很低 | 动作空间过大,策略探索效率低 | 统计动作频率 | 缩小动作空间,把场地分成更少的离散区域 |
| 模拟器画面卡顿 | 截图频率过高或游戏渲染负载大 | 检查 CPU 占用和截图耗时 | 降低截图帧率,使用 mss 替代慢速截图接口 |
| 不同电脑上表现不一致 | 屏幕缩放比例导致坐标偏移 | 检查系统显示缩放设置 | 尽量固定窗口大小,或通过 adb 相对坐标执行 |
以上问题里,最常见的是识别和坐标问题,而不是模型问题。很多项目最后失败都不是“模型不聪明”,而是 AI 根本没看清游戏画面,或者手没点对地方。所以在排查时,优先怀疑采集、识别、执行这三层,最后才怀疑模型参数。
8. 工程化与合规建议
8.1 数据采集与标注
如果你的卡牌识别想做得更稳定,建议从模板匹配升级为目标检测模型。你可以用录屏拆帧的方式制作训练集,然后用 X-AnyLabeling 或 LabelImg 手工标注卡牌位置,再用 YOLOv8 训练自己的数据集。
具体到皇室战争项目,卡牌数量很多,一张一张截模板很累,但用目标检测模型可以更鲁棒地应对不同皮肤和特效遮挡。数据集建议只保留清晰帧,特效强烈、严重遮挡的帧可以先不标注。先保证模型在正常画面上有高识别率,再逐步覆盖复杂情况。
8.2 日志与回放
训练 AI 的过程中,日志和回放非常重要。
建议每次对局保存几个文件:
- 原始截图序列,或者关键帧截图
- 识别后的状态向量
- 模型输出的动作
- 实际结果和 reward
这样你可以复盘“AI 当时看到了什么”,而不是只盯着曲线。很多策略问题,比如 AI 一直把牌放在同一个位置,只有看到画面才能定位原因。
8.3 合规边界
这一点必须单独强调。自动化控制游戏客户端、读取游戏进程数据,在多数游戏的用户协议中可能被定义为违规行为。本文讨论所有技术细节,目的是帮助你在本地环境学习 AI、验证算法,而不是鼓励破坏他人游戏体验或绕过游戏规则。
在实际操作前,至少确认三件事:
- 你的使用环境是否允许模拟点击或自动化测试?
- 你是否只是在自己可控的测试账号、测试环境中运行?
- 你是否会避免在排位赛等在线对战场景中使用自动化脚本?
如果哪一条不满足,就不要继续做实时执行模块,只保留“离线录像 + 模仿学习 + 状态提取”这部分用于学习即可。技术能力不应该变成破坏公平性的工具。
9. 总结与下一步
回到最初的问题:没有官方 API,我如何训练自己的皇室战争 AI?
答案不是执着于找接口,而是自己搭一条“感知-决策-执行”的闭环。先用截图和 OpenCV 从画面中提取卡牌、圣水、塔血等状态,再用模仿学习得到一个能出牌的初始策略,紧接着用强化学习在你定义的奖励信号下迭代优化,最后用自动点击闭环完成执行。每一层都可以独立测试,也可以逐步替换成更好的方案。
从实操角度,我给你的第一条建议是:先不要追求“模型很强”,先跑通“AI 能看见、能点出去”。第二步才是把随机策略换成监督学习策略,第三步再进入强化学习。如果你跳过了前面的基础闭环,后面每一步都会很痛苦。
这个项目真正训练的不是一个模型,而是你解决真实环境中不确定性的工程能力。先把“看得懂局面”这一点做到位,再谈“策略比别人强”。把数据闭环跑通,比换一个更大的模型更有价值。这一步走通,你的皇室战争 AI 就已经赢过了大多数还没开工的人。