Skip to main content

python-action-guide

MaaNTE Python 自定义动作(CustomAction)编写指南。覆盖 agent/custom/action/ 下的 Python 代码的架构、注册、命名、maafocus 用户消息、日志、Controller API、Pipeline 集成、错误处理等编码规范和模式参考。在编写、修改或审查 Python 自定义动作,或需要了解 agent 项目结构与 MaaFramework Python 绑定集成方式时使用。

소스 정보

저장소
1bananachicken/MaaNTE
최근 소스 활동
2026년 5월 30일 09:00
감지된 SKILL.md 언어
중국어
스타
2,837
포크
209

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
python-action-guide
description
MaaNTE Python 自定义动作(CustomAction)编写指南。覆盖 agent/custom/action/ 下的 Python 代码的架构、注册、命名、maafocus 用户消息、日志、Controller API、Pipeline 集成、错误处理等编码规范和模式参考。在编写、修改或审查 Python 自定义动作,或需要了解 agent 项目结构与 MaaFramework Python 绑定集成方式时使用。
# MaaNTE Python CustomAction 编写指南 ## 架构定位 Python CustomAction 处理 Pipeline JSON 无法覆盖的复杂逻辑(图像算法、状态机、音频检测、MIDI 播放等)。**流程控制由 Pipeline 负责,Python 只处理难点。** 一句话:**Pipeline 管流程,Python 管难点。** 没有必要的 Python 逻辑会大大增加代码复杂度,造成下一位开发者开发调试极其困难。 所有坐标与图像以 **720p (1280×720)** 为基准。 ## 目录结构 ``` agent/ ├── main.py # 入口:venv、依赖安装、AgentServer 启动 ├── custom/ │ ├── __init__.py # from .action import * │ └── action/ │ ├── __init__.py # 所有 CustomAction 的 import 与 __all__ 注册 │ ├── Common/ # 通用工具(click、alt_click、logger、utils) │ │ ├── __init__.py │ │ ├── click.py │ │ ├── alt_click.py │ │ ├── logger.py │ │ └── utils.py # get_image、click_rect、match_template_in_region │ ├── AutoFish/ # 钓鱼业务(按功能聚合) │ ├── Tetris/ # 俄罗斯方块业务 │ │ ├── feats/ # 功能实现 │ │ └── utils/ # 工具类(棋盘、场景检测) │ ├── rhythm/ # 节奏任务 │ │ ├── feats/ │ │ └── utils/ │ ├── SoundTrigger/ # 音频触发业务 │ ├── Movement/ # 移动控制 │ ├── auto_piano/ # 自动钢琴 │ ├── pinkpaw/ # 粉爪大劫案 │ ├── DatasetCollection/ # 数据集采集 │ ├── auto_make_coffee.py # 单文件简单动作 │ ├── auto_tetris.py # 俄罗斯方块入口 │ ├── auto_f_scroll.py # 自动滚屏 │ ├── furniture_claim.py # 家具收取 │ ├── realtime_task.py # 实时任务调度 │ ├── withdraw_money_choose_item.py # 补货取钱物品选择 │ └── __init__.py # 所有 CustomAction 的导入与注册 └── utils/ ├── __init__.py # 子模块聚合导出 ├── logger.py # 日志系统(loguru/logging) ├── pienv.py # PI 环境变量 ├── maafocus.py # 用户可见消息(Pipeline focus 协议) ├── i18n.py # 多语言/翻译 ├── screen.py # 屏幕缩放/分辨率 ├── time.py # 时间工具 └── win32_process.py # Win32 窗口查找 ``` ## 注册机制 ### 装饰器注册 每个 CustomAction 类使用 `@AgentServer.custom_action("name")` 注册,名称必须与 Pipeline JSON 中 `custom_action` 的值一致: ```python from maa.agent.agent_server import AgentServer from maa.custom_action import CustomAction from maa.context import Context @AgentServer.custom_action("auto_make_coffee") class AutoMakeCoffee(CustomAction): def run( self, context: Context, argv: CustomAction.RunArg ) -> CustomAction.RunResult: ... ``` Pipeline JSON 中对应: ```jsonc { "action": { "type": "Custom", "param": { "custom_action": "auto_make_coffee", "custom_action_param": { "count": 10 } } } } ``` ### `__init__.py` 注册 所有 CustomAction 必须在 `agent/custom/action/__init__.py` 中导入并加入 `__all__`: ```python from .auto_make_coffee import * from .Tetris import * __all__ = [ "AutoMakeCoffee", "AutoTetris", "TetrisResetContext", "TetrisCheckVitalityAction", ] ``` 遗漏导入 = 动作不生效。 ## 命名 - **类名**:PascalCase,语义化。如 `AutoMakeCoffee`、`TetrisGamePlayer`、`SoundDodgeAction`。 - **注册名**:snake_case,与 Pipeline JSON 一致。如 `"auto_make_coffee"`、`"auto_tetris"`。 - **文件名**:snake_case。单文件简单动作直接用 `.py` 文件;复杂模块用目录 + `__init__.py`。 - **内部函数/变量**:snake_case,模块内部使用的加前导下划线 `_`。 ## Common 内置动作 以下 Custom Action 位于 `agent/custom/action/Common/`,可在 Pipeline 中直接使用,无需额外编写 Python 代码。 ### click_override 自定义点击。通过 `custom_action_param` 指定目标 rect,或使用当前识别结果的 box。 - 注册名:`click_override` - 参数 `custom_action_param`:`{ "target": [x, y, w, h] }` - 若未提供 `custom_action_param`,则使用 `argv.box`(识别结果 box) ```jsonc { "action": { "type": "Custom", "param": { "custom_action": "click_override", "custom_action_param": { "target": [100, 200, 50, 50] } } } } ``` ### alt_click Alt + 点击。先按下 Alt 键,再点击识别结果 box 位置,最后松开 Alt。 - 注册名:`alt_click` - 无需额外参数,点击位置由识别结果的 `box` 决定 ```jsonc { "recognition": { "type": "TemplateMatch", "param": { "template": "xxx.png" } }, "action": { "type": "Custom", "param": { "custom_action": "alt_click" } } } ``` ## Common 工具函数 `agent/custom/action/Common/utils.py` 提供常用辅助函数: | 函数 | 说明 | |------|------| | `get_image(controller)` | 截图,返回 numpy array | | `click_rect(controller, rect, delay)` | 点击指定 rect 的中心 | | `match_template_in_region(img, region, template, min_similarity, green_mask)` | 在区域内做模板匹配,返回 `(hit, score, x, y)` | ```python from Common.utils import get_image, click_rect, match_template_in_region img = get_image(controller) hit, score, x, y = match_template_in_region(img, [0, 0, 1280, 720], template, 0.8) if hit: click_rect(controller, [x, y, 50, 50]) ``` ## CustomAction 模板 ### 简单动作 ```python import json from maa.agent.agent_server import AgentServer from maa.custom_action import CustomAction from maa.context import Context from utils.logger import logger from utils.maafocus import PrintT @AgentServer.custom_action("my_action") class MyAction(CustomAction): def run( self, context: Context, argv: CustomAction.RunArg ) -> CustomAction.RunResult: PrintT(context, "my_action.started") params = {} if argv.custom_action_param: try: params = json.loads(argv.custom_action_param) except (json.JSONDecodeError, TypeError): pass controller = context.tasker.controller # 截图 controller.post_screencap().wait() img = controller.cached_image # 检查停止信号 if context.tasker.stopping: return CustomAction.RunResult(success=False) # 业务逻辑 ... logger.debug("识别结果: score=%.2f", score) return CustomAction.RunResult(success=True) ``` ### 带状态管理的动作 对于需要跨多次调用保持状态的场景(如连打计数),使用模块级全局变量,并通过独立的 reset action 重置: ```python _round_count = 0 _task_config = {} @AgentServer.custom_action("my_reset") class MyResetContext(CustomAction): def run(self, context, argv) -> CustomAction.RunResult: global _round_count, _task_config _round_count = 0 _task_config = {} return CustomAction.RunResult(success=True) @AgentServer.custom_action("my_action") class MyAction(CustomAction): def run(self, context, argv) -> CustomAction.RunResult: global _round_count _round_count += 1 ... ``` ## Controller API 通过 `context.tasker.controller` 访问,常用操作: ```python controller = context.tasker.controller # 截图 controller.post_screencap().wait() img = controller.cached_image # numpy array (BGR or BGRA) # 点击(绝对坐标,720p 基准) controller.post_touch_down(x, y).wait() time.sleep(0.005) # 短暂按下 controller.post_touch_up().wait() # 按键(虚拟键码) controller.post_key_down(0x46).wait() # F 键 time.sleep(0.1) controller.post_key_up(0x46).wait() # 组合键:先 Alt 再点 controller.post_key_down(0x12).wait() # VK_MENU (Alt) # ... 执行点击 ... controller.post_key_up(0x12).wait() ``` 常用虚拟键码: | 键 | 码 | |----|----| | Esc | 27 | | F | 70 | | E | 69 | | Q | 81 | | D | 0x44 | | F | 0x46 | | J | 0x4A | | K | 0x4B | ## Pipeline 集成 ### context.run_recognition 调用已定义的 Pipeline 识别节点: ```python # 基本调用 result = context.run_recognition("FurnitureOcrRec", image) # 带参数覆盖(动态 expected 文本、自定义 roi 等) result = context.run_recognition( "FurnitureOcrRec", image, pipeline_override={ "FurnitureOcrRec": { "recognition": {"param": {"expected": "仓鼠球"}} } }, ) if result and result.box: x, y, w, h = result.box.x, result.box.y, result.box.w, result.box.h ``` ### RecognitionDetail 结构 `context.run_recognition()` 返回 `RecognitionDetail`(`maa.define.RecognitionDetail`),结构如下: ```python @dataclass class RecognitionDetail: reco_id: int name: str algorithm: Union[AlgorithmEnum, str] hit: bool # 是否有命中 box: Optional[Rect] # 最佳命中的 box all_results: List[RecognitionResult] # 全部原始结果 filtered_results: List[RecognitionResult] # 过滤后的结果(常用) best_result: Optional[RecognitionResult] # 最佳结果 raw_detail: Dict raw_image: numpy.ndarray draw_images: List[numpy.ndarray] ``` 关键方法/属性: ```python result = context.run_recognition("MyNode", image) # 是否存在命中 if result and result.hit: # 遍历所有命中(正确的多结果方式,不是 .all) for r in result.filtered_results: # r.box — Rect(x, y, w, h) # 根据算法类型,r 还有不同属性: # OCRResult: r.text (str), r.score (float) # ColorMatchResult: r.count (int) # TemplateMatchResult: r.score (float) # result.box — 最佳命中的 Rect(快捷方式) # result.box.x, result.box.y, result.box.w, result.box.h 都是 int ``` ### 多结果模式 **点击所有命中 rect(如 ColorMatch connected 组件、OCR 多匹配):** ```python def _filtered_boxes(result): """返回 filtered_results 中所有命中的 Rect 列表""" if result is None or not result.hit: return [] return [r.box for r in result.filtered_results if r.box is not None] result = context.run_recognition("SomeColorMatch", image) for box in _filtered_boxes(result): cx, cy = box.x + box.w // 2, box.y + box.h // 2 controller.post_touch_down(cx, cy).wait() time.sleep(0.001) controller.post_touch_up().wait() ``` **遍历 OCR 多结果并提取文本:** ```python result = context.run_recognition("SomeOCRNode", image) for r in result.filtered_results if result else []: text = getattr(r, "text", "") # OCRResult 有 .text,其他类型无 box = r.box # Rect # 处理 text 和 box ... ``` **常见错误:** - ~~`result.all`~~ → 不存在,应该用 `result.filtered_results` - ~~`box.text`~~ → `Rect` 没有 `.text`,应该用 `getattr(r, "text", "")` 在结果对象上取 - ~~`ColorMatchResult.box.text`~~ → ColorMatchResult 只有 `.box`(Rect)和 `.count`(int),无 `.text` **`box` 类型注意**:MaaFramework Python 绑定返回的 `.box` 实际是 `list [x,y,w,h]` 而非 `Rect` 对象(虽然类型标注为 `Rect`),转换时需兼容: ```python def _box_to_rect(box): if isinstance(box, (list, tuple)): return list(box) return [box.x, box.y, box.w, box.h] ``` **真实 `raw_detail` 结构参考(ColorMatch):** ```json { "reco_id": 400000008, "algorithm": "ColorMatch", "box": [256, 384, 19, 18], "detail": { "all": [ {"box": [256, 384, 19, 18], "count": 185}, {"box": [256, 531, 19, 17], "count": 182} ], "best": {"box": [256, 384, 19, 18], "count": 185}, "filtered": [ {"box": [256, 384, 19, 18], "count": 185}, {"box": [256, 531, 19, 17], "count": 182} ] } } ``` - `result.filtered_results[i].box` → `list [x,y,w,h]` - `result.filtered_results[i].count` → `int`(ColorMatch)/ `result.filtered_results[i].text` → `str`(OCR) - `result.raw_detail` 包含完整 JSON-serializable dict,可作为兜底访问 ### context.run_recognition_direct 在 Python 中直接调用 MaaFramework 识别算法(无需 Pipeline 节点定义): ```python from maa.pipeline import JRecognitionType, JOCR detail = context.run_recognition_direct( JRecognitionType.OCR, JOCR(roi=[x, y, w, h]), frame
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기