Skip to content

ArenaPro UI 索引使用指南

还在为“路径地狱”抓狂?

你在 UI 树里找节点,路径层级深、命名不统一、改了结构就全崩。每次都是:

  • “要拿到 content1,每次都得拼 blackground/.../box/list/0/... 这种长路径。”
  • “设计把 panel 挪进了 container,结果我所有字符串路径都要跟着改一遍。”
  • “我只想直接拿到元素引用,不想维护深层级的字符串路径。”

从 V1.4.0 开始,ArenaPro 提供了“UI 索引”能力——像看“地图索引”一样,通过“屏幕名 + 节点名”直达目标元素,不再被路径困住。

示例

ts
// 过去(路径地狱):任何结构变化都要手改字符串路径
const el = UiScreen.getAllScreen()
  .find((e) => e.name === "blackground")
  .findChildByName("blackground")
  .findChildByName("root")
  .findChildByName("container")
  .findChildByName("panel")
  .findChildByName("box")
  .findChildByName("unit")
  .findChildByName("list")
  .findChildByName("0")
  .findChildByName("content")
  .findChildByName("content1");
console.log(el?.name);

// 现在(UI 索引):按“屏幕名 + 节点名”强类型直达
const idx = find("blackground");
console.log(idx?.uiText_content1.name); // 强类型、自动补全、结构调整后只需重新同步索引

UI 索引能带来什么?

UI 索引是对 UI 树的“强类型投影”。它会为每个屏幕生成一个索引类,并提供统一入口来查找索引实例:

你将获得…它意味着…
🔎 强类型直达元素通过属性访问 UI 元素,IDE 自动补全、类型可见。
🧭 脱离路径依赖不再手写字符串路径,结构调整也不必批量改路径字符串。
⚡ 首次预热,后续快构造时按路径表一次性预热缓存,后续访问无需再查找。
🧩 命名冲突消解自动采用末级名/回退全路径/再追加序号,最大化获得“好用的属性名”。
✅ 类型即规范不存在的屏幕在类型上直接是 never,问题在编译期暴露;运行时缺失返回 undefined

核心优势:UI 索引由插件自动生成和维护。你只需按“屏幕名”获取索引实例,剩下的交给类型系统与缓存机制。

示例

ts
import find from "@client/UiIndex";
const idx = find("home");

// 1) 强类型直达元素(IDE 自动补全)
idx?.uiText_title.text = "Hello";

// 2) findBy:按类型/谓词检索,el 自动收窄
const imgs = idx?.findBy("UiImage", (meta, el) => el?.visible === true);
const buttons = idx?.findBy((meta) => meta.name.includes("btn"));

如何获取并使用

生成索引文件

  • 触发“同步地图资源”(例如按下 Alt+Y)或相关命令后,插件会扫描 UI 树并自动生成索引文件。
  • 生成目录结构如下(自动生成,勿手动修改):
client/UiIndex/
  ├─ index.ts                // 入口,默认导出 find()
  ├─ ClientUIWindow.ts       // 基类(缓存、按路径查找、预热逻辑 + 实用方法)
  └─ screens/
      ├─ UiIndex_home.ts     // 示例:home 屏幕索引类
      └─ UiIndex_xxx.ts      // 其它屏幕索引类

若你看到导入路径为 @client/UiIndex,说明已在项目中配置了该别名,通常可直接使用该导入方式。

快速上手

ts
import find from "@client/UiIndex";

// 1) 通过屏幕名获取索引实例(强类型)
const idx = find("blackground");
if (idx) {
  // 2) 直接通过强类型属性访问 UI 元素
  // 实际路径:blackground/box/unit/list/0/content1
  idx.uiText_content1; // 只需要写 uiText_content1,就可以访问到
  idx.uiImage_logo; // 同理
}

要点:

  • find(name) 的返回类型由 name 决定:
    • 在类型上存在的屏幕名 -> 返回该屏幕对应的 UiIndex_xxx 实例类型。
    • 不存在的屏幕名 -> 返回类型为 never(编译期立即暴露问题)。
  • 运行时如果确实未找到该屏幕,会返回 undefined,因此示例里有判空。
  • 性能:同一屏幕名的 find(name) 会被缓存,后续重复调用直接复用实例,不会重复构建。

常用操作清单

  • 获取索引实例const idx = find("home"),并判空处理。
  • 访问元素idx.uiText_titleidx.uiImage_logo 等属性即为元素引用。
  • 查看真实路径:打开对应 UiIndex_<Screen>.ts,查阅 static PATHS 或 getter 注释。
  • 结构改动后:重新同步资源以更新索引,避免手写路径带来的维护成本。

