16 KiB
16 KiB
英雄配置 UI 设计方案
为编辑器扩展
extensions/hero_setup新增「英雄配置」功能的设计文档。 目标:以 SkillSet.ts / BuffSet.ts / FieldSkillSet 为只读底座,通过 UI 配置英雄(新增 + 编辑现有),落地 英雄设计方案.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 只读(分歧裁定 2,round-trip 门保真)
一、总体架构与数据流
heroSet.data.json(source of truth,git 跟踪)
│ ① 扩展保存时生成(单向 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.ts,TS 数据段永远为生成产物;不一致时弹冲突对话框(含 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 不被任何运行时代码 import,Cocos 构建不会打包它;asset-db 会为其生成 .meta,代价可接受。
2.2 Schema 要点
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=5(5401-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 |
| 必填缺失 | name / path / type / role |
5.2 Warning(确认后可存)
| 规则 | 说明 |
|---|---|
| lv > 6 档位 | HERO_MAX_LV=6 封顶,lv7/9 档当前永不生效(数据先行,运行时扩展上限后自动生效) |
| card_lv 1/2/3 配了 field | 应改走 ap_bonus/hp_bonus(30/40/50% 补偿) |
| card_lv 4/5 的 lv9 未配 field | 按设计指引应有驻场光环 |
| infos 条数与实际档位不匹配 | 面板逐条展示依赖 |
六、UI 设计
6.1 布局
同面板(default,1024×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|击退 bck|buff 注入 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(不污染用户暂存区) |
| 原子写 | 同目录临时文件 + rename;Windows EPERM(编辑器占用)时 200ms 退避重试 3 次 |
| 语法自检 | ts.createSourceFile 取 syntactic diagnostics(typescript 已在依赖);语义级检查提示用户跑 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 复用)+ 串行队列 |
八、初始迁移(一次性)
--cmd=export:tsx import 现 heroSet.ts → 导出heroSet.data.json(26 英雄 + 14 怪物全量,怪物标记只读)- 从 JSON 重新生成 TS → round-trip 逐字节比对必须通过(迁移门)
- 迁移门失败的处理:修序列化器直至通过(一次性成本);确实无法保真的字段(如怪物特有表达式)再评估局部文本透传回退
- 迁移完成后 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)