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.
Scanned 9/12/2026
Install to Claude Code
npx -y skills add KhazixW2/MaaGumballs --skill pipeline-generate --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Pipeline Generate?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/khazixw2-pipeline-generate)More formats (shields.io, HTML) on the badges page.
---
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 步工作流(伪代码)
```python
# === 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
```bash
# 生成多个 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](../pipeline-testing/SKILL.md))。记录成功的 expand 值(score ≥ 0.99 为佳)。
### 步骤 2: 正式生成节点
```bash
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。
1. **`ocr()` 自动截图**:MaaMCP 的 `ocr()` 工具会自行获取当前画面,调用前不要重复 `screencap()`;如果换了 MCP provider,先读该工具的参数说明确认截图语义。
2. **ROI 不是越大越好**:默认 `expand=75` 会失败(OCR 把"角色"拆成"电"+"色")。多数节点 sweet spot 是 `expand=20-30`。
3. **特殊节点需要小 ROI**:"城堡" expand≥20 全失败,**只接受 0-15**(上方有图标 M/3.9m/1077/👍 干扰)。
4. **`expected` 必须匹配当前资源实际显示文本**:在 MaaGumballs 中文资源里 `["角色"]` 正确、`["Role"]` 找不到;跨语言项目要按目标资源/locale 写实际 OCR 文本或项目约定的 i18n 形式。
5. **OCR 非确定性**:同一 ROI 不同次结果可能不同,`timeout: 2000` 期间会重试。
6. **OCR 失败不要立刻换 TemplateMatch**:先看截图、扫 ROI、检查 `expected` 与颜色干扰;如果目标本质是稳定图标/按钮,TemplateMatch 本来就是正确选择,不必死守 OCR。
7. **可滚动 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]`
8. **`run_pipeline` 必须有手动超时意识**:超过 ~10 秒不返回要主动停止,可能 ROI/expected 配错或 OCR 引擎卡住。
9. **改完 pipeline 文件后调 `load_pipeline(path)` 即可**:**不需要重启 server**。`run_pipeline` 每次都按 `pipeline_path` 从磁盘读最新内容,reload 后立即生效。
10. **可滚动 UI 用统一大 ROI**:当多个目标在同一个可滚动列表(如城堡建筑列表)时,**所有节点共用同一 ROI** `[x, top_y, w, full_h]`,覆盖整个滚动区域。避免每个节点各自 ROI 滚动后失效。前提:每个节点的 `expected` 文字是唯一的(OCR 按 expected 匹配不会冲突)。
11. **ROI 上边界 ≤ 元素最小 y**:目标元素在 y=424 时,ROI y 起点必须 ≤ 424,否则切掉顶部导致 OCR 失败。例:原 ROI `[100, 450, ...]` 把"城堡管理"切掉 26px → 改为 `[100, 400, ...]` 通过。
12. **卡住时截图查看**:节点超时、OCR 找不到、行为异常时,调 `screencap` 看当前屏幕实际状态。可能界面已不在预期页、可能位置已被遮挡。
13. **跨页面流程用 `next` 状态机而非 Python orchestration**:当一个流程涉及多个页面跳转(如:大地图 → 活动入口 → 难度选择 → 队伍 → 战斗),用 MaaFramework 的 `next` + `[JumpBack]` 串节点。**不要**写 Python `for/while` 调 `context.run_task()` 模拟状态机。详见 [.claude/skills/pipeline-option/SKILL.md](../pipeline-option/SKILL.md) 的「不要做 #10」和 [.claude/skills/pipeline-guide/SKILL.md](../pipeline-guide/SKILL.md) 的「跨页面状态机」。
14. **跨文件节点引用在 `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` 字段引用它。
```jsonc
"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**。
### 模式:状态机入口节点
```jsonc
{
"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"
}
}
```
### 关键设计要点
1. **`[JumpBack]` 是状态回退的关键**:命中后执行完节点链,自动返回父节点的 `next` 继续。
2. **窄 ROI 区分同名字段**:用 y 范围 [490, 740, 100, 80] vs [490, 590, 100, 80] 区分两个"确定"按钮行(y 范围不重叠)。
3. **`target_offset` 偏移点击**:识别难度文字后用 `target_offset: [270, 0, 0, 0]` 把点击位置右移到"确定"按钮上。
4. **跨文件节点引用**: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](../pipeline-option/SKILL.md) 的「不要做 #10」和 [.claude/skills/pipeline-guide/SKILL.md](../pipeline-guide/SKILL.md) 的「跨页面状态机」典型模式。
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!