refactor(cardSkill): 完成卡牌技能触发机制类型化改造

本次提交为全量的卡牌技能触发系统重构,主要变更包括:
1.  新增CardTriggerType枚举,统一卡牌触发类型定义
2.  补全依赖事件派发:每波战斗结束FightEnd、英雄死亡HeroDead(带阵营过滤)、复活成功ReviveSuccess
3.  重构SkillBoxComp,按触发类型动态注册事件监听,拆分即时/定时/驻场/事件型逻辑
4.  批量迁移所有卡牌配置,为旧技能补充显式触发类型
5.  新增全局触发次数上限机制,区分每波/全局触发计数规则
6.  新增配套设计文档,记录改造背景与方案细节

本次重构彻底解决了原有隐式配置难以维护、无法支持事件型触发的痛点,实现了技能触发逻辑的标准化与可扩展性。
This commit is contained in:
panFD
2026-06-19 23:01:24 +08:00
parent a866cba8d1
commit dc8391847b
14 changed files with 4924 additions and 2125 deletions

View File

@@ -0,0 +1,332 @@
# 卡牌技能触发类型化改造执行计划
> 状态Accepted
> 日期2026-06-19
> 关联文档:`skill_card_trigger_refactor.md`(旧草案,已废弃,被本计划取代)
> 关联设计:`2026-05-22-skill-template-refactor-design.md`(技能 overrides 机制,本计划复用)
---
## 一、背景与目标
### 1.1 现状问题
当前技能卡([SkillCardData](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts#L151))通过 `is_inst` / `t_inv` / `field` 三个字段**隐式组合**推断触发模式:
| 隐式模式 | 判定条件 | 痛点 |
|---------|---------|------|
| 即时一次性 | `is_inst: true` | 类型不直观,新人需交叉对比 3 个字段 |
| 战斗中定时 | `is_inst: false && t_inv > 0` | 同上 |
| 纯驻场光环 | `field.length > 0 && t_inv <= 0` | 同上 |
且**无法表达**事件驱动型触发(战斗开始/结束、英雄死亡/召唤)。
### 1.2 改造目标
1. **显式类型化**:新增 `trigger_type` 字段,一张卡一个类型,强制必填
2. **事件驱动扩展**:新增 4 种事件触发类型,对齐英雄侧 [SkillTriggerType](file:///d:/game/pixelheros/assets/script/game/common/config/heroSet.ts#L91-L101)
3. **复用现有事件**:直接监听 `GameEvent.FightStart` / `FightEnd` / `HeroDead` / `MasterCalled` / `ReviveSuccess`
4. **零破坏迁移**:一次性批改所有 SkillCardData 配置,不保留向后兼容推断逻辑
### 1.3 关键决策(已确认)
| 决策点 | 选择 | 理由 |
|--------|------|------|
| 向后兼容策略 | **强制显式声明** | 一次性迁移到位,避免推断逻辑长期残留 |
| FightEnd 事件 | **新增 FightEnd 派发** | MissionEnd 是整局结束语义不符FightEnd 才是每波战斗结束 |
| HeroCall 覆盖范围 | **所有英雄上场** | MasterCalled主角+技能召唤)+ ReviveSuccess复活 |
| Field 类型改造 | **仅显式分类** | 实际生效仍由 FieldSkillSet 处理SkillBoxComp 不主动施法 |
---
## 二、最终设计(融合修正版)
### 2.1 CardTriggerType 枚举定义
> 融合说明吸收旧草案的命名规范Field/Interval/从1开始规避其事件映射错误
```typescript
/** 卡牌技能触发类型 */
export enum CardTriggerType {
Instant = 1, // 即时触发:使用后立即生效一次
Interval = 2, // 定时循环:战斗中按 t_inv 间隔重复触发
Field = 3, // 驻场光环:被动生效(仅显式分类,仍由 field 字段驱动)
FightStart = 4, // 战斗开始时触发
FightEnd = 5, // 战斗结束时触发(每波结束)
HeroDead = 6, // 场上己方英雄死亡时触发
HeroCall = 7, // 英雄上场时触发(主角召唤 + 技能召唤 + 复活)
}
```
**命名对齐说明**
- `Field` 对齐英雄侧 [SkillTriggerType.Field](file:///d:/game/pixelheros/assets/script/game/common/config/heroSet.ts#L96)
- `Interval` 对齐现有 `t_inv`interval命名
- 枚举值从 1 开始,避免 `0` 的 falsy 坑(`if (trigger_type)` 判断出错)
### 2.2 事件映射表(核心设计)
| trigger_type | 监听事件 | 派发点现状 | 需补派发 |
|--------------|---------|-----------|---------|
| `Instant` | 无init 时立即触发) | — | — |
| `Interval` | `FightStart`(启动计时) | ✅ [MissionComp.ts:458](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts#L458) | — |
| `Field` | 无(不主动施法) | — | — |
| `FightStart` | `FightStart` | ✅ 已派发 | — |
| `FightEnd` | `FightEnd` | ❌ **未派发** | ✅ [MissionComp.ts:494](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts#L494) 之后 |
| `HeroDead` | `HeroDead` | ❌ **未派发**(死代码) | ✅ [HeroAtkSystem.ts:329](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts#L329) 内(带阵营过滤) |
| `HeroCall` | `MasterCalled` + `ReviveSuccess` | MasterCalled ✅ 已派发ReviveSuccess ❌ 未派发 | ✅ 复活成功处补 ReviveSuccess |
### 2.3 CardConfig 接口扩展
```typescript
export interface CardConfig {
// ... 既有字段 ...
/** 触发类型(必填) */
trigger_type: CardTriggerType;
/** 事件型触发的全局次数上限(仅 FightStart/FightEnd/HeroDead/HeroCall 有效)
* 默认 Infinity达到上限后销毁节点
* 注意:与 t_times 语义不同——t_times 控制每波内 Interval 的次数 */
trigger_limit?: number;
}
```
### 2.4 t_times vs trigger_limit 语义区分
| 字段 | 适用类型 | 含义 | 重置时机 |
|------|---------|------|---------|
| `t_times` | `Interval` | 每波内的触发次数上限 | 每波 NewWave 时重置 |
| `trigger_limit` | `FightStart/FightEnd/HeroDead/HeroCall` | 整局全局触发总次数 | 不重置,达上限销毁 |
---
## 三、分阶段执行计划
### 阶段 1补齐事件派发缺口基础设施
**目标**:确保所有新触发类型依赖的事件都能正确派发
#### 任务 1.1MissionComp 补派发 FightEnd
- **文件**[MissionComp.ts](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts)
- **位置**`BattleEnd` case`triggerHeroBattleSkills(false)` + `healAllHeroes()` 之后
- **改动**
```typescript
case MissionPhase.BattleEnd:
// ... 既有评分逻辑 ...
this.triggerHeroBattleSkills(false);
this.healAllHeroes();
// 【新增】派发战斗结束事件,供卡牌技能监听
oops.message.dispatchEvent(GameEvent.FightEnd);
break;
```
#### 任务 1.2HeroAtkSystem 补派发 HeroDead带阵营过滤
- **文件**[HeroAtkSystem.ts](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts)
- **位置**`triggerDeadSkills` 方法L329 附近)
- **改动**
```typescript
private triggerDeadSkills(entity: ecs.Entity): void {
const TAttrsComp = entity.get(HeroAttrsComp);
if (!TAttrsComp) return;
const view = entity.get(HeroViewComp);
if (view) {
SkillTriggerHelper.trigger(SkillTriggerType.Dead, TAttrsComp, view);
// 【新增】仅英雄阵营派发全局死亡事件(怪物死亡不触发卡牌效果)
if (TAttrsComp.fac === FacSet.HERO) {
oops.message.dispatchEvent(GameEvent.HeroDead, { eid: entity.eid });
}
}
}
```
#### 任务 1.3:复活逻辑补派发 ReviveSuccess
- **文件**:需先定位复活成功处理点(搜索 `is_reviving` 置 false 的位置)
- **改动**:复活成功时派发 `oops.message.dispatchEvent(GameEvent.ReviveSuccess, { eid })`
- **注意**:需先执行任务:全局搜索复活成功逻辑位置
---
### 阶段 2CardSet 配置层改造
**目标**:定义枚举 + 扩展接口 + 修复字段透传 + 批量迁移配置
#### 任务 2.1:新增 CardTriggerType 枚举
- **文件**[CardSet.ts](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts)
- **位置**`CardLV` 枚举之后
- **内容**:见 [2.1 节](#21-cardtriggertype-枚举定义)
#### 任务 2.2CardConfig 接口扩展
- **文件**:同上
- **位置**`CardConfig` 接口
- **内容**:见 [2.3 节](#23-cardconfig-接口扩展)
#### 任务 2.3:修复 SkillCardData.forEach 字段透传断点
- **文件**:同上
- **位置**[L220-L240](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts#L220-L240) `SkillCardData.forEach`
- **改动**:补充 `overrides``trigger_type` 透传:
```typescript
SkillCardData.forEach(data => {
CardPoolList.push({
// ... 既有字段 ...
keep_waves: data.keep_waves,
field: data.field,
overrides: data.overrides, // 【修复】原遗漏
trigger_type: data.trigger_type, // 【新增】
trigger_limit: data.trigger_limit, // 【新增】
});
});
```
#### 任务 2.4SkillCardData 批量补 trigger_type30 张卡牌)
- **文件**:同上
- **迁移对照表**
| 卡牌区间 | 旧字段特征 | 新增 trigger_type |
|---------|-----------|------------------|
| 8301, 8302, 8303, 8401-8409, 8501`is_inst: true` | 即时技能 | `CardTriggerType.Instant` |
| 8705, 8706, 8707, 8708-8718, 8701-8704`field` | 驻场光环 | `CardTriggerType.Field` |
| 8201-8206`is_inst: false, t_inv: 5` | 定时循环 | `CardTriggerType.Interval` |
---
### 阶段 3SkillBoxComp 核心重构
**目标**:按 trigger_type 分发事件监听与触发
#### 任务 3.1:新增成员变量
- **文件**[SkillBoxComp.ts](file:///d:/game/pixelheros/assets/script/game/map/SkillBoxComp.ts)
- **位置**`// ======================== 技能配置 ========================` 区块
```typescript
/** 触发类型 */
private trigger_type: CardTriggerType = CardTriggerType.Instant;
/** 事件型触发次数上限 */
private trigger_limit: number = Infinity;
/** 事件型已触发次数 */
private trigger_count: number = 0;
```
#### 任务 3.2init 读取 trigger_type
- **位置**`init()` 方法内,读取 config 之后
```typescript
this.trigger_type = config.trigger_type ?? CardTriggerType.Instant;
this.trigger_limit = config.trigger_limit ?? Infinity;
```
#### 任务 3.3:新增 registerTrigger 方法
按 trigger_type 注册对应事件监听,见下方完整代码。
#### 任务 3.4:新增 onEventTrigger 统一入口
事件型触发的统一处理:检查 trigger_limit → triggerSkill → 累加计数 → 检查销毁。
#### 任务 3.5onLoad / onDestroy 调整
- `onLoad`:移除原 FightStart / NewWave 硬编码监听,改为 `init` 后调用 `registerTrigger` 动态注册
- `onDestroy`:统一 off 所有可能订阅的事件(即使没订阅也无副作用)
#### 任务 3.6update 方法收窄
`Interval` 类型走帧驱动计时逻辑,其他类型提前 return。
---
### 阶段 4验证与回归
#### 任务 4.1:编译检查
- 确认所有 CardTriggerType 引用正确
- 确认无 TS 类型错误
#### 任务 4.2:功能验证清单
- [ ] 即时卡8301 护盾):使用后立即触发,每波重置
- [ ] 定时卡8201 雷墙):战斗中每 5 秒触发,跨波次维持
- [ ] 驻场卡8705 金币收益):被动生效,不主动施法
- [ ] 新增 FightStart 卡:每波战斗开始时触发
- [ ] 新增 FightEnd 卡:每波战斗结束时触发
- [ ] 新增 HeroDead 卡:英雄死亡时触发,怪物死亡不触发
- [ ] 新增 HeroCall 卡:主角召唤/技能召唤/复活都触发
#### 任务 4.3:边界场景
- [ ] trigger_limit 达上限后节点正确销毁
- [ ] keep_waves 与 trigger_type 的组合行为正确
- [ ] 节点销毁时所有事件监听正确注销(无内存泄漏)
---
## 四、风险与注意事项
### 4.1 高风险点
1. **HeroDead 必须加阵营过滤**
- [HeroAtkSystem.triggerDeadSkills](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts#L319) 是英雄和怪物共用
- 不加 `fac === FacSet.HERO` 过滤 → 每波几百只怪物死亡 = 海量误触发
2. **FightEnd vs MissionEnd 不可混淆**
- MissionEnd = 整局任务结束(通关/失败)
- FightEnd = 每波战斗结束BattleEnd 阶段)
- 文档草案错误地把 BattleEnd 映射到 MissionEnd本计划已修正
3. **MasterCalled 携带数据不一致**
- 3 个派发点([Hero.ts:206](file:///d:/game/pixelheros/assets/script/game/hero/Hero.ts#L206)、[MissionHeroComp.ts:223](file:///d:/game/pixelheros/assets/script/game/map/MissionHeroComp.ts#L223)、[273](file:///d:/game/pixelheros/assets/script/game/map/MissionHeroComp.ts#L273)payload 字段不同
- SkillBoxComp 的 `onEventTrigger` **不要读 payload 字段**,仅作触发信号
### 4.2 不破坏的现有逻辑
- ✅ Field 类型完全复用现有 [FieldSkillSet](file:///d:/game/pixelheros/assets/script/game/common/config/SkillSet.ts#L414-L421) 机制
- ✅ Interval 类型完全复用现有 `update` 帧驱动 + cd_mask 表现
- ✅ [forceCastCardSkill](file:///d:/game/pixelheros/assets/script/game/hero/SCastSystem.ts#L75) 施法入口零改动
- ✅ [SBox.ts](file:///d:/game/pixelheros/assets/script/game/map/SBox.ts) 节点工厂零改动
### 4.3 keep_waves 跨类型语义
| trigger_type | keep_waves 默认值 | 行为 |
|--------------|-----------------|------|
| `Instant` | 0 = 用完即销 | `-1` = 每波重置再触发一次 |
| `Interval` | -1 = 跨波次维持 | 每波重置 timer 和 trigger_count |
| `Field` | -1 = 全程存活 | 不主动触发 |
| 事件型 | 由 `trigger_limit` 控制 | 达上限销毁 |
---
## 五、文件改动清单
| 文件 | 改动类型 | 阶段 |
|------|---------|------|
| [MissionComp.ts](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts) | 补 FightEnd 派发 | 1 |
| [HeroAtkSystem.ts](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts) | 补 HeroDead 派发(带过滤) | 1 |
| 复活逻辑文件(待定位) | 补 ReviveSuccess 派发 | 1 |
| [CardSet.ts](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts) | 枚举+接口+透传+30张卡迁移 | 2 |
| [SkillBoxComp.ts](file:///d:/game/pixelheros/assets/script/game/map/SkillBoxComp.ts) | 核心重构 | 3 |
| [SBox.ts](file:///d:/game/pixelheros/assets/script/game/map/SBox.ts) | **零改动** | — |
---
## 六、新增卡牌配置示例
```typescript
// 战斗开始护盾(整局每波开始都给全队加盾)
{ uuid: 8310, skill: 6301, wave: 5, name: "起手护盾",
trigger_type: CardTriggerType.FightStart, keep_waves: -1,
overrides: { TGroup: TGroup.Team, ap: 3 },
info: "每波战斗开始时为全体友方添加护盾", is_inst: false }
// 英雄死亡治疗(整局最多触发 3 次)
{ uuid: 8311, skill: 6302, wave: 10, name: "亡语治疗",
trigger_type: CardTriggerType.HeroDead, trigger_limit: 3, keep_waves: -1,
overrides: { TGroup: TGroup.Team, ap: 200 },
info: "己方英雄死亡时治疗全体友方整局最多触发3次", is_inst: false }
// 英雄上场攻击强化(每次有新英雄上场都触发,最多 5 次)
{ uuid: 8312, skill: 6401, wave: 15, name: "召唤强化",
trigger_type: CardTriggerType.HeroCall, trigger_limit: 5, keep_waves: -1,
info: "有英雄上场时触发攻击强化整局最多触发5次", is_inst: false }
```