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