UI 数据

  • 统一元信息 META(强类型)

    • 每个屏幕类都生成 static readonly META,与 PATHS 一一对应。
    • META[i].path 不再重复字符串,改为引用 PATHS[i],单一来源更稳。
    • META[i].type 收紧为联合类型:'UiBox' | 'UiImage' | 'UiInput' | 'UiScrollBox' | 'UiText'
  • findBy 下沉到基类并增强类型

    • 现位于 ClientUIWindow,所有屏幕类直接继承。
    • 支持两种调用方式:
      • findBy(predicate):按谓词过滤,el 类型为 UiElement | undefined
      • findBy(kind, predicate?):先按类型过滤,el 会随 kind 自动收窄为对应类型(例如 UiImage | undefined)。
    • 返回值:有命中 -> 返回命中数组;无命中 -> undefined
    • 示例:
      ts
      const texts = idx?.findBy("UiText");
      const imgs = idx?.findBy("UiImage", (meta, el) => el?.visible === true);
      const hits = idx?.findBy((meta, el) => meta.name.includes("btn"));
  • 实例方法:getPaths / getMeta

    • getPaths(): ReadonlyArray<string> 返回当前屏幕的 PATHS
    • getMeta(): ReadonlyArray<{ path: string; type: UiKind; name: string }> 返回 META(便于调试/可视化)。

API 参考

1) 默认导出:find(screenName)

  • 作用:通过屏幕名获取该屏幕对应的 UI 索引实例。
  • 类型(简化示意):
    ts
    export default function find<Name extends keyof typeof __UiIndexCtorMap>(
      screenName: Name
    ): InstanceType<(typeof __UiIndexCtorMap)[Name]> | undefined;
  • 运行时返回:UiIndex_xxx 实例或 undefined(屏幕不存在)。
  • 典型用法:
    ts
    const idx = find("home");
    if (idx) {
      idx.uiText_title;
    }

2) 屏幕索引类:UiIndex_<Screen>

  • 每个屏幕对应一个类,继承自 ClientUIWindow
  • 主要成员:
    • static readonly PATHS: readonly string[]:该屏幕下需缓存的完整路径表。
    • static readonly META: readonly { path: (typeof PATHS)[number]; type: UiKind; name: string }[]
    • 强类型 getters:例如 get uiText_title(): UiText(从缓存中按 PATHS[i] 读取)。

3) 基类:ClientUIWindow

  • 职责:
    • 维护以完整路径为键的缓存表 __cache
    • 提供 getByPath(path) 根据完整路径查找元素。
    • 构造时按 PATHS 进行预热,将所有路径映射到缓存。
  • 关键方法(新增与调整):
    • getPaths(): ReadonlyArray<string>
    • getMeta(): ReadonlyArray<{ path: string; type: UiKind; name: string }>
    • findBy(kind, predicate?) / findBy(predicate):带类型收窄与空返回 undefined 的过滤。

类型别名:type UiKind = 'UiBox' | 'UiImage' | 'UiInput' | 'UiScrollBox' | 'UiText'

命名规则

  • 属性名前缀与类型绑定:
    • UiText -> uiText_...
    • UiImage -> uiImage_...
    • UiBox -> uiBox_...
    • UiScrollBox -> uiScrollBox_...
    • UiInput -> uiInput_...
  • 基础命名使用“路径最后一段名”;若冲突,则回退为“完整路径”;若仍冲突,自动追加序号 _2/_3/...
  • 支持中文/Unicode,非法字符将被替换为下划线,并确保首字符合法。

uiIndexPrefix 配置与行为

  • 配置位置dao3.config.json -> file.typescript.client.uiIndexPrefix
  • 类型与默认值:字符串,默认空字符串 ""(不启用过滤)
  • 功能说明
    • 若配置了前缀,例如 "U_",则仅收集名称以该前缀开头的 UI 节点来生成索引。
    • 生成的 getter 名会自动剥离该前缀,避免出现 uiText_U_title 这种冗余命名。
    • 未配置或为空字符串时,对所有节点生成索引。

示例配置:

json
{
  "file": {
    "typescript": {
      "client": {
        "uiIndexPrefix": "U_"
      }
    }
  }
}

示例效果:

  • 节点实际名称:U_titleU_logodesc
  • 生成结果:
    • U_title -> getter: uiText_title
    • U_logo -> getter: uiImage_logo
    • desc -> 不生成(未匹配前缀)
  • PATHS 中保留真实名称(如 .../U_title),仅 getter 名剥离前缀,运行时查找不受影响。

注意事项与最佳实践

  • 仅支持单个前缀字符串。若需多个前缀或正则匹配,请提需求,可扩展为数组/正则。
  • 建议统一给“需要暴露为索引”的节点加上前缀,以减少噪声和属性冲突。
  • 修改前缀后请重新同步资源,以更新索引文件。
  • 命名冲突处理仍生效:末级名冲突 -> 回退全路径 -> 再追加序号。

神岛实验室