name: "pipeline-generate"
description: "Generate MaaFramework Pipeline nodes and recognition snippets from screenshots or observed UI state. Use for OCR node generation, ROI sweep, choosing TemplateMatch/OCR/ColorMatch/CustomRecognition, preserving target-file schema style, and designing next/[JumpBack] links before merging generated nodes into pipeline JSON."
pipeline-generate
概念
Pipeline 由 Node 组成。本 skill 针对OCR 文本识别节点,按 Pipeline 协议生成节点 JSON 并合并到目标 pipeline 文件。
核心流程:连接设备 → ocr() 拿 box → 扩大 ROI → 合并节点
自带脚本(与本 SKILL.md 同目录):
| 脚本 | 用途 |
|------|------|
| generate_node.py | 单节点生成(默认 expand=20) |
| generate_sweep.py | 多 expand 变体扫描,找最佳 ROI |
MCP 工具绑定
依赖 maa-mcp MCP 服务。
| 工具 | 说明 |
|------|------|
| find_adb_device_list / connect_adb_device | 连接设备 |
| ocr | 截图 + OCR 一步完成(内部已调 screencap,外部不要再调) |
| load_pipeline / save_pipeline | 读/写 pipeline JSON |
| check_and_download_ocr | 首次需下载 OCR 模型 |
| run_pipeline | 测试 pipeline 节点 |
输入参数
| 参数 | 必填 | 默认 | 说明 |
|------|------|------|------|
| target_text | ✅ | — | 要识别的目标中文文字 |
| node_name | ✅ | — | 节点名(PascalCase) |
| pipeline_file | ✅ | — | 目标 pipeline 路径(相对 assets/resource/base/pipeline/xxx.json 或绝对路径) |
| action_type | ❌ | Click | Click / DoNothing / LongPress / Swipe / ClickKey / InputText |
| expand_offset | ❌ | 20 | ROI 扩边像素(推荐先用 sweep 找最佳) |
| post_delay | ❌ | 500 | |
| timeout | ❌ | 2000 | |
| overwrite | ❌ | False | 节点名冲突时是否覆盖 |
3 步工作流(伪代码)
# === Step 1: 连接设备 ===
from maa_mcp.adb import find_adb_device_list, connect_adb_device
controller_id = connect_adb_device(find_adb_device_list()[0])
# === Step 2: OCR 拿 box + 算 ROI ===
from maa_mcp.vision import ocr
from maa_mcp.download import check_and_download_ocr
ocr_results = ocr(controller_id)
if isinstance(ocr_results, str) and "OCR 模型文件不存在" in ocr_results:
check_and_download_ocr()
ocr_results = ocr(controller_id)
matched = [r for r in ocr_results if target_text in (r.text if hasattr(r, "text") else r["text"])]
best = max(matched, key=lambda r: r.score if hasattr(r, "score") else r["score"])
box = best.box if hasattr(best, "box") else best["box"]
# 扩大 ROI(720p 硬编码 + 4 边裁剪)
SCREEN_W, SCREEN_H = 720, 1280
x, y, w, h = box
E = expand_offset
roi = [
max(0, x - E),
max(0, y - E),
min(SCREEN_W - max(0, x - E), w + 2 * E),
min(SCREEN_H - max(0, y - E), h + 2 * E),
]
# === Step 3: 合并到目标 pipeline ===
from maa_mcp.pipeline_tools import load_pipeline, save_pipeline
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[3]
pipeline_path = Path(pipeline_file)
if not pipeline_path.is_absolute():
pipeline_path = PROJECT_ROOT / "assets" / "resource" / "base" / "pipeline" / pipeline_file
existing = load_pipeline(str(pipeline_path)) or {}
if node_name in existing and not overwrite:
raise RuntimeError(f"节点 '{node_name}' 已存在")
existing[node_name] = {
"recognition": "OCR",
"expected": [target_text],
"roi": roi,
"action": action_type,
"post_delay": post_delay,
"timeout": timeout,
}
save_pipeline(
pipeline_json=json.dumps(existing, ensure_ascii=False, indent=4),
output_path=str(pipeline_path),
overwrite=True,
)
ROI 扩大示意
原始 box: ┌────┐
│ 文字│
└────┘
扩大后 roi: ┌──────────┐
│ ┌────┐ │
│ │文字│ │
│ └────┘ │
└──────────┘
使用流程
脚本位于
.claude/skills/pipeline-generate/,所有命令从项目根目录f:\workspace\MAAGC运行。
步骤 1: Sweep 找最佳 expand
# 生成多个 expand 变体的测试 pipeline
python .claude/skills/pipeline-generate/generate_sweep.py "角色" "46,1248,50,30" 0,5,10,15,20,25,30
然后用 run_pipeline 逐个测试每个 Sweep_<text>_eN 节点,用 BackButton_500ms 返回大地图(详见 pipeline-testing)。记录成功的 expand 值(score ≥ 0.99 为佳)。
步骤 2: 正式生成节点
python .claude/skills/pipeline-generate/generate_node.py "角色" UI_RoleListPage main_ui.json --expand 20 --overwrite
关键经验
历史审查后的生成策略
- 先判断节点类型,不要默认所有问题都是 OCR:稳定图标/按钮优先 TemplateMatch,颜色状态可用 ColorMatch,动态文本用 OCR,列表/复杂图像后处理用 CustomRecognition。
- MaaGumballs 的历史文件多为平铺字段风格;M9A HEAD 多为 v5 object-form:
action: { type, param }、recognition: { type, param }。生成时沿用目标文件的既有风格,不要在同一局部混用两套格式。 - 生成链路时先画父级
next状态机:稳定页面、成功态、弹窗[JumpBack]、加载[JumpBack]、危险确认分支分开建节点。 - 对会消耗资源或改变账号状态的节点,默认生成
DoNothing或单独验证节点;只有用户明确要执行时才生成直接点击确认。 - 如果需要 Python,先决定是 CustomAction 还是 CustomRecognition:动作/控制流用 CustomAction;识别后处理和动态 box 返回用 CustomRecognition。
-
ocr()自动截图:MaaMCP 的ocr()工具会自行获取当前画面,调用前不要重复screencap();如果换了 MCP provider,先读该工具的参数说明确认截图语义。 -
ROI 不是越大越好:默认
expand=75会失败(OCR 把"角色"拆成"电"+"色")。多数节点 sweet spot 是expand=20-30。 -
特殊节点需要小 ROI:"城堡" expand≥20 全失败,只接受 0-15(上方有图标 M/3.9m/1077/👍 干扰)。
-
expected必须匹配当前资源实际显示文本:在 MaaGumballs 中文资源里["角色"]正确、["Role"]找不到;跨语言项目要按目标资源/locale 写实际 OCR 文本或项目约定的 i18n 形式。 -
OCR 非确定性:同一 ROI 不同次结果可能不同,
timeout: 2000期间会重试。 -
OCR 失败不要立刻换 TemplateMatch:先看截图、扫 ROI、检查
expected与颜色干扰;如果目标本质是稳定图标/按钮,TemplateMatch 本来就是正确选择,不必死守 OCR。 -
可滚动 UI 用大 ROI + 父级 orchestrator(重要):
- 不要在 Click 节点的
next里放[JumpBack]CastleSwipeDown/Up—— 找不到文字时会死循环滑动! - 正确模式参考 marry.json 里的
CastleHall节点:父级 orchestrator 节点的next列表里放[JumpBack]XXXEntry+[JumpBack]XXXSwipeDown+[JumpBack]XXXSwipeUp等 - 滚动容错 ROI 范围参考
CastleHallEntry:[60, 391, 609, 795]
- 不要在 Click 节点的
-
run_pipeline必须有手动超时意识:超过 ~10 秒不返回要主动停止,可能 ROI/expected 配错或 OCR 引擎卡住。 -
改完 pipeline 文件后调
load_pipeline(path)即可:不需要重启 server。run_pipeline每次都按pipeline_path从磁盘读最新内容,reload 后立即生效。 -
可滚动 UI 用统一大 ROI:当多个目标在同一个可滚动列表(如城堡建筑列表)时,所有节点共用同一 ROI
[x, top_y, w, full_h],覆盖整个滚动区域。避免每个节点各自 ROI 滚动后失效。前提:每个节点的expected文字是唯一的(OCR 按 expected 匹配不会冲突)。 -
ROI 上边界 ≤ 元素最小 y:目标元素在 y=424 时,ROI y 起点必须 ≤ 424,否则切掉顶部导致 OCR 失败。例:原 ROI
[100, 450, ...]把"城堡管理"切掉 26px → 改为[100, 400, ...]通过。 -
卡住时截图查看:节点超时、OCR 找不到、行为异常时,调
screencap看当前屏幕实际状态。可能界面已不在预期页、可能位置已被遮挡。 -
跨页面流程用
next状态机而非 Python orchestration:当一个流程涉及多个页面跳转(如:大地图 → 活动入口 → 难度选择 → 队伍 → 战斗),用 MaaFramework 的next+[JumpBack]串节点。不要写 Pythonfor/while调context.run_task()模拟状态机。详见 .claude/skills/pipeline-option/SKILL.md 的「不要做 #10」和 .claude/skills/pipeline-guide/SKILL.md 的「跨页面状态机」。 -
跨文件节点引用在
run_pipeline测试中会失败:MaaFramework 全局加载时所有assets/resource/base/pipeline/*.json合并到同一命名空间,[JumpBack]OtherFileNode能解析。但run_pipeline只加载单文件,跨文件引用会报"加载 Pipeline 失败"。应对:- 单元测试每个节点用
run_pipeline(无跨文件依赖的子流程)是 OK 的 - 含跨文件引用的状态机流程,集成测试必须用 MaaFramework GUI/CLI 触发
- 调试时可考虑
MaaCli命令行运行全 bundle
- 单元测试每个节点用
已验证最优 expand(5 节点实测)
| 节点 | expand | score | 备注 |
|------|--------|-------|------|
| UI_RoleListPage | 20 | 0.9997 | 中部偏左 |
| UI_RoleFormationPage | 20 | 0.998 | 角色右边 |
| UI_CastlePage | 3 | 0.997 | ⚠️ 仅 0-15 |
| UI_TeamPage | 20 | 0.997 | 城堡右边 |
| ClickGoToArchipelago | 20 | 0.991 | 中间大地图按钮 |
用 color_filter 减少 OCR 干扰(实战技巧)
场景:ROI 里同时有目标文字 + 周边装饰(如"0/31"绿色能量条 vs "0/23"绿色节点数),OCR 可能误识别装饰色块。
方案:先建一个 ColorMatch 节点(限定像素颜色范围),然后在其他 OCR 节点上加 color_filter 字段引用它。
"AutoSky_GreenCheck": {
"recognition": "ColorMatch",
"roi": [558, 802, 157, 45],
"method": 4,
"lower": [22, 123, 57], // RGB 下界(暗绿)
"upper": [55, 215, 102], // RGB 上界(亮绿)
"action": "DoNothing",
"post_delay": 200,
"timeout": 2000
},
"AutoSky_CheckEnergyZero": {
"recognition": "OCR",
"expected": ["0/\\d+"],
"roi": [850, 1280, 220, 50],
"color_filter": "AutoSky_GreenCheck", // ← 只在绿色区域 OCR
"action": "DoNothing"
}
取色技巧(用截图工具):
- 目标区域:取目标装饰/边框色(非文字色,文字一般会变色)
- RGB 范围要宽松一些(±20),覆盖光照变化
- method=4 是 RGB(0=HSV)
实战案例:本项目(MaaGumballs)用这个方法区分"能量条 0/31"vs"节点数 0/23",两者都是绿色 OCR 文本,周围装饰色也不同。
已验证:可滚动 UI 统一 ROI(10 城堡建筑)
| 节点 | 统一 ROI | score | 备注 |
|------|----------|-------|------|
| CastleManage | [100, 400, 520, 880] | 0.999 | 顶部 |
| Market | 同上 | 0.999 | 顶部 |
| Blacksmith | 同上 | 0.998 | 顶部 |
| AlchemyWorkshop | 同上 | 0.999 | 顶部 |
| TrainingCenter | 同上 | 1.000 | 顶部 |
| CastleMainHall | 同上 | 0.876 | 顶部只露 25px |
| Shrine | 同上 | 0.999 | 中段 |
| Family | 同上 | 0.999 | 中段 |
| Museum | 同上 | 0.999 | 底部 |
| Manor | 同上 | 0.999 | 底部 |
关键设计:
- 所有节点 ROI 完全相同(
[100, 400, 520, 880],覆盖 y=400-1280) - 不靠 expand 微调,靠
expected文字差异让 OCR 区分 - 不放
next链(避免死循环)
跨页面状态机流程(用 next + [JumpBack])
当生成的活动流程需要跨多个页面跳转(如:大地图 → 活动入口 → 难度选择 → 队伍 → 战斗),用 MaaFramework 的 next + [JumpBack] 机制串接各页面节点,不要写 Python orchestration。
模式:状态机入口节点
{
"MyActivity_Start": {
"next": [
"MyActivity_TeamReady", // 已在队伍配置页 → 点击"进入战斗"
"[JumpBack]MyActivity_Difficulty_Select", // 在难度选择页 → 选难度
"[JumpBack]MyActivity_Enter" // 在大地图 → 找入口
],
"timeout": 10000
},
"MyActivity_Enter": {
"next": [
"MyActivity_Enter_Click", // 找到图标 → 点击
"[JumpBack]BigMap_Activity_Resident", // 切"常驻"tab
"[JumpBack]BigMap_Activity" // 打开活动页
],
"timeout": 10000
},
"MyActivity_EnterBattle": {
"recognition": "OCR",
"expected": ["进入战斗"],
"action": "Click",
"next": [
"MyActivity_FightStart", // 战斗开始
"[JumpBack]MyActivity_TravelSelect_Boat", // 乘船
"[JumpBack]MyActivity_TravelSelect_Walk" // 步行 fallback
]
},
"MyActivity_TravelSelect_Boat": {
"recognition": "OCR",
"expected": ["确定"],
"roi": [490, 740, 100, 80], // 窄 ROI 限定乘船行
"action": "Click"
},
"MyActivity_TravelSelect_Walk": {
"recognition": "OCR",
"expected": ["确定"],
"roi": [490, 590, 100, 80], // 窄 ROI 限定步行行
"action": "Click"
}
}
关键设计要点
[JumpBack]是状态回退的关键:命中后执行完节点链,自动返回父节点的next继续。- 窄 ROI 区分同名字段:用 y 范围 [490, 740, 100, 80] vs [490, 590, 100, 80] 区分两个"确定"按钮行(y 范围不重叠)。
target_offset偏移点击:识别难度文字后用target_offset: [270, 0, 0, 0]把点击位置右移到"确定"按钮上。- 跨文件节点引用:MaaFramework 全局加载会合并所有
pipeline/*.json,所以[JumpBack]BigMap_Activity(在 main_ui.json)能从 growth_trial.json 引用。但run_pipeline测试只加载单文件,集成测试需用 GUI/CLI。
与 Python orchestration 的本质区别
| 状态机(推荐) | Python orchestration(次选) |
|--------------|--------------------------|
| 流程推进由 MaaFramework 调度 | 自己写 for/if 调度 |
| 每个节点 next 显式声明后继 | Python 函数串行 run_task |
| [JumpBack] 自动状态回退 | 手动实现回退逻辑 |
| 跨页面异常有自然路径 | 需手动 try/except |
详见 .claude/skills/pipeline-option/SKILL.md 的「不要做 #10」和 .claude/skills/pipeline-guide/SKILL.md 的「跨页面状态机」典型模式。
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.