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

17 KiB
Raw Blame History

技能基座 + 角色定制 重构设计

背景

当前技能系统存在以下问题:

  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 — 基座模板

/** 技能基座模板 — 定义技能的类型、表现方式和默认数值 */
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 — 可覆盖的逻辑参数

/** 技能逻辑参数 — 可被角色 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 — 角色定义的技能参数

/** 角色技能参数覆盖 — 与 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 — 角色技能信息

interface HSkillInfo {
    uuid: number;
    lv: number;
    cd: number;
    ccd: number;
    /** 角色专属技能参数,覆盖基座 defaults */
    overrides?: HeroOverrides;
}

TriggerSkillConf — 触发技能配置

/** 触发技能配置 */
interface TriggerSkillConf {
    s_uuid: number;             // 触发的技能 UUID
    t_num: number;              // 激活阈值(攻击/受击多少次后触发),与 SkillDefaults.t_num 无关
    /** 角色定制参数,覆盖基座 defaults */
    overrides?: HeroOverrides;
}

语义区分TriggerSkillConf.t_num = 激活阈值("打几下触发"SkillDefaults.t_num = 目标数量上限("打几个人")。两者同名但语义完全不同,代码中不会交叉使用。

heroInfo 触发字段类型更新

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_uuidt_num 仍在顶层。

BuffConf 接口更新

// 旧
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

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 读取。调用方式:

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-6107SkillConfig 没有设置 kind,运行时通过 config.kind ?? SkillKind.Damage 回退。迁移时,所有攻击技能必须显式添加 kind: SkillKind.Damage?? SkillKind.Damage 回退逻辑可在迁移完成后移除。

call_hero 字段处置

SkillConfig 中存在 call_hero?: number 字段(召唤技能召唤英雄 ID。当前无技能配置使用此字段。在 SkillTemplate 中暂不保留,如后续需要可通过 defaults 扩展或单独处理。

配置示例

基座配置SkillSet.ts— 伤害类

// 普通攻击 — 伤害类基座
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: "火球攻击",
},

基座配置 — 治疗类

// 群体治疗 — 治疗类基座
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: "持续恢复基座",
},

基座配置 — 护盾类

// 护盾 — 护盾类基座
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 类

// 攻击强化 — 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— 同技能不同角色差异化

// 见习战士受击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.apconfig.TGroupconfig.hit_count 等逻辑参数的地方,改为从 resolveSkillParams() 获取
  2. kind 始终从 template.kind 获取,不参与覆盖
  3. applyFriendlySkillEffectsapplyActualFriendlyEffect 的参数从 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 获取 apreadyAnm

兼容性

  • 现有攻击技能6001-6107数值从顶层字段移入 defaults,必须显式添加 kind: SkillKind.Damage
  • 触发技能配置atking/atked 等):旧格式 {s_uuid, t_num} 通过类型兼容继续工作,overrides 为可选,现有代码无需改动即可兼容
  • 卡牌技能:使用 template.defaults,无需角色上下文
  • SkillUpList升级加成作用于最终 resolved 后的参数,调用位置不变

迁移策略

分步进行,每步可独立验证:

  1. Step 1新增接口 — 添加 SkillTemplateSkillDefaultsHeroOverridesTriggerSkillConf 类型定义,与旧 SkillConfig 并存
  2. Step 2迁移 SkillSet 数据 — 逐个将现有技能配置从旧格式转为新格式。注意所有攻击技能6001-6107必须显式添加 kind: SkillKind.DamageDAnm/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.buffBuffConf.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.tsSIconComp.tsSkillBoxComp.tsHlistComp.tsHInfoComp.tsMissionHeroComp.tsMissionEconomy.tsMissionComp.ts