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 的「跨页面状态机」典型模式。
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.