refactor(cardSkill): 完成卡牌技能触发机制类型化改造
本次提交为全量的卡牌技能触发系统重构,主要变更包括: 1. 新增CardTriggerType枚举,统一卡牌触发类型定义 2. 补全依赖事件派发:每波战斗结束FightEnd、英雄死亡HeroDead(带阵营过滤)、复活成功ReviveSuccess 3. 重构SkillBoxComp,按触发类型动态注册事件监听,拆分即时/定时/驻场/事件型逻辑 4. 批量迁移所有卡牌配置,为旧技能补充显式触发类型 5. 新增全局触发次数上限机制,区分每波/全局触发计数规则 6. 新增配套设计文档,记录改造背景与方案细节 本次重构彻底解决了原有隐式配置难以维护、无法支持事件型触发的痛点,实现了技能触发逻辑的标准化与可扩展性。
This commit is contained in:
@@ -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.1:MissionComp 补派发 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.2:HeroAtkSystem 补派发 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 })`
|
||||
- **注意**:需先执行任务:全局搜索复活成功逻辑位置
|
||||
|
||||
---
|
||||
|
||||
### 阶段 2:CardSet 配置层改造
|
||||
|
||||
**目标**:定义枚举 + 扩展接口 + 修复字段透传 + 批量迁移配置
|
||||
|
||||
#### 任务 2.1:新增 CardTriggerType 枚举
|
||||
|
||||
- **文件**:[CardSet.ts](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts)
|
||||
- **位置**:`CardLV` 枚举之后
|
||||
- **内容**:见 [2.1 节](#21-cardtriggertype-枚举定义)
|
||||
|
||||
#### 任务 2.2:CardConfig 接口扩展
|
||||
|
||||
- **文件**:同上
|
||||
- **位置**:`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.4:SkillCardData 批量补 trigger_type(30 张卡牌)
|
||||
|
||||
- **文件**:同上
|
||||
- **迁移对照表**:
|
||||
|
||||
| 卡牌区间 | 旧字段特征 | 新增 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` |
|
||||
|
||||
---
|
||||
|
||||
### 阶段 3:SkillBoxComp 核心重构
|
||||
|
||||
**目标**:按 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.2:init 读取 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.5:onLoad / onDestroy 调整
|
||||
|
||||
- `onLoad`:移除原 FightStart / NewWave 硬编码监听,改为 `init` 后调用 `registerTrigger` 动态注册
|
||||
- `onDestroy`:统一 off 所有可能订阅的事件(即使没订阅也无副作用)
|
||||
|
||||
#### 任务 3.6:update 方法收窄
|
||||
|
||||
仅 `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 }
|
||||
```
|
||||
Reference in New Issue
Block a user