绝区零一条龙:从"翻车现场"到全自动挂机,事件驱动状态机如何撑起这套游戏自动化框架?
【免费下载链接】ZenlessZoneZero-OneDragon绝区零 一条龙 | 全自动 | 自动闪避 | 自动每日 | 自动空洞 | 支持手柄项目地址: https://gitcode.com/gh_mirrors/ze/ZenlessZoneZero-OneDragon
深夜两点,你盯着屏幕,自动战斗脚本又在 BOSS 战里卡死了:角色原地发呆,技能循环毫无反应,一场本该 3 分钟的战斗拖成了 10 分钟,最后以团灭收场。这不是个别现象,而是游戏自动化领域的通病——脚本能"动",却不会"看"、不会"想"、不会"应变"。
绝区零一条龙(ZenlessZoneZero-OneDragon)正是冲着这个问题来的。这个基于 Python 的开源游戏自动化框架,用一套事件驱动状态机引擎重新定义了"挂机":它不只是机械地按键盘,而是先"看懂画面",再"决定下一步做什么",最后才"执行操作"。本文将带你从它的核心设计出发,看看这套框架凭什么敢说自己能全自动刷空洞、自动闪避、自动做每日——以及它为什么值得你在动手写下一个自动化脚本前,先认真研究一遍。
一个深夜翻车现场,暴露了脚本的三个致命伤
先从一个具体的翻车场景说起。假设你想写一个"自动刷副本"的脚本,第一版通常长这样:等 5 秒 → 按攻击键 → 循环。它确实能跑,但只能跑 30 秒——因为游戏不会按你的剧本走:
- 画面是动态的。加载画面、结算弹窗、升级动画,每个环节的节奏都不一样,固定延时迟早对不上。
- 状态是分叉的。同样点一下"挑战"按钮,可能进入战斗、可能弹出体力不足、可能网络重连,分支多得写不完。
- 失败是不可预测的。一次闪避失误、一次技能放空,都可能让流程走向完全不同的结局。
传统脚本的应对方式是用if/else堆逻辑,但游戏的流程复杂度远超人的枚举能力。绝区零一条龙的答案,是把"流程"彻底数据结构化:不再是一行行顺序执行的代码,而是一张可以运行、可以调试、可以复用的有向节点图。这套设计正是它的核心资产,我们一步步拆开看。
拆开快递:一条龙框架的三层世界
动手看代码前,先建立全局认知。项目的目录结构非常规整,可以理解为三个相互独立的"世界":
第一层:通用自动化底座(src/one_dragon/)。这里没有一行绝区零专属代码,只有与游戏无关的通用能力:操作节点引擎、模板匹配器、OCR 服务、事件总线、截图与输入控制器。换个游戏,这一层几乎可以原样复用。
第二层:业务逻辑(src/zzz_od/)。这里才是"懂绝区零"的部分。自动空洞、世界巡逻、防卫战、每日签到、咖啡店、邦布店……二十多个功能应用全部以插件形式挂载在底座上,每个应用就是一个独立的包。
第三层:数据与配置(assets/game_data/与config/)。游戏的所有"知识"被抽离成 YAML:每个界面的按钮位置、每个区域的文本、每张地图的道路掩码、每个角色的战斗策略,全部外部化配置,改配置就能适配新版本,不用动代码。
这种"底座—插件—数据"的分层,让新功能开发变成了"填配置 + 写节点"的组装游戏,而不是从零造轮子。接下来我们进入最精彩的部分:节点引擎。
让流程成为"图":状态机设计的三个关键决策
一个自动化流程,本质是"当前处于什么状态 → 根据感知结果 → 迁移到下一个状态"。绝区零一条龙把这句话翻译成了极简的代码模型,全部浓缩在两个装饰器里:
# src/one_dragon/base/operation/operation_node.py def operation_node( name: str, timeout_seconds: float = None, is_start_node: bool = False, node_max_retry_times: int = 3, screenshot_before_round: bool = True, ): # 把节点元数据附加到函数上,运行前统一收集# src/one_dragon/base/operation/operation_edge.py def node_from(from_name: str, success: bool = True, status: str = None, ignore_status: bool = True): # 声明"从哪个节点来",边会在构图阶段自动补全去向用真实代码看效果。这是自动空洞(迷失之地)入口流程的一段(src/zzz_od/application/hollow_zero/lost_void/lost_void_app.py):
@operation_node(name='初始化加载', is_start_node=True) def init_for_lost_void(self) -> OperationRoundResult: if self.run_record.is_finished_by_day: return self.round_success(LostVoidApp.STATUS_ENOUGH_TIMES) self.ctx.lost_void.init_before_run() return self.round_success(LostVoidApp.STATUS_AGAIN) @node_from(from_name='初始化加载', status=STATUS_AGAIN) @operation_node(name='识别初始画面') def check_initial_screen(self) -> OperationRoundResult: # 通过 find_and_click / OCR 判断当前在哪一屏 return self.round_success('可前往快捷手册') # 或其它状态 @node_from(from_name='识别初始画面', status='可前往快捷手册') @node_from(from_name='识别初始画面', status=Operation.STATUS_SCREEN_UNKNOWN) @node_from(from_name='识别初始画面', status='未识别初始画面') @operation_node(name='前往零号空洞-入口') def tp_to_lost_void(self) -> OperationRoundResult: ...这个设计里有三个值得玩味的决策:
决策一:节点即函数,边即装饰器。每个节点就是类里的一个方法,@operation_node把节点元数据(名称、超时、重试次数)挂在函数上;@node_from则声明"我这个节点从哪个节点来"。运行时引擎用inspect扫描类方法,自动收集节点和边,组装成图——代码写出来就是流程图本身,可读性拉满。
决策二:节点不"调用"下一个节点,只"返回状态"。节点之间没有直接调用关系,每个节点只管返回round_success('某个状态')或round_retry(...),至于下一步去哪,由引擎根据边的匹配规则决定。这让节点高度内聚:改一个节点,不需要动它的所有邻居。
决策三:边支持"状态过滤"与"兜底"。从上面的代码能看到,同一个节点可以发出多条边,分别匹配不同的返回状态;ignore_status=True的边则作为兜底,在所有精确匹配都落空时兜住流程,避免死锁。这是应对游戏"画面千变万化"的关键保险。
引擎的呼吸:主循环、轮次结果与边匹配
图建好了,谁来跑?Operation.execute()是一个极其清醒的主循环(src/one_dragon/base/operation/operation.py):
while True: if self.timeout_seconds != -1 and self.operation_usage_time >= self.timeout_seconds: op_result = self.op_fail(Operation.STATUS_TIMEOUT) # 整体超时保护 break if self.ctx.run_context.is_context_stop: op_result = self.op_fail('人工结束') # 用户随时可叫停 break round_result = self._execute_one_round() # 执行当前节点一轮 if round_result.result == OperationRoundResultEnum.RETRY: self.node_retry_times += 1 # 单节点重试计数 if self.node_retry_times <= self.node_max_retry_times: continue if round_result.result == OperationRoundResultEnum.WAIT: continue # 等待 = 原地再拍一张 next_node = self._get_next_node(round_result) # 按状态找下一条边 if next_node is None: # 没有后继 = 流程结束 break self._current_node = next_node每一轮执行,节点方法返回四种结果之一:SUCCESS(进入下一条边)、FAIL(走失败边或结束)、RETRY(本节点重试 N 次)、WAIT(等一帧再判断)。_get_next_node则遍历当前节点的出边,按success匹配 +status精确匹配 +ignore_status兜底的优先级选出下一个节点。
这个循环还内置了三层防护,值得单独强调:
- 逐节点超时与重试:一个节点可以设置自己的超时秒数和最大重试次数(比如示例里"前往迷失之地-入口"设了 20 次重试),不怕偶发卡顿。
- 整体超时与人工干预:整个操作有总时长上限,用户暂停、停止随时生效,绝不无限挂机。
- 游戏窗口守护:构图时会自动在起始节点前插入"检测游戏窗口 → 打开并进入游戏"两个前置节点(
_add_check_game_node),游戏没开,先把你送进游戏再说。很多"无人值守"场景的翻车,其实都栽在这一步——这个框架替你兜住了。
一双看得见的手:三级识别与配置驱动的"眼睛"
状态机的每一步决策都依赖感知,而感知靠的是三条并行的识别通道:
- 模板匹配:对"按钮、图标、血条"这类固定 UI 元素,用模板图像做匹配,毫秒级出结果,是高频路径的主力。
- OCR 识别:对"挑战、确认、体力不足"这类动态文本,用内置的 ONNX 运行时 OCR 引擎读取,并带区域缓存,避免重复计算。
- YOLO 目标检测:对"敌人姿态、连携条、BOSS 血线"这类复杂视觉场景,用目标检测模型定位。
三条通道的分工耐人寻味:凡是稳定的东西用模板,凡是变化的东西用 OCR,凡是模糊的东西用 YOLO。但真正的设计精华,是把"眼睛看哪里"也配置化了——assets/game_data/screen_info/下每个界面一个 YAML,声明各个按钮的区域坐标;assets/game_data/world_patrol/lemnian_hollow/下每张地图配有道路掩码图,导航时先识别小地图再对照掩码判断可走路径。
这意味着:游戏 UI 改版,通常只需更新 YAML 与图片模板,框架代码毫发无损。版本适配成本被压缩到一个不可思议的量级。
让组件彼此说话:事件总线与上下文
状态机解决的是"一个流程怎么走",但一个框架里同时跑着战斗、巡逻、推送、GUI 多个子系统,它们之间怎么协作?绝区零一条龙的答案是**上下文(Context)+ 事件总线(EventBus)**的松耦合组合。
上下文(OneDragonContext)是全局单例的"服务容器",截图控制器、匹配器、OCR、配置、运行记录全都挂在这里,任何节点都能通过self.ctx取到。而事件总线(src/one_dragon/base/operation/context_event_bus.py)实现标准的发布—订阅:
class ContextEventBus: def dispatch_event(self, event_id: str, event_obj: Any = None): if event_id not in self.callbacks: return for callback in self.callbacks[event_id]: future = _od_event_bus_executor.submit(callback, ContextEventItem(event_id, event_obj)) future.add_done_callback(thread_utils.handle_future_result)注意ThreadPoolExecutor.submit——事件回调被丢进线程池异步执行,发事件的人不阻塞。操作引擎靠它监听"暂停/恢复",GUI 靠它刷新状态,异常检测靠它广播告警。组件之间不知道彼此存在,只认事件 ID,这正是"加一个功能不动旧代码"的底气所在。
实战走读:一次自动空洞,整条链路如何协同
把上面的设计串起来,看一次真实的自动空洞(迷失之地)运行,你会理解每一层在干什么:
- 入口判断:
识别初始画面节点截屏,先用区域查找判断"挑战-确认"按钮,再用 OCR 判断当前在哪个界面(大世界/快捷手册/副本画面),返回不同状态。 - 路线分流:返回"可前往快捷手册"就走手册传送链,返回"可前往副本画面"就直接进副本链,不同状态走不同边——这就是状态机对"分叉"的优雅处理。
- 循环推进:进入副本后,节点在"识别悬赏委托完成进度"处检查每日次数,够了直接
STATUS_ENOUGH_TIMES收工;否则继续挑战流程,战斗阶段由自动战斗应用接管,配合闪避检测与技能循环。 - 异常兜底:任何一个环节"找不到目标",节点返回
RETRY,重试 N 次仍失败则走失败边,或由全局超时兜底退出,绝不死循环。
值得一提的是,整套引擎自带运行轨迹可视化:每个节点从哪来、返回什么状态、耗时多少毫秒,都通过 overlay 调试总线实时推送。用户能在界面上看到"初始化加载 → 识别初始画面 → 前往零号空洞-入口"的完整流转轨迹。自动化流程不再是黑盒,而是可以观察、可以回放、可以定位的透明管道——这一点对排障体验的提升,怎么强调都不过分。
冷静的审视:它做到了什么,还差什么
回到开头的翻车现场。绝区零一条龙给出的解法,本质上是把自动化从"指令序列"升级为"感知—决策—执行的闭环状态机",并配套了三件利器:配置驱动的数据层(低成本适配版本)、事件总线的松耦合(低成本扩展功能)、轨迹可视化的调试层(低成本定位问题)。对于想研究游戏自动化架构、或想给自家工具套上"会看图、会决策"能力的开发者,它是一份极好的活教材。
当然,它也有清醒的边界:
- 强绑定 Windows 生态:控制器基于窗口句柄与模拟输入,跨平台(Linux/macOS)需要重写控制器层。
- 配置工作量大:新界面、新玩法需要采集模板、标注区域、写道路掩码,数据生产本身有成本。
- 对画面稳定性敏感:高分辨率缩放、滤镜、非默认画质都可能影响识别率,需要用户保持环境一致。
如果让我给改进方向提三条建议:一是将模板与区域配置做可视化采集工具,把"写 YAML"变成"框选即生成";二是引入强化学习做技能循环自适应,让战斗策略从配置驱动升级为数据驱动;三是沉淀一套通用"界面状态识别"抽象层,让底座真正脱离绝区零,成为通用的游戏自动化内核。
毕竟,这套框架最迷人的地方,不是"它自动化了哪款游戏",而是它证明了:把流程画成图、把感知做成数据、把协作交给事件——复杂系统的自动化,可以优雅得像个艺术品。
【免费下载链接】ZenlessZoneZero-OneDragon绝区零 一条龙 | 全自动 | 自动闪避 | 自动每日 | 自动空洞 | 支持手柄项目地址: https://gitcode.com/gh_mirrors/ze/ZenlessZoneZero-OneDragon
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考