拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

绝区零一条龙 Screen Scope 方案解析:screen 局部化作用域设计与插件注入实践

绝区零一条龙 Screen Scope 方案解析:screen 局部化作用域设计与插件注入实践
  • 桌面应用
  • RPA
  • 计算机视觉

【免费下载链接】ZenlessZoneZero-OneDragon

绝区零 一条龙 | 全自动 | 自动闪避 | 自动每日 | 自动空洞 | 支持手柄

项目地址:https://gitcode.com/gh_mirrors/ze/ZenlessZoneZero-OneDragon
点击查看免费下载

导读:本文以docs/develop/screen_scope_design.md方案为主体,完整讲解 ZenlessZoneZero-OneDragon(绝区零一条龙)中 screen 匹配开销过大、插件无法自定义画面、全局跳转缓慢三个问题的根因,以及通过app_id字段实现「全局 screen + 局部 screen」作用域管理的设计思路、API、迁移步骤与插件 screen 加载机制。读完你既能掌握如何给现有 screen YAML 添加app_id完成局部化迁移,也能理解 BFS 匹配优化与 Floyd 路由表为什么不受影响,还能复现插件通过screen_info/目录注入自建画面的完整链路。

1. 背景与问题:为什么 screen 需要「作用域」

在绝区零一条龙项目中,画面(screen)是自动化流程识别的基础单元:每个 screen 由若干区域(area)组成,区域可以是 OCR 文本区域或模板匹配区域,id_mark区域全部命中才算匹配到该画面。历史实现中所有 screen 都注册在全局唯一的ScreenContext中,由此引出三个核心问题(设计方案原文):

问题原因影响
插件无法管理 screenscreen 只从 assets/game_data/screen_info/ 加载,无注入点第三方插件无法定义画面
全局污染所有 screen 同一命名空间,BFS 搜索和路由计算混杂不相关 screen识别变慢
全局跳转慢round_by_goto_screen无范围限制,BFS 可能检查大量无关 screen每轮匹配耗时高

从源码看,匹配开销的真实来源在 screen_utils.py:get_match_screen_name_from_last从当前/上一个画面出发做 BFS 扩散,每个节点都要走is_target_screen→find_area_in_screen,而 find_area_in_screen 内部要么调用 OCR 服务做文本识别(lcs_percent阈值默认 0.5),要么调用模板匹配(template_match_threshold默认 0.7)。画面越多,每轮截图被 OCR/模板匹配的次数越多,最坏情况与 screen 总数成正比。

2. 设计方案:全局 screen 与局部 screen

方案的核心概念是用app_id把 screen 划分为两类(设计图):

┌─────────────────────────────────────────────┐ │ ScreenContext │ │ ┌────────────────────┐ ┌───────────────┐ │ │ │ 全局 screen │ │ 局部 screen │ │ │ │ (app_id 为空) │ │ (app_id 非空) │ │ │ │ 菜单 │ │ 迷失之地-入口 │ │ │ │ 大世界-普通 │ │ 迷失之地-大世界│ │ │ │ 快捷手册-训练 │ │ 零号空洞-入口 │ │ │ │ ... │ │ ... │ │ │ └────────────────────┘ └───────────────┘ │ │ 活跃范围 = 全局 ∪ 当前应用的局部 │ └─────────────────────────────────────────────┘
  • 全局 screen:YAML 中app_id为空。始终参与匹配,如菜单、大世界、快捷手册等导航骨架。
  • 局部 screen:YAML 中app_id非空。仅在对应应用运行时参与匹配。

作用域由三个时机自动维护,无需在代码中手工维护字符串列表:

  1. reload()后自动计算全局集合:_global_screen_names = {无 app_id 的 screen};
  2. 应用启动时enter_scope(app_id),自动收集app_id匹配的局部 screen;
  3. 应用停止时exit_scope(),恢复全量匹配。

这三步在 screen_loader.py 有对应实现:reload()末尾即重新计算_global_screen_names。

3. 数据模型变更:ScreenInfo 新增app_id字段

3.1 字段定义与解析

ScreenInfo 在构造时通过data.get('app_id', '')读取该字段,空字符串表示全局 screen,默认值保证完全向后兼容。to_dict()仅在app_id非空时才写入该键,因此旧配置文件的读取与写回不受影响。

3.2 YAML 配置示例

# 全局 screen(不设 app_id 或为空) - screen_id: menu screen_name: 菜单 pc_alt: false area_list: ... # 局部 screen(设置 app_id) - screen_id: lost_void_entry screen_name: 迷失之地-入口 app_id: lost_void # ← 与 Application.app_id 一致 pc_alt: false area_list: ...

目前主工程已落地的合并配置 assets/game_data/screen_info/_od_merged.yml 中已有 47 处app_id声明,覆盖world_patrol、city_fund、coffee、lost_void、suibian_temple、shiyu_defense等应用,与 screen_scope_rollout.md 记录的迁移清单一致。

4. API 设计:ScreenContext 的 Scope 管理与 Application 生命周期

