Files
pixelheros/docs/superpowers/specs/2026-05-22-skill-template-refactor-design.md
walkpan eae62f245a docs: add skill template refactor design and migration plan docs
新增技能基座重构的设计文档和完整迁移计划文档,包含新的接口定义、配置结构、运行时参数解析逻辑以及分步迁移验证方案,为后续技能系统重构提供完整的设计指导。
2026-05-22 10:22:33 +08:00

410 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 技能基座 + 角色定制 重构设计
## 背景
当前技能系统存在以下问题:
1. **所有技能类型混在一张表** — 攻击、治疗、护盾、buff 共用 `SkillConfig` 接口,字段复用导致每个技能配置含义不同(如 `ap` 在攻击技能是百分比,在护盾是次数,在 buff 是 0
2. **字段语义混乱**`hit_count` 在攻击技能是"命中次数",在 buff 技能是"持续次数"
3. **buff 技能包含大量无用字段** — speed、with、EType、RType 等 buff 完全不需要
4. **同技能不同角色无法差异化** — 所有使用同一技能的角色效果完全相同
5. **扩展困难** — 新增 buff 属性类型需要改 SCastSystem 的 switch 代码
## 设计目标
- SkillSet 作为**基座模板**,定义技能类型分类(伤害/治疗/护盾/buff+ 表现层(动画、弹道、特效)+ 默认数值
- `kind`(技能类型)由模板固定,角色不可覆盖
- heroSet 负责定义**实际效果值**伤害、治疗量、目标数量、buff 效果)
- 同一个技能 UUID不同角色可以有完全不同的数值行为
- 触发技能atking/atked/fstart/fend也支持角色定制
- 卡牌技能通过基座 defaults 正常工作
## 配置结构
### SkillTemplate — 基座模板
```typescript
/** 技能基座模板 — 定义技能的类型、表现方式和默认数值 */
interface SkillTemplate {
uuid: number;
name: string;
sp_name: string; // 特效名
icon: string;
// 技能类型分类 — 由模板固定,角色不可覆盖
kind: SkillKind; // Damage | Heal | Shield | Support
// 动画表现
act: string; // 角色动画
readyAnm: string; // 前摇动画
endAnm: string;
EAnm: number; // 结束动画 ID
DAnm: string; // 命中后动画名(如 "atked_ice"、"atked_fire"
ready: number; // 前摇时间
// 弹道/表现行为(技能"怎么飞"
IType: IType; // 近战/远程/辅助
RType: RType; // 直线/贝塞尔/固定
EType: EType; // 结束条件
speed: number; // 移动速度
DTType: DTType; // 单体/范围(影响碰撞体)
with: number; // 宽度
bezier_start_y?: number;
bezier_mid_y?: number;
bezier_arc?: number;
time?: number; // timeEnd 持续时间
// 基座默认值(卡牌技能等无角色主体时使用)
defaults: SkillDefaults;
info: string;
}
```
### SkillDefaults — 可覆盖的逻辑参数
```typescript
/** 技能逻辑参数 — 可被角色 overrides 覆盖 */
interface SkillDefaults {
TGroup: TGroup; // 目标群体(敌方/友方/自身等)
ap: number; // 伤害百分比 / 治疗百分比 / 护盾次数 / buff 固定为 0
t_num: number; // 目标数量上限
hit_count: number; // 命中/持续次数
hitcd: number; // 间隔
crt?: number; // 额外暴击率
frz?: number; // 额外冰冻概率
bck?: number; // 额外击退概率
buffs?: BuffConf[]; // buff 效果列表(仅 kind=Support 时使用)
}
```
### HeroOverrides — 角色定义的技能参数
```typescript
/** 角色技能参数覆盖 — 与 SkillDefaults 结构一致,不含 kind */
interface HeroOverrides {
TGroup?: TGroup; // 覆盖目标群体
ap?: number; // 覆盖效果值
t_num?: number; // 覆盖目标数量上限
hit_count?: number; // 覆盖命中/持续次数
hitcd?: number; // 覆盖间隔
crt?: number; // 覆盖暴击率
frz?: number; // 覆盖冰冻概率
bck?: number; // 覆盖击退概率
buffs?: BuffConf[]; // 覆盖 buff 效果列表
}
```
所有字段可选 — 只覆盖需要定制的参数,其余继承基座 defaults。**kind 不可覆盖**。
### HSkillInfo — 角色技能信息
```typescript
interface HSkillInfo {
uuid: number;
lv: number;
cd: number;
ccd: number;
/** 角色专属技能参数,覆盖基座 defaults */
overrides?: HeroOverrides;
}
```
### TriggerSkillConf — 触发技能配置
```typescript
/** 触发技能配置 */
interface TriggerSkillConf {
s_uuid: number; // 触发的技能 UUID
t_num: number; // 激活阈值(攻击/受击多少次后触发),与 SkillDefaults.t_num 无关
/** 角色定制参数,覆盖基座 defaults */
overrides?: HeroOverrides;
}
```
> **语义区分**`TriggerSkillConf.t_num` = 激活阈值("打几下触发"`SkillDefaults.t_num` = 目标数量上限("打几个人")。两者同名但语义完全不同,代码中不会交叉使用。
### heroInfo 触发字段类型更新
```typescript
interface heroInfo {
// ... 现有字段不变
skills: Record<number, HSkillInfo>;
call?: TriggerSkillConf[];
dead?: TriggerSkillConf[];
fstart?: TriggerSkillConf[];
fend?: TriggerSkillConf[];
atking?: TriggerSkillConf[];
atked?: TriggerSkillConf[];
revive?: { s_uuid: number; r_num: number; upr: number };
}
```
向后兼容:旧格式 `{s_uuid, t_num}` 仍然有效,`overrides` 为可选字段。现有代码遍历这些数组时,`overrides` 不存在则跳过,`s_uuid``t_num` 仍在顶层。
## BuffConf 接口更新
```typescript
// 旧
interface BuffConf {
buff: Attrs;
value: number;
}
// 新 — 字段名更清晰
interface BuffConf {
attr: Attrs; // 修改哪个属性
value: number; // 效果值
}
```
重命名范围SCastSystem.ts:433 的 `switch (buffConf.buff)``switch (buffConf.attr)`,以及 SkillSet.ts 中 6401-6406 配置数据中的 `buff:``attr:`
## 运行时参数解析
### resolveSkillParams — 三级覆盖
优先级:**基座 defaults → 角色技能 overrides → 触发配置 overrides**
```typescript
function resolveSkillParams(
template: SkillTemplate,
heroSkill?: HSkillInfo,
triggerConf?: TriggerSkillConf
): { kind: SkillKind } & Required<SkillDefaults> {
return {
// kind 由模板固定,不参与覆盖
kind: template.kind,
// 数值参数三级覆盖
...template.defaults,
...heroSkill?.overrides,
...triggerConf?.overrides,
};
}
```
`kind` 始终取自 `template.kind`,即使 overrides 中误写了 kind 也不会生效。
### SkillUpList 升级加成
SkillUpList 升级加成作用于 **resolved 之后的参数**,而非直接从 config 读取。调用方式:
```typescript
const resolved = resolveSkillParams(template, heroSkill, triggerConf);
const sUp = SkillUpList[s_uuid] ?? SkillUpList[1001];
const finalAp = resolved.ap + sUp.ap * skillLv;
const finalHitCount = resolved.hit_count + sUp.hit_count * skillLv;
const finalCrt = (resolved.crt ?? 0) + sUp.crt * skillLv;
```
在 SCastSystem.ts 和 Skill.ts 中,所有当前直接读取 `config.ap` + `sUp.ap` 的地方,统一改为先 resolve 再应用 sUp。
### 卡牌技能路径
卡牌技能无角色上下文,调用 `resolveSkillParams(template, undefined, undefined)` 返回 `template.defaults`。在 `forceCastCardSkill()` 中使用此方式获取参数。
## 迁移注意事项
### 攻击技能必须显式设置 kind
当前攻击技能6001-6107`SkillConfig` 没有设置 `kind`,运行时通过 `config.kind ?? SkillKind.Damage` 回退。迁移时,所有攻击技能必须显式添加 `kind: SkillKind.Damage``?? SkillKind.Damage` 回退逻辑可在迁移完成后移除。
### call_hero 字段处置
`SkillConfig` 中存在 `call_hero?: number` 字段(召唤技能召唤英雄 ID。当前无技能配置使用此字段。在 `SkillTemplate` 中暂不保留,如后续需要可通过 `defaults` 扩展或单独处理。
## 配置示例
### 基座配置SkillSet.ts— 伤害类
```typescript
// 普通攻击 — 伤害类基座
6001: {
uuid: 6001, name: "普通攻击", sp_name: "atk", icon: "1026",
kind: SkillKind.Damage,
act: "atk", readyAnm: "", endAnm: "", EAnm: 0, DAnm: "", ready: 0.2,
IType: IType.Melee, RType: RType.linear, EType: EType.collision,
speed: 720, DTType: DTType.single, with: 0,
defaults: {
TGroup: TGroup.Enemy, ap: 100, t_num: 1, hit_count: 1, hitcd: 0.2,
},
info: "造成攻击力100%的伤害",
},
// 火球 — 伤害类基座,带暴击
6101: {
uuid: 6101, name: "火球", sp_name: "ball_fire", icon: "1126",
kind: SkillKind.Damage,
act: "atk", readyAnm: "", endAnm: "", EAnm: 0, DAnm: "", ready: 0.2,
IType: IType.remote, RType: RType.linear, EType: EType.collision,
speed: 720, DTType: DTType.single, with: 90,
defaults: {
TGroup: TGroup.Enemy, ap: 100, t_num: 1, hit_count: 1, hitcd: 0.3, crt: 20,
},
info: "火球攻击",
},
```
### 基座配置 — 治疗类
```typescript
// 群体治疗 — 治疗类基座
6302: {
uuid: 6302, name: "群体治疗", sp_name: "buff_wind", icon: "1292",
kind: SkillKind.Heal,
act: "atk", readyAnm: "up_green", endAnm: "", EAnm: 0, DAnm: "", ready: 0.2,
IType: IType.support, RType: RType.fixed, EType: EType.animationEnd,
speed: 720, DTType: DTType.single, with: 0,
defaults: {
TGroup: TGroup.Team, ap: 300, t_num: 5, hit_count: 1, hitcd: 0.2,
},
info: "治疗基座",
},
// 持续恢复 — 治疗类基座
6304: {
uuid: 6304, name: "持续恢复", sp_name: "buff_wind", icon: "1292",
kind: SkillKind.Heal,
act: "atk", readyAnm: "up_green", endAnm: "", EAnm: 0, DAnm: "", ready: 0.2,
IType: IType.support, RType: RType.fixed, EType: EType.animationEnd,
speed: 720, DTType: DTType.single, with: 0,
defaults: {
TGroup: TGroup.Team, ap: 200, t_num: 5, hit_count: 3, hitcd: 0.2,
},
info: "持续恢复基座",
},
```
### 基座配置 — 护盾类
```typescript
// 护盾 — 护盾类基座
6301: {
uuid: 6301, name: "护盾", sp_name: "buff_wind", icon: "1255",
kind: SkillKind.Shield,
act: "atk", readyAnm: "up_blue", endAnm: "", EAnm: 0, DAnm: "", ready: 0.2,
IType: IType.support, RType: RType.fixed, EType: EType.animationEnd,
speed: 720, DTType: DTType.single, with: 0,
defaults: {
TGroup: TGroup.Self, ap: 3, t_num: 1, hit_count: 1, hitcd: 0.2,
},
info: "护盾基座",
},
```
### 基座配置 — Buff 类
```typescript
// 攻击强化 — buff 类基座
6401: {
uuid: 6401, name: "攻击强化", sp_name: "buff_wind", icon: "1255",
kind: SkillKind.Support,
act: "atk", readyAnm: "up_ap", endAnm: "", EAnm: 0, DAnm: "", ready: 0.2,
IType: IType.support, RType: RType.fixed, EType: EType.animationEnd,
speed: 720, DTType: DTType.single, with: 0,
defaults: {
TGroup: TGroup.Team, ap: 0, t_num: 5, hit_count: 1, hitcd: 0.2,
buffs: [{ attr: Attrs.ap, value: 5 }],
},
info: "攻击力强化",
},
```
### 角色配置heroSet.ts— 同技能不同角色差异化
```typescript
// 见习战士受击2次触发护盾 → 自己加3层盾
5001: {
uuid: 5001, name: "见习战士", ...,
skills: { 6002: { uuid: 6002, lv: 1, cd: 1.5, ccd: 0 } },
atked: [{ s_uuid: 6301, t_num: 2 }],
// 6301 基座 defaults: TGroup.Self, ap:3, t_num:1
// → 使用基座默认值给自己加3次盾
},
// 盾骑士受击2次触发护盾 → 全队加2层盾覆盖 TGroup 和 ap
5002: {
uuid: 5002, name: "盾骑士", ...,
skills: { 6002: { uuid: 6002, lv: 1, cd: 1.5, ccd: 0 } },
atked: [{ s_uuid: 6301, t_num: 2,
overrides: { TGroup: TGroup.Team, ap: 2, t_num: 5, hit_count: 3 }
}],
// → kind=Shield 由基座固定TGroup/ap/t_num 由角色覆盖
},
// 牧师普攻2次触发群体治疗 → 恢复 300%(使用基座默认值)
5301: {
uuid: 5301, name: "牧师", ...,
skills: { 6004: { uuid: 6004, lv: 1, cd: 1.2, ccd: 0 } },
atking: [{ s_uuid: 6302, t_num: 2 }],
// → 6302 基座 defaults: TGroup.Team, ap:300, t_num:5, hit_count:1
},
// 医师普攻2次触发持续治疗 → 共3次每次200%(覆盖 hit_count
5302: {
uuid: 5302, name: "医师", ...,
skills: { 6004: { uuid: 6004, lv: 1, cd: 1.2, ccd: 0 } },
atking: [{ s_uuid: 6304, t_num: 2,
overrides: { hit_count: 3 }
}],
// → 6304 基座 defaults: ap:200, t_num:5, hit_count:3 → 只覆盖了 hit_count
},
```
## SCastSystem 改动要点
1. 所有读取 `config.ap``config.TGroup``config.hit_count` 等逻辑参数的地方,改为从 `resolveSkillParams()` 获取
2. `kind` 始终从 `template.kind` 获取,不参与覆盖
3. `applyFriendlySkillEffects``applyActualFriendlyEffect` 的参数从 `config: SkillConfig` 调整为接收 `SkillTemplate` + resolved 后的 `SkillDefaults`
4. buff 应用逻辑的 `switch` 分支扩展时只需改一处
5. 卡牌技能路径:调用 `resolveSkillParams(template, undefined, undefined)` 获取参数
6. SkillUpList 升级加成统一应用于 resolved 之后的参数
## HeroAtkSystem 改动要点
`HeroAtkSystem.ts` 直接读取 `config.ap` 用于伤害计算和复活技能。必须同步适配:
- 伤害计算路径(~第 287-292 行):从 resolved defaults 获取 `ap`
- 复活技能路径(~第 220-236 行):从 resolved defaults 获取 `ap``readyAnm`
## 兼容性
- 现有攻击技能6001-6107数值从顶层字段移入 `defaults`,必须显式添加 `kind: SkillKind.Damage`
- 触发技能配置atking/atked 等):旧格式 `{s_uuid, t_num}` 通过类型兼容继续工作,`overrides` 为可选,现有代码无需改动即可兼容
- 卡牌技能:使用 `template.defaults`,无需角色上下文
- SkillUpList升级加成作用于最终 resolved 后的参数,调用位置不变
## 迁移策略
分步进行,每步可独立验证:
1. **Step 1新增接口** — 添加 `SkillTemplate``SkillDefaults``HeroOverrides``TriggerSkillConf` 类型定义,与旧 `SkillConfig` 并存
2. **Step 2迁移 SkillSet 数据** — 逐个将现有技能配置从旧格式转为新格式。注意所有攻击技能6001-6107必须显式添加 `kind: SkillKind.Damage``DAnm`/`EAnm` 保留在模板顶层
3. **Step 3迁移 heroSet 数据** — 为需要差异化的角色添加 overrides同步更新 `HeroAttrsComp.ts` 中 atking/atked 的内联类型定义为 `TriggerSkillConf[]`
4. **Step 4重构 SCastSystem + HeroAtkSystem** — 引入 `resolveSkillParams()`,替换所有直接读取 config 字段的逻辑SkillUpList 统一应用于 resolved 参数
5. **Step 5适配引用文件** — 逐个更新 Skill.ts、SkillView.ts、SMoveSystem.ts、HeroViewComp.ts、CardComp.ts 等消费者
6. **Step 6清理** — 删除旧 `SkillConfig` 接口,全局 `BuffConf.buff``BuffConf.attr` 重命名(配置数据 + 运行时代码),移除 `?? SkillKind.Damage` 回退逻辑
## 涉及文件
| 文件 | 改动级别 | 改动内容 |
|------|----------|----------|
| `assets/script/game/common/config/SkillSet.ts` | 高 | 重构接口,新增 SkillTemplate/SkillDefaults数据迁移 |
| `assets/script/game/common/config/heroSet.ts` | 中 | HSkillInfo 增加 overrides触发配置类型更新 |
| `assets/script/game/common/config/HeroAttrs.ts` | 低 | BuffConf 字段名 buff → attr |
| `assets/script/game/hero/SCastSystem.ts` | 高 | 参数解析逻辑重构,引入 resolveSkillParamsSkillUpList 适配 |
| `assets/script/game/hero/HeroAtkSystem.ts` | 高 | 伤害计算和复活技能读取 ap/readyAnm 适配新接口 |
| `assets/script/game/hero/SkillTriggerHelper.ts` | 中 | 触发技能传参适配,传递 overrides |
| `assets/script/game/hero/HeroAttrsComp.ts` | 中 | atking/atked 内联类型更新为 TriggerSkillConf[] |
| `assets/script/game/hero/HeroViewComp.ts` | 低 | 读取 DAnm 字段,接口名变更 |
| `assets/script/game/skill/Skill.ts` | 高 | skill.load() 适配新接口,区分模板字段和逻辑参数 |
| `assets/script/game/skill/SkillView.ts` | 低 | SkillConfig 引用改为 SkillTemplate |
| `assets/script/game/skill/SMoveSystem.ts` | 低 | 读取 speed/RType 等表现字段,接口名变更 |
| `assets/script/game/skill/STimeComp.ts` | 低 | 读取 EType/time接口名变更 |
| `assets/script/game/hero/Hero.ts` | 中 | 英雄初始化,传递 overrides |
| `assets/script/game/hero/Mon.ts` | 中 | 怪物初始化,传递 overrides |
| `assets/script/game/map/CardComp.ts` | 低 | SkillSet 引用适配 |
| `assets/script/game/map/IBoxComp.ts` | 低 | SkillSet 引用适配 |
以下文件仅读取 SkillTemplate 中保留顶层的字段name、icon、IType 等),改动级别极低或无需改动:
`TooltipCom.ts``SIconComp.ts``SkillBoxComp.ts``HlistComp.ts``HInfoComp.ts``MissionHeroComp.ts``MissionEconomy.ts``MissionComp.ts`