| 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 步工作流(伪代码)
from maa_mcp.adb import find_adb_device_list, connect_adb_device
controller_id = connect_adb_device(find_adb_device_list()[0])
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"]
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),
]
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
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]
-
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] 串节点。不要写 Python for/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],
"upper": [55, 215, 102],
"action": "DoNothing",
"post_delay": 200,
"timeout": 2000
},
"AutoSky_CheckEnergyZero": {
"recognition": "OCR",
"expected": ["0/\\d+"],
"roi": [850, 1280, 220, 50],
"color_filter": "AutoSky_GreenCheck",
"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",
"[JumpBack]BigMap_Activity"
],
"timeout": 10000
},
"MyActivity_EnterBattle": {
"recognition": "OCR",
"expected": ["进入战斗"],
"action": "Click",
"next": [
"MyActivity_FightStart",
"[JumpBack]MyActivity_TravelSelect_Boat",
"[JumpBack]MyActivity_TravelSelect_Walk"
]
},
"MyActivity_TravelSelect_Boat": {
"recognition": "OCR",
"expected": ["确定"],
"roi": [490, 740, 100, 80],
"action": "Click"
},
"MyActivity_TravelSelect_Walk": {
"recognition": "OCR",
"expected": ["确定"],
"roi": [490, 590, 100, 80],
"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 的「跨页面状态机」典型模式。