4.1 ScreenContext 新 API

方案文档给出的接口,在源码中全部有对应实现(screen_loader.py):

class ScreenContext: # ---- Screen Scope 管理 ---- def enter_scope(self, app_id: str) -> None: """进入应用 scope 活跃范围 = 全局 screen + 该 app_id 的局部 screen 仅当存在匹配的局部 screen 时才启用 scope """ def exit_scope(self) -> None: """退出应用 scope,恢复全量匹配""" @property def active_screen_names(self) -> set[str] | None: """当前活跃的 screen 名称集合。None 表示全部活跃""" @property def active_screen_info_list(self) -> list[ScreenInfo]: """当前活跃的 ScreenInfo 列表""" def is_screen_active(self, screen_name: str) -> bool: """判断某个 screen 是否在活跃范围内"""

关键实现细节:

  • enter_scope内部先调用exit_scope()复位,然后做两次防御性判断:若_global_screen_names为空(所有 screen 都没设app_id)或该应用没有匹配的局部 screen,则直接返回、不启用 scope。这保证了「无局部 screen 的应用行为不变」。
  • active_screen_names返回set[str] | None:None表示未启用 scope、全部活跃;启用时返回全局集合 ∪ 局部集合。
  • active_screen_info_list在未启用 scope 时直接返回完整screen_info_list,启用时按名称过滤。

4.2 Application 生命周期自动集成

Application.execute 是实际落地的挂载点,且比设计文档更健壮——使用try/finally保证异常路径也会退出 scope:

def execute(self) -> OperationResult: self.ctx.screen_loader.enter_scope(self.app_id) try: return Operation.execute(self) finally: self.ctx.screen_loader.exit_scope()

应用无需额外代码,scope 通过app_id+ YAMLapp_id字段自动匹配。

5. 匹配优化:BFS 搜索范围收窄

5.1 优化策略

get_match_screen_name_from_last中的 BFS 现在适配活跃范围(screen_utils.py):

  • 非活跃 screen 跳过匹配:active_names is not None and current_screen_name not in active_names时直接continue,不执行is_target_screen,从而省掉 OCR/模板匹配开销;
  • 非活跃 screen 仍展开邻居:跳过匹配的同时遍历其goto_list继续入队,保持路由图连通性,确保 BFS 最终能到达活跃 screen;
  • fallback 遍历仅搜索活跃 screen:BFS 结束后兜底遍历active_screen_info_list,而不是全量列表。

5.2 效果估算

场景改动前改动后
迷失之地运行时 BFS 匹配范围~70 screen~30 screen(全局 ~16 + 局部 ~14)
每轮 OCR/模板匹配次数(最坏)~70 次~30 次
Floyd 路由计算O(70³) ≈ 34万次不变(全局路由表共用)

注意表格第三行是设计文档的关键约束:路由表不随 scope 变化。init_screen_route()在 reload() 时对全量 screen 执行一次 Floyd 算法(O(n³)三层循环),scope 只影响识别阶段get_match_screen_name的搜索范围,round_by_goto_screen仍使用全局路由表查路径。这意味着局部化不会破坏任意两个 screen 之间的可达性计算。

6. 向后兼容性与迁移指南

6.1 兼容性矩阵

场景行为
所有 YAML 未设app_id(当前现状)全部为全局 →enter_scope无匹配 → 不启用 scope → 行为不变
部分 YAML 设了app_id该应用启用 scope,其他应用不受影响
应用本身无匹配 screenenter_scope跳过 → 行为不变

结论是零风险渐进迁移:全部不改 YAML 时与现在一模一样,改一个应用的 YAML 只影响该应用。

6.2 迁移步骤

步骤 1:给局部 screen 的 YAML 添加app_id。以迷失之地为例,给 14 个 screen 各加一行:

- screen_id: lost_void_entry screen_name: 迷失之地-入口 app_id: lost_void # ← 新增 pc_alt: false area_list: ...

步骤 2:完成。无需修改任何 Python 代码,Application.execute自动调用enter_scope(self.app_id),匹配到 YAML 中app_id: lost_void的 screen,scope 自动生效。

6.3 分层迁移范围(来自 rollout 文档)

配套的 screen_scope_rollout.md 给出了主工程assets/game_data/screen_info/的落地清单:

  • 保持全局:打开游戏、菜单、菜单-更多功能、画面-通用、通用-出战、大世界系列、地图、HDD、战斗画面系列、快捷手册系列、区域巡防、实战模拟室、专业挑战室、恶名狩猎、恢复电量、电玩店、拉面店、家政券。这些是基础导航、共享路由或跨应用复用入口。
  • 第一层(独立日常类):city_fund→丽都城募、email→邮件、intel_board→情报板、random_play→影像店营业、ridu_weekly→丽都周纪、scratch_card→报刊亭、trigrams_collection→卦象集录。
  • 第二层(路线链路/业务组):world_patrol→3D地图+绳网、coffee→咖啡店、commission_assistant→委托助手+钓鱼、drive_disc_dismantle→三个仓库 screen、life_on_line→真拿命验收、suibian_temple→随便观 7 个 screen。
  • 第三层(空洞/战斗域):lost_void→迷失之地 14 个 screen、withered_domain→零号空洞 4 个 screen、shiyu_defense→两个防卫战 screen。

