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_title、idx.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_title、U_logo、desc - 生成结果:
U_title-> getter:uiText_titleU_logo-> getter:uiImage_logodesc-> 不生成(未匹配前缀)
- PATHS 中保留真实名称(如
.../U_title),仅 getter 名剥离前缀,运行时查找不受影响。
注意事项与最佳实践
- 仅支持单个前缀字符串。若需多个前缀或正则匹配,请提需求,可扩展为数组/正则。
- 建议统一给“需要暴露为索引”的节点加上前缀,以减少噪声和属性冲突。
- 修改前缀后请重新同步资源,以更新索引文件。
- 命名冲突处理仍生效:末级名冲突 -> 回退全路径 -> 再追加序号。