用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/OneDragon-Anything/ZenlessZoneZero-OneDragon --skill zzz-od-dev-state-machine命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | zzz-od-dev-state-machine |
| description | 绝区零一条龙 Operation 状态机开发指南,说明如何用节点、边和轮次结果编排自动化流程 |
| version | 1.0.0 |
| author | OneDragon-Anything |
| tags | ["zzz","one-dragon","operation","state-machine","development"] |
本项目的“状态机”通常指 Operation 节点图:
ZOperation:src/zzz_od/operation/zzz_operation.pyZApplication:src/zzz_od/application/zzz_application.pysrc/one_dragon/base/operation/operation.pyoperation_nodenode_fromOperationRoundResultfrom one_dragon.base.operation.operation_edge import node_from
from one_dragon.base.operation.operation_node import operation_node
from one_dragon.base.operation.operation_round_result import OperationRoundResult
from zzz_od.context.zzz_context import ZContext
from zzz_od.operation.zzz_operation import ZOperation
class MyOperation(ZOperation):
def __init__(self, ctx: ZContext):
ZOperation.__init__(self, ctx, op_name='我的流程')
@operation_node(name='入口', is_start_node=True)
def start(self) -> OperationRoundResult:
return self.round_success('下一步')
@node_from(from_name='入口', status='下一步')
@operation_node(name='处理')
def do_work(self) -> OperationRoundResult:
return self.round_success()
@operation_node常用参数:
| 参数 | 用途 |
|---|---|
name | 节点名,必须稳定,node_from.from_name 会精确匹配 |
is_start_node | 入口节点,一个流程只应有一个 |
node_max_retry_times | 当前节点最大重试次数 |
timeout_seconds | 当前节点超时时间 |
mute | 高频节点不输出过多日志 |
save_status | 保存节点状态,供后续节点读取 |
screenshot_before_round | 当前节点每一轮执行前是否自动截图,默认 True |
@node_from常用匹配方式:
@node_from(from_name='入口') # 上游成功即可进入
@node_from(from_name='入口', success=False) # 上游失败进入
@node_from(from_name='入口', status='指定状态') # 上游 status 精确匹配
@node_from(from_name='入口', success=True, status='指定状态')
同一个节点可以声明多个 @node_from,表示多个入口都能流转到这里。
| 方法 | 含义 | 典型场景 |
|---|---|---|
round_success(status=None) | 当前节点完成,进入下一条匹配边 | 点击成功、识别到目标、分支选择完成 |
round_retry(status=None, wait=...) | 当前节点失败一次,消耗重试次数 | 暂时没识别到按钮、等待页面加载 |
round_wait(status=None, wait=...) | 当前节点等待,不消耗重试次数 | 高频轮询、等待动画、持续战斗检测 |
round_fail(status=None) | 当前 Operation 失败终止 | 必要条件缺失、没有可执行方案 |
规则:
round_retry 或 round_wait。round_wait。status 是流转条件的一部分;被 @node_from(status=...) 使用的字符串不要随意改。Operation.execute() 每次循环调用 _execute_one_round()。如果当前节点是 @operation_node 方法,且该节点的 screenshot_before_round=True,框架会在调用节点方法前执行一次 self.screenshot(),更新:
self.last_screenshotself.last_screenshot_time默认 screenshot_before_round=True。
| 返回值 | 下一步 | 是否会自动重新截图 |
|---|---|---|
round_retry(...) | 继续执行同一个节点的下一轮,消耗重试次数 | 下一轮进入同一节点前会重新截图 |
round_wait(...) | 继续执行同一个节点的下一轮,不消耗重试次数 | 下一轮进入同一节点前会重新截图 |
round_success(...) | 查找匹配的下一节点;没有下一节点则 Operation 成功结束 | 有下一节点时,下一轮进入下一节点前会重新截图 |
round_fail(...) | 查找失败分支;没有下一节点则 Operation 失败结束 | 有下一节点时,下一轮进入下一节点前会重新截图 |
结论:返回值本身不会截图;它只决定是否进入下一轮。真正截图发生在下一轮节点方法执行前。
在一次节点方法调用期间,self.last_screenshot 是同一张图,除非你显式调用:
screen = self.screenshot()
因此同一个节点里连续写多次识别,默认都基于同一张截图:
@operation_node(name='识别并点击', is_start_node=True)
def check_and_click(self) -> OperationRoundResult:
# 这里使用本轮开始前自动截取的 last_screenshot
result = self.round_by_find_area(self.last_screenshot, '画面', '按钮')
if result.is_success:
self.round_by_click_area('画面', '按钮')
# 这里仍然是同一轮的 last_screenshot,不会因为刚才点击过就自动刷新
result = self.round_by_find_area(self.last_screenshot, '画面', '点击后出现的按钮')
return self.round_retry('等待点击后界面刷新', wait=1)
这种场景应拆成两个节点,或在点击后显式截图:
self.round_by_click_area('画面', '按钮')
self.screenshot()
return self.round_by_find_area(self.last_screenshot, '画面', '点击后出现的按钮')
多数 round_by_find_area、round_by_find_and_click_area、round_by_goto_screen、round_by_ocr... 辅助方法在 screen=None 时使用 self.last_screenshot。不要误以为传 None 就会立即截图。
需要新画面时,先调用 self.screenshot(),或让本轮返回 round_retry / round_wait,等下一轮自动截图。
@operation_node(name='画面识别', is_start_node=True)
def check_screen(self) -> OperationRoundResult:
current = self.check_and_update_current_screen(self.last_screenshot)
if current == '大世界':
return self.round_success('已在大世界')
if current == '菜单':
return self.round_success('需要返回')
return self.round_retry('未识别当前画面', wait=1)
@node_from(from_name='画面识别', status='需要返回')
@operation_node(name='返回大世界')
def back_to_world(self) -> OperationRoundResult:
...
@operation_node(name='返回大世界', is_start_node=True)
def back_to_world(self) -> OperationRoundResult:
op = BackToNormalWorld(self.ctx)
return self.round_by_op_result(op.execute())
适合把通用流程拆成可复用 Operation。不要把子 Operation 的内部节点复制到当前类里。
@operation_node(name='点击按钮')
def click_button(self) -> OperationRoundResult:
return self.round_by_find_and_click_area(
self.last_screenshot,
'快捷手册',
'今日最大活跃度',
success_wait=1,
retry_wait=1,
)
区域名来自 assets/game_data/screen_info/。新增或改动区域时,要同步检查 screen YAML。
@operation_node(name='画面识别', mute=True)
def check_screen(self) -> OperationRoundResult:
self.ctx.auto_battle_context.check_battle_state(self.last_screenshot)
return self.round_wait(wait_round_time=self.ctx.battle_assistant_config.screenshot_interval)
高频节点建议设置 mute=True,避免日志淹没真正错误。
@node_from(from_name='确认', success=False)
@operation_node(name='返回大世界')
def back_after_fail(self) -> OperationRoundResult:
op = BackToNormalWorld(self.ctx)
op.execute()
return self.round_fail('确认失败,已尝试返回')
失败兜底可以清理现场,但不要把业务失败伪装成成功。
应用状态机继承 ZApplication,节点写法与 ZOperation 相同。
from zzz_od.application.zzz_application import ZApplication
class MyApp(ZApplication):
def __init__(self, ctx: ZContext):
ZApplication.__init__(
self,
ctx=ctx,
app_id=my_app_const.APP_ID,
op_name=my_app_const.APP_NAME,
)
应用还需要工厂、常量、可选配置和运行记录。新增应用/插件见 zzz-od-dev-plugin。
@node_from(from_name=...) 能精确对应。is_start_node=True。status 都是固定字符串或类常量。round_wait,失败重试用 round_retry。self.screenshot()。Operation,通过 round_by_op_result 接入。mute=True。round_fail,不要吞掉关键错误。ruff check。