docs(hero-config): add hero configuration UI design documentation

新增英雄配置UI的完整设计方案文档,包含架构、数据流、JSON存储规范、生成器逻辑、UI布局与交互细节,以及实施步骤和校验规则。
This commit is contained in:
panFD
2026-08-18 22:45:20 +08:00
parent a2a52f20f2
commit ca7642b36e
3 changed files with 264 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
{
"ver": "1.0.1",
"importer": "text",
"imported": true,
"uuid": "82be1e57-4be9-4315-ba27-0c2789fbd9f2",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,242 @@
# 英雄配置 UI 设计方案
> 为编辑器扩展 `extensions/hero_setup` 新增「英雄配置」功能的设计文档。
> 目标:以 [SkillSet.ts](./SkillSet.ts) / [BuffSet.ts](./BuffSet.ts) / FieldSkillSet 为只读底座,通过 UI 配置英雄(新增 + 编辑现有),落地 [英雄设计方案.md](./英雄设计方案.md) 与 [英雄设计指引.md](./英雄设计指引.md) 的能力池范式。
>
> **已确认决策**
> - lv 档位按文档完整支持 **lv2/3/5/7/9**(数据先行),超过运行时 `HERO_MAX_LV=6` 的档给黄色警告不阻止
> - 落地方式 **JSON 外部化**JSON 为 source of truth单向生成 heroSet.ts 数据段
> - 功能范围:**新增英雄 + 编辑现有英雄**(不做删除)
> - 校验uuid 存在性 + 段位规则 + lv 升序 + t_num≥1 + 形态硬约束
> - 生成器边界:**标记注释定界**(分歧裁定 1
> - 怪物段:**全量迁移进 JSON、UI 只读**(分歧裁定 2round-trip 门保真)
---
## 一、总体架构与数据流
```
heroSet.data.jsonsource of truthgit 跟踪)
│ ① 扩展保存时生成(单向 JSON→TS
heroSet.ts 数据段HeroInfo 表 + HeroList = 生成产物,其余 ~500 行类型/函数原样保留)
│ ② 下游零改动
CardPoolList 自动派生 eval-heroes 强度评估 游戏运行时
```
```
面板Vue3同面板双 tab强度评估英雄配置
│ Editor.Message.request('hero_setup', ...)
主进程 methods ──spawn──► scripts/heroes-cli.ts复用 tsx + tsconfig.eval.json + mock-cc 链路)
│ --cmd=query-base | export | validate
├─ 读 JSON / 校验
└─ save备份 → 写 TS → 语法自检 → round-trip 比对 → 写 JSON → refresh-asset
```
关键原则:
- **运行时零改动**游戏代码、CardSet 派生链、eval-heroes、下游 49 个 import 方均不感知本功能存在
- **同步方向单向**JSON → heroSet.tsTS 数据段永远为生成产物;不一致时弹冲突对话框(含 diff提供一次性「以 TS 反向导入」通道,禁止静默覆盖
- **CardPoolList 无需手动同步**:新英雄写入 HeroInfo + HeroList 后CardSet 模块加载期自动生成卡池条目
## 二、JSON 存储
### 2.1 位置
`assets/script/game/common/config/heroSet.data.json`
| 候选 | 结论 | 理由 |
|---|---|---|
| assets/script/.../heroSet.data.json | **采用** | 与 heroSet.ts 同目录、随 git 入库 |
| assets/resources/ 下 | 否决 | resources 目录全量打包进游戏 |
| extensions/hero_setup/ 内 | 否决 | `.gitignore` 整体忽略 extensions/*source of truth 会丢版本历史 |
| 项目根 config/ | 备选 | 零 asset-db 干扰,但离代码远、易被误删 |
JSON 不被任何运行时代码 importCocos 构建不会打包它asset-db 会为其生成 .meta代价可接受。
### 2.2 Schema 要点
```typescript
interface HeroConfigFile {
schema_version: 1; // 结构性变更时递增,读取端按版本迁移
generated_at: string; // ISO 时间(人读)
gen_hash: string; // 上次生成 TS 数据段指纹,用于漂移检测
heroes: Record<number, HeroJson>; // 英雄5001~5499 段)
monsters: Record<number, HeroJson>; // 怪物6001~6199 段UI 只读)
}
```
`heroInfo` 接口逐字段对齐,仅三处转换:
| 转换 | JSON 存储 | 生成还原 |
|---|---|---|
| 枚举 | 语义字符串 `"type": "Melee"` | `HType.Melee`(生成端持名称→成员映射表) |
| 基准属性(英雄) | `{ ref: "HERO_ROLE_BASE", role: "Tank", delta: 1200 }` | `HERO_ROLE_BASE[HRole.Tank].hp + 1200`delta 0 省略 `+ 0` |
| 基准属性(怪物) | 绝对值 `hp: 3000` | 直接数值 |
| 攻速 cd | `atk_speed: "Slow"` | `AtkSpeedSet[AtkSpeedLv.Slow].cd` |
设计理由:
- 枚举存字符串diff 友好、手改安全、枚举成员重排不破坏数据
- 属性存「基准引用+增量」:保住 `HERO_ROLE_BASE` 模板调参全局传播语义heroSet.ts L145 注释意图);**禁止 JSON 出现已求值裸数值**,杜绝基准值改动后历史英雄静默漂移
- UI 端用底座数据实时计算绝对值预览(基准+增量双输入)
## 三、heroSet.ts 生成器(核心)
### 3.1 边界定位:标记注释定界
- 首次迁移(`--cmd=export`)时在 `export const HeroInfo` 前插入 `// @hero-setup:region-begin`,在 `HeroList` 数组 `];` 后插入 `// @hero-setup:region-end`
- 此后每次生成:读全文 → 定位两标记 → `前缀 + 生成段 + 后缀` 拼接
- **双保险**
- 标记被手删 → 拒绝生成并提示走「重新导入」流程,绝不退化为全文件重写
- 保存前重算当前 TS 数据段指纹与 JSON 的 `gen_hash` 比对,不一致 → 弹冲突对话框(展示 diff二选一以 TS 反向导入一次 / 以 JSON 覆盖)
### 3.2 自研序列化器(非 JSON.stringify
格式保真清单与现文件逐项对齐round-trip 门验证):
| 不变量 | 依赖方 |
|---|---|
| 4 空格缩进、双引号、行尾无分号风格跟随原文件 | 全部 diff 工具 |
| 条目头字段单行紧凑(`uuid, name, path, fac, card_lv, lv, type, grow_type, role, hp, ap, def` 一行),触发块换行展开 | 现有阅读习惯 |
| 按 uuid 段位插入注释块(`// ===== card_lv=55401-5499=====` | 段位规则 |
| **档位数组 lv 严格升序**lv_entries / LvTriggerEntry[] / field / revive / ap_bonus / hp_bonus | `HeroSkillDesc.ts:384``filter(e=>e.lv<=lv).pop()` |
| 数字键无引号、保持插入序 | Record 索引 |
| `ccd: 0` 占位字段不丢HSkillInfo 必填) | Hero.ts 加载 |
| 字段顺序稳定skills → atked/atking → dead → fstart → field → revive → info | 减少 diff 噪音 |
| HeroList 按 card_lv 分组升序 | CardSet 卡池派生顺序 |
| `info: ""` 收尾、`path/icon/info` 字符串原样不转义变换 | 文本保真 |
| **文件头 BOM 原样保留**(读 utf-8 后首字符检测写回) | 避免整文件 diff |
### 3.3 Round-trip 门
每次保存后执行「重新生成 → 与落盘 TS 逐字节比对」,不等则回滚并报错。初始迁移同样以此门作为验收标准。
## 四、扩展模块与消息协议
### 4.1 新增文件
| 文件 | 职责 |
|---|---|
| `scripts/heroes-cli.ts` | Node 子进程脚本,承载 `--cmd=query-base / export / validate` 三命令;复用 `tsconfig.eval.json`paths: cc → mock-cc.ts直接 import SkillSet/BuffList/FieldSkillSet/heroSet |
| `source/heroes/generator.ts`(主进程侧) | JSON→TS 序列化器 + round-trip 校验(可被 heroes-cli 复用) |
| `source/panels/default/components/HeroConfig.ts` + `static/template/vue/hero-config.html` | 配置 UI 组件 |
### 4.2 消息协议
| 消息 | 方向 | 入参 | 出参 |
|---|---|---|---|
| `query-base` | 面板→主进程 | | 底座快照SkillSet/BuffList/FieldSkillSet 元数据 + 枚举名表;按文件 mtime 缓存) |
| `read-config` | 面板→主进程 | | `{ file: HeroConfigFile, drift?: boolean }`JSON 不存在则先跑 `--cmd=export` |
| `validate` | 面板→主进程 | HeroConfigFile | 校验报告error[] / warning[] |
| `save-config` | 面板→主进程 | HeroConfigFile | `{ ok, report }` |
| `config-saved` | 主进程→面板广播 | | 强度评估 tab 重算,形成「改→看」闭环 |
`package.json``contributions.messages` 补齐上述映射。
### 4.3 保存流水线(顺序固定)
```
校验通过 → 备份当前 heroSet.ts → 生成 TS 文本 → ts 语法自检createSourceFile 取 syntactic diagnostics
→ 写 TS临时文件 + rename 原子写)→ round-trip 逐字节比对 → 通过才写 JSON → asset-db refresh-asset
```
任一步失败:从备份恢复 TS、JSON 保持未写状态、返回错误报告。
## 五、校验规则分级
### 5.1 Error阻止保存
| 规则 | 说明 |
|---|---|
| uuid 段位匹配 | 新增时5001-5099=card1 / 5101-5199=card2 / 5201-5299=card3 / 5301-5399=card4 / 5401-5499=card5且不与现有重复编辑现有英雄 uuid 不可改) |
| skills/触发组 s_uuid 存在 | 必须在 SkillSet否则运行时静默不出手 |
| timed_buff_id 存在 | 必须在 BuffList 7001-7024 段 |
| field uuid 存在 | 必须在 FieldSkillSet 7001-7020 段(与 BuffList 同号段但互不相通) |
| t_num ≥ 1 | 0 会导致 `% 0 = NaN` 永不触发 |
| 档位 lv 升序不重复 | 保存时序列化器强制排序,重复档报错 |
| 形态硬约束 | tank/warrior→近战组普攻 6004、大招 6103~6106/6108assassin/射手→中距组6001~6003/6006~6008support/mage→远程组6009~6019、大招 6101/6102/6107/6109~6111辅助技能 6301/6302/6412/6501 不限形态 |
| 必填缺失 | name / path / type / role |
### 5.2 Warning确认后可存
| 规则 | 说明 |
|---|---|
| lv > 6 档位 | `HERO_MAX_LV=6` 封顶lv7/9 档当前永不生效(数据先行,运行时扩展上限后自动生效) |
| card_lv 1/2/3 配了 field | 应改走 ap_bonus/hp_bonus30/40/50% 补偿) |
| card_lv 4/5 的 lv9 未配 field | 按设计指引应有驻场光环 |
| infos 条数与实际档位不匹配 | 面板逐条展示依赖 |
## 六、UI 设计
### 6.1 布局
同面板default1024×600 dockable顶部 `ui-tab`:「强度评估|英雄配置」。三栏:
```
┌ [强度评估 | 英雄配置●] ────────────────────────── ●=有未保存改动 ┐
├────────────┬──────────────────────────────┬─────────┤
│ 🔍搜索 │ 锚点导航: 基础|技能|触发|光环|复活|加成|说明 │ 校验抽屉 │
│ ▾LV5 │ ①基础: uuid/name/path/card_lv/role/type │ ✕2 ⚠1 │
│ ●5401 刀刀│ hp/ap/def 基准+增量双输入(显示合计) │ 点击 │
│ ▾LV4 … │ ②技能: [普攻|大招] SkillPicker + 底座只读条 │ 定位 │
│ [+新建] │ + OverridesEditor + 档位矩阵 │ [保存] │
└────────────┴──────────────────────────────┴─────────┘
```
### 6.2 组件清单
| 组件 | 职责 | 数据源 | 校验 |
|---|---|---|---|
| HeroListView | card_lv 分组、搜索、dirty 圆点、新建入口 | heroSet.data.json | |
| BaseForm | 基础字段hp/ap/def 基准+增量双输入 | HERO_ROLE_BASE | uuid 段位/重复role→type 联动锁定 |
| SkillPicker | 自制搜索下拉选 SkillSet显示 `6101 火球 · 远程 · AOE` | SkillSet | 普攻限 6001~6019 按 IType 过滤;大招按形态过滤;辅助技能放行 |
| SkillBasePanel | 底座关键参数只读展示ap/cd/hit/IType/RType/DTType | SkillSet | |
| OverridesEditor | 分组编辑 overrides数值 ap/hit_count/num概率 crt/frz/stun击退 bckbuff 注入 timed_buff_id→联动 buff_value/buff_duration永久 buff_type召唤 call_hero | BuffList | timed_buff_id 存在性 |
| LvMatrix | lv 档位矩阵(技能 lv_entries / 触发档 / field / revive 复用变体):行=lv1/2/3/5/7/9、列=参数,可整行复制加档;**lv7/9 行黄色边框+⚠「运行时 HERO_MAX_LV=6 暂不生效」** | 当前英雄 | lv 升序不重复t_num≥1 |
| TriggerGroupEditor | s_uuid 卡片嵌套档位行(外层技能组、内层档位),保存时统一升序 | SkillSet | s_uuid 存在性 |
| InfosEditor | 9 档分级说明文本 | 当前英雄 | 条数与档位对照提示 |
| BonusEditor | ap_bonus/hp_bonus 档位lv+value | 当前英雄 | 仅 card_lv1/2/3 提示使用 |
| ValidationPanel | 错误/警告列表(区域·字段·消息),点击 scrollIntoView + 字段红框高亮;保存按钮 error>0 时禁用 | 校验器输出 | |
### 6.3 关键交互
- **ui- 组件与 Vue 混用**`isCustomElement` 已配置ui- 元素 `v-model` 不生效,须 `:value` + `@change`/`@confirm` 手动双向;属性用 kebab-case
- **自制组件**ui- 缺失):档位矩阵表格、可搜索 SkillPicker、BuffList 分组下拉、校验列表、确认 modal
- **新建向导**:单屏 modal——选 card_lv → role →(形态按映射自动锁定为只读徽章)→ 模板预填uuid 取段位内最小空位、职业基准属性(增量 0、按指引 2.6 能力组合范式推荐技能tank→6004+6301、support→6009+6302 等、lv3/5/7 空档骨架;提供「空白模板」选项
- **状态管理**dirty 深度 watch加载时存 snapshot 对比),列表项与 tab 标题打点;切换英雄/切 tab 弹确认(放弃/留下);底座加载沿用 spawn 异步 + loading 态
- **保存流程**error>0 → 保存禁用tooltip 显示错误数);仅 warning → 自制 modal 确认后执行保存流水线
## 七、失败恢复与安全
| 项 | 方案 |
|---|---|
| 备份 | `local/hero_setup/backup/heroSet.<HHmmss>.ts`local/ 已被 git 忽略),滚动保留 5 份;不用 git stash不污染用户暂存区 |
| 原子写 | 同目录临时文件 + renameWindows EPERM编辑器占用时 200ms 退避重试 3 次 |
| 语法自检 | `ts.createSourceFile` 取 syntactic diagnosticstypescript 已在依赖);语义级检查提示用户跑 `npx tsc --noEmit`,不阻塞 |
| 回滚 | 自检/round-trip 失败 → 从备份恢复原文件 → 返回错误报告JSON 保持未写 |
| asset-db 刷新 | 写后 `Editor.Message.request('asset-db', 'refresh-asset', 'db://assets/script/game/common/config/heroSet.ts')`;禁止「删文件+建新文件」式写法(触发 meta 重建) |
| 并发 | 面板内保存互斥in-flight promise 复用)+ 串行队列 |
## 八、初始迁移(一次性)
1. `--cmd=export`tsx import 现 heroSet.ts → 导出 `heroSet.data.json`26 英雄 + 14 怪物全量,怪物标记只读)
2. 从 JSON 重新生成 TS → **round-trip 逐字节比对必须通过**(迁移门)
3. 迁移门失败的处理:修序列化器直至通过(一次性成本);确实无法保真的字段(如怪物特有表达式)再评估局部文本透传回退
4. 迁移完成后 heroSet.ts 数据段即为生成产物,此后手改走「以 TS 反向导入」通道
## 九、实施阶段
| 阶段 | 内容 | 验收 |
|---|---|---|
| 1. 脚本核心 | heroes-cli.ts + JSON schema + 导入器 + 序列化生成器 + round-trip 门(纯 Node 可单测) | 迁移门逐字节通过 |
| 2. 主进程 | methods + 消息协议 + 备份/原子写/自检/刷新 | 手工消息调用保存成功、编辑器感知刷新 |
| 3. 面板骨架 | tab + 英雄列表 + 基础表单 + SkillPicker/OverridesEditor | 可加载、可编辑基础字段 |
| 4. 完整表单 | 触发组/光环/复活/infos/bonus + 校验抽屉 + 新建向导 | 校验规则全覆盖、按设计指引录入新英雄走通 |
| 5. 联调冒烟 | eval-heroes 验证加载链、形态约束用例、按指引落地 1 个 P0 英雄(紫电魔女 5301 | 强度评估 tab 正常评分 |
## 十、遗留事项
- heroSet.ts:297 注释「怪物5200段」与实际 6001/6101 段不符(幽灵注释),迁移时顺带修正
- UI 支持 lv7/9 档而 `HERO_MAX_LV=6`HeroSkillDesc.ts:321-331 会向玩家展示永远无法解锁的 Lv7/Lv9 文案——超 6 档仅预录入,运行时/文案侧需按 HERO_MAX_LV 裁剪(独立任务,不在本功能范围)
- 未来扩展 `HERO_MAX_LV` 至 9 时,需同步升级碎片表 `HERO_UPGRADE_NEED_FRAGMENTS`GameSet.ts:70

View File

@@ -0,0 +1,11 @@
{
"ver": "1.0.1",
"importer": "text",
"imported": true,
"uuid": "52cc7e16-38f3-4e62-8e90-fc34a9b4a622",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}