6.4 回归重点

  • 加载默认_od_merged.yml与分文件加载from_separated_files=True时,app_id数量应一致(reload()的两个分支都从ScreenInfo(data)解析,天然共享同一字段语义);
  • HDD与菜单-更多功能必须仍保持全局;
  • 逐层验证enter_scope(app_id)后的活跃范围为全局 screen + 当前 app 局部 screen;
  • 优先 smoke test:email、drive_disc_dismantle、lost_void、withered_domain、suibian_temple、shiyu_defense。

7. 插件 Screen 加载:让第三方插件拥有自己的画面

7.1 加载机制

插件的 screen 通过OneDragonContext._load_plugin_screens()自动加载,实际调用链(one_dragon_context.py):

OneDragonContext.init() ├── register_application_factory() ← 扫描插件,填充 plugin_infos ├── screen_loader.reload() ← 加载主 screen YAML └── _load_plugin_screens() ← 遍历 plugin_infos → load_extra_screen_dir() └── 对每个插件的 screen_info/ 目录加载 YAML └── 未设 app_id 的 screen 自动使用插件的 app_id refresh_application_registration() ← 运行时刷新插件 ├── clear_applications + discover_factories ← 重新扫描 ├── screen_loader.reload() ← 重新加载主 YAML └── _load_plugin_screens() ← 重新加载插件 screen

核心函数 load_extra_screen_dir 的行为要点:

  • 仅遍历目录下.yml文件;非 dict 顶层结构会跳过并告警;
  • default_app_id仅在 YAML 未显式设置app_id时填充:if default_app_id and not data.get('app_id'): data['app_id'] = default_app_id;
  • screen_name 与 screen_id 冲突时跳过并输出警告日志;
  • 只要有新增,就重新执行init_screen_route()并重算_global_screen_names,保证插件 screen 立即参与路由与作用域计算。

7.2 插件目录结构

plugins/ my_plugin/ __init__.py my_plugin_const.py # APP_ID = 'my_plugin' my_plugin_factory.py my_plugin.py screen_info/ # ← 新增,放 screen YAML my_screen.yml

7.3 插件 Screen YAML 示例

# plugins/my_plugin/screen_info/my_screen.yml screen_id: my_plugin_main screen_name: 我的插件-主界面 # app_id 可省略,自动使用插件的 APP_ID pc_alt: false area_list: - area_name: 返回按钮 id_mark: true pc_rect: [82, 13, 150, 90] template_id: back template_sub_dir: menu goto_list: - 大世界-普通

这段配置展示了 area 的常用字段:id_mark: true使该区域参与画面精准判定(is_target_screen 要求所有id_mark区域全部命中才匹配成功)、pc_rect定义 PC 端矩形区域、template_id/template_sub_dir指向 assets/template/ 下的模板、goto_list声明跳转边用于路由图。

7.4 冲突处理与命名空间隔离

  • 插件 screen_name 与主 YAML 或其他插件冲突时,跳过并输出警告日志;
  • default_app_id仅在 YAML 未显式设置app_id时使用;
  • 插件 screen 加载到ScreenContext后,通过app_id自动归入局部命名空间,不污染其他应用。

插件 screen 同样支持save_screen/delete_screen管理:save_screen会通过_extra_screen_file_path_map判断来源,将修改写回插件自己的 YAML 文件而非合并文件(screen_loader.py),与主工程 screen 的管理路径完全隔离。

8. 文件变更清单与阅读索引

文件变更
src/one_dragon/base/screen/screen_info.pyScreenInfo新增app_id字段
src/one_dragon/base/screen/screen_loader.pyScreenContext新增 scope 管理 API 和load_extra_screen_dir
src/one_dragon/base/screen/screen_utils.pyBFS 匹配适配活跃范围
src/one_dragon/base/operation/application_base.pyApplication生命周期自动 enter/exit scope
src/one_dragon/base/operation/one_dragon_context.py新增_load_plugin_screens(),init 和 refresh 时加载插件 screen

建议的阅读路径:先读本设计文档与 分层迁移清单 把握全貌,再对照 ScreenContext 的 scope 三件套(enter_scope/exit_scope/active_screen_names)、BFS 匹配 的活跃范围跳过逻辑,最后通过 Application.execute 与 插件加载 确认整个闭环。实际 YAML 落地效果可直接查看 assets/game_data/screen_info/ 中已带app_id的 screen 文件。

  • 桌面应用
  • RPA
  • 计算机视觉

【免费下载链接】ZenlessZoneZero-OneDragon

绝区零 一条龙 | 全自动 | 自动闪避 | 自动每日 | 自动空洞 | 支持手柄

项目地址:https://gitcode.com/gh_mirrors/ze/ZenlessZoneZero-OneDragon
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表