refactor(cardSkill): 完成卡牌技能触发机制类型化改造
本次提交为全量的卡牌技能触发系统重构,主要变更包括: 1. 新增CardTriggerType枚举,统一卡牌触发类型定义 2. 补全依赖事件派发:每波战斗结束FightEnd、英雄死亡HeroDead(带阵营过滤)、复活成功ReviveSuccess 3. 重构SkillBoxComp,按触发类型动态注册事件监听,拆分即时/定时/驻场/事件型逻辑 4. 批量迁移所有卡牌配置,为旧技能补充显式触发类型 5. 新增全局触发次数上限机制,区分每波/全局触发计数规则 6. 新增配套设计文档,记录改造背景与方案细节 本次重构彻底解决了原有隐式配置难以维护、无法支持事件型触发的痛点,实现了技能触发逻辑的标准化与可扩展性。
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -16006,7 +16006,7 @@
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": true
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetOverrideInfo",
|
||||
@@ -16135,8 +16135,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 160,
|
||||
"height": 30
|
||||
"width": 180,
|
||||
"height": 40
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -16174,10 +16174,10 @@
|
||||
"_string": "名称",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 25,
|
||||
"_fontSize": 25,
|
||||
"_actualFontSize": 31,
|
||||
"_fontSize": 30,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 30,
|
||||
"_lineHeight": 35,
|
||||
"_overflow": 2,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
@@ -16233,7 +16233,7 @@
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 161.724,
|
||||
"_top": 156.724,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
@@ -19587,7 +19587,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -92.5,
|
||||
"y": -86.209,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -19677,8 +19677,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 150,
|
||||
"height": 70
|
||||
"width": 180,
|
||||
"height": 80
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -19713,13 +19713,13 @@
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_string": "全体英雄攻击+5\n全体英雄攻击+5",
|
||||
"_string": "全体英雄攻击+5全体英雄攻击+5全体英雄攻击+5",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 21,
|
||||
"_fontSize": 35,
|
||||
"_fontSize": 20,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 40,
|
||||
"_lineHeight": 25,
|
||||
"_overflow": 2,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
@@ -19787,7 +19787,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 170,
|
||||
"height": 55
|
||||
"height": 90
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -20092,8 +20092,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 160,
|
||||
"height": 30
|
||||
"width": 180,
|
||||
"height": 40
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -20131,10 +20131,10 @@
|
||||
"_string": "名称",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 25,
|
||||
"_fontSize": 25,
|
||||
"_actualFontSize": 31,
|
||||
"_fontSize": 30,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 30,
|
||||
"_lineHeight": 35,
|
||||
"_overflow": 2,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
@@ -20190,7 +20190,7 @@
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 161.724,
|
||||
"_top": 156.724,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
@@ -23541,7 +23541,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -92.5,
|
||||
"y": -86.209,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -23631,8 +23631,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 150,
|
||||
"height": 70
|
||||
"width": 180,
|
||||
"height": 80
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -23667,13 +23667,13 @@
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_string": "全体英雄攻击+5\n全体英雄攻击+5",
|
||||
"_string": "全体英雄攻击+5全体英雄攻击+5全体英雄攻击+5",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 21,
|
||||
"_fontSize": 35,
|
||||
"_fontSize": 20,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 40,
|
||||
"_lineHeight": 25,
|
||||
"_overflow": 2,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
@@ -23741,7 +23741,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 170,
|
||||
"height": 55
|
||||
"height": 90
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -24046,8 +24046,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 160,
|
||||
"height": 30
|
||||
"width": 180,
|
||||
"height": 40
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -24085,10 +24085,10 @@
|
||||
"_string": "名称",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 25,
|
||||
"_fontSize": 25,
|
||||
"_actualFontSize": 31,
|
||||
"_fontSize": 30,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 30,
|
||||
"_lineHeight": 35,
|
||||
"_overflow": 2,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
@@ -24144,7 +24144,7 @@
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 161.724,
|
||||
"_top": 156.724,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
@@ -27495,7 +27495,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -92.5,
|
||||
"y": -86.209,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -27585,8 +27585,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 150,
|
||||
"height": 70
|
||||
"width": 180,
|
||||
"height": 80
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -27621,13 +27621,13 @@
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_string": "全体英雄攻击+5\n全体英雄攻击+5",
|
||||
"_string": "全体英雄攻击+5全体英雄攻击+5全体英雄攻击+5",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 21,
|
||||
"_fontSize": 35,
|
||||
"_fontSize": 20,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 40,
|
||||
"_lineHeight": 25,
|
||||
"_overflow": 2,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
@@ -27695,7 +27695,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 170,
|
||||
"height": 55
|
||||
"height": 90
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -180,
|
||||
"y": 990,
|
||||
"x": -160,
|
||||
"y": 350,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -79,7 +79,7 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 360,
|
||||
"width": 400,
|
||||
"height": 100
|
||||
},
|
||||
"_anchorPoint": {
|
||||
@@ -198,6 +198,7 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "5622mxbS1PNqMFP0FH5Mir",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
@@ -114,7 +114,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 30,
|
||||
"y": 40,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -126,8 +126,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"x": 0.2,
|
||||
"y": 0.2,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -154,8 +154,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 60,
|
||||
"height": 60
|
||||
"width": 370,
|
||||
"height": 370
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -191,7 +191,7 @@
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d9082",
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d8db6",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 1,
|
||||
@@ -230,10 +230,10 @@
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 0,
|
||||
"_bottom": 0,
|
||||
"_left": 3,
|
||||
"_right": 3,
|
||||
"_top": 3,
|
||||
"_bottom": 3,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
@@ -261,8 +261,6 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "93fFoXu3BBYqu4RLG2YPon",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -1160,7 +1158,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 30.251,
|
||||
"y": 40,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -1172,8 +1170,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.7,
|
||||
"y": 0.7,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -1302,7 +1300,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 30,
|
||||
"y": 40,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -1342,8 +1340,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 56,
|
||||
"height": 56
|
||||
"width": 76,
|
||||
"height": 76
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -1477,7 +1475,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 30,
|
||||
"y": 40,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -1517,8 +1515,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 150,
|
||||
"height": 150
|
||||
"width": 200,
|
||||
"height": 200
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -1798,8 +1796,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 60,
|
||||
"height": 60
|
||||
"width": 80,
|
||||
"height": 80
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
|
||||
@@ -37,6 +37,16 @@ export enum CKind {
|
||||
Potion = 4, //药水
|
||||
}
|
||||
|
||||
/** 技能卡触发类型 */
|
||||
export enum CardSkillType {
|
||||
Interval = 1, // 间隔定时触发 (战斗中每隔N秒执行)
|
||||
Field = 2, // 驻场技能 (被动光环)
|
||||
BattleStart = 3, // 战斗开始时触发一次
|
||||
BattleEnd = 4, // 战斗结束时触发一次
|
||||
HeroDead = 5, // 场上己方英雄死亡时触发
|
||||
HeroCall = 6, // 场上己方英雄召唤上场时触发
|
||||
}
|
||||
|
||||
/** 卡池等级定义 */
|
||||
export enum CardLV {
|
||||
LV1 = 1,
|
||||
@@ -46,6 +56,21 @@ export enum CardLV {
|
||||
LV5 = 5,
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡牌技能触发类型
|
||||
* - 命名对齐英雄侧 SkillTriggerType,便于跨模块认知统一
|
||||
* - 枚举值从 1 开始,避免 0 的 falsy 坑(if (trigger_type) 判断出错)
|
||||
*/
|
||||
export enum CardTriggerType {
|
||||
Instant = 1, // 即时触发:使用后立即生效一次
|
||||
Interval = 2, // 定时循环:战斗中按 t_inv 间隔重复触发
|
||||
Field = 3, // 驻场光环:被动生效(仅显式分类,仍由 field 字段驱动)
|
||||
FightStart = 4, // 战斗开始时触发
|
||||
FightEnd = 5, // 战斗结束时触发(每波结束)
|
||||
HeroDead = 6, // 场上己方英雄死亡时触发
|
||||
HeroCall = 7, // 英雄上场时触发(主角召唤 + 技能召唤 + 复活)
|
||||
}
|
||||
|
||||
/** 通用卡牌配置 */
|
||||
export interface CardConfig {
|
||||
uuid: number
|
||||
@@ -69,6 +94,15 @@ export interface CardConfig {
|
||||
keep_waves?: number // 维持的波次数(-1表示持续到战斗结束,0或undefined表示仅本波次)
|
||||
overrides?: SkillOverrides // 技能参数覆写(如自定义伤害ap、buff值、金币数等)
|
||||
field?: number[] // 驻场技能 UUID 数组,表示该卡牌提供驻场属性加成
|
||||
|
||||
/** 触发类型(必填,技能卡专用;功能卡/英雄卡可缺省) */
|
||||
trigger_type?: CardTriggerType;
|
||||
/**
|
||||
* 事件型触发的全局次数上限(仅 FightStart/FightEnd/HeroDead/HeroCall 有效)
|
||||
* 默认 Infinity;达到上限后销毁节点
|
||||
* 注意:与 t_times 语义不同——t_times 控制每波内 Interval 的次数
|
||||
*/
|
||||
trigger_limit?: number;
|
||||
}
|
||||
export const CardsUpSet: Record<number, number> = {
|
||||
1: 50,
|
||||
@@ -150,48 +184,48 @@ const waveToPoolLv: Record<number, number> = {
|
||||
|
||||
const SkillCardData: any[] = [
|
||||
// === 1波技能 ===
|
||||
{ uuid: 8301, skill: 6301, wave: 1, name: "护盾", info: "每2秒为1个英雄添加抵挡3次伤害的护盾", is_inst: false, t_times: 999, t_inv: 2, keep_waves: -1 },
|
||||
{ uuid: 8302, skill: 6302, wave: 1, name: "治疗", info: "每2秒治疗1个英雄", is_inst: false, t_times: 999, t_inv: 2, keep_waves: -1},
|
||||
{ uuid: 8705, skill: 0, wave: 1, name: "金币收益", info: "每回合金币收益+1", is_inst: false, keep_waves: -1, field: [7005] },
|
||||
{ uuid: 8706, skill: 0, wave: 1, name: "出售强化", info: "卖出英雄金币+1", is_inst: false, keep_waves: -1, field: [7006] },
|
||||
{ uuid: 8707, skill: 0, wave: 1, name: "战后恢复", info: "战斗结束生命回复量+10%", is_inst: false, keep_waves: -1, field: [7007] },
|
||||
{ uuid: 8301, skill: 6301, wave: 1, name: "护盾", info: "为伙伴/自己添加护盾,可抵挡3次伤害", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
{ uuid: 8302, skill: 6302, wave: 1, name: "治疗", info: "治疗伙伴/自己", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
{ uuid: 8705, skill: 0, wave: 1, name: "金币收益", info: "每回合金币收益+1", is_inst: false, keep_waves: -1, field: [7005], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8706, skill: 0, wave: 1, name: "出售强化", info: "卖出英雄金币+1", is_inst: false, keep_waves: -1, field: [7006], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8707, skill: 0, wave: 1, name: "战后恢复", info: "战斗结束生命回复量+10%", is_inst: false, keep_waves: -1, field: [7007], trigger_type: CardTriggerType.Field },
|
||||
|
||||
// === 5波技能 ===
|
||||
{ uuid: 8303, skill: 6303, wave: 5, name: "获取金币", info: "战斗阶段:每5秒增加1个金币", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8401, skill: 6401, wave: 5, name: "攻击强化", info: "战斗阶段:每5秒为全体友方攻击力提升5点", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8402, skill: 6402, wave: 5, name: "生命强化", info: "战斗阶段:每5秒为全体友方最大生命值提升20点", is_inst: false, keep_waves: -1 },
|
||||
{ uuid: 8403, skill: 6403, wave: 5, name: "暴击强化", info: "战斗阶段:每5秒为全体友方暴击率提升1%", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8404, skill: 6404, wave: 5, name: "暴伤强化", info: "战斗阶段:每5秒为全体友方暴击伤害提升2%", is_inst: false, keep_waves: -1 },
|
||||
{ uuid: 8405, skill: 6405, wave: 5, name: "击晕强化", info: "战斗阶段:每5秒为全体友方击晕概率提升1%", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8408, skill: 6408, wave: 5, name: "穿刺强化", info: "战斗阶段:每5秒为全体友方穿透概率提升2%", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8409, skill: 6409, wave: 5, name: "风怒强化", info: "战斗阶段:每5秒为全体友方风怒概率提升1%", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
// { uuid: 8501, skill: 6501, wave: 5, name: "复活", info: "ap 代表复活的生命值百分比", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8303, skill: 6303, wave: 5, name: "获取金币", info: "增加一定数量的金币", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
{ uuid: 8401, skill: 6401, wave: 5, name: "攻击强化", info: "全体友方攻击力提升5点,持续1次", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
{ uuid: 8402, skill: 6402, wave: 5, name: "生命强化", info: "全体友方最大生命值提升20点,持续1次", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
{ uuid: 8403, skill: 6403, wave: 5, name: "暴击强化", info: "全体友方暴击率提升10%,持续1次", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
{ uuid: 8404, skill: 6404, wave: 5, name: "暴伤强化", info: "全体友方暴击伤害提升20%,持续1次", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
{ uuid: 8405, skill: 6405, wave: 5, name: "击晕强化", info: "全体友方击晕概率提升10%,持续1次", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
{ uuid: 8408, skill: 6408, wave: 5, name: "穿刺强化", info: "全体友方穿透概率提升20%,持续1次", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
{ uuid: 8409, skill: 6409, wave: 5, name: "风怒强化", info: "全体友方风怒次数提升1次,持续1次", is_inst: true, keep_waves: -1, trigger_type: CardTriggerType.Instant },
|
||||
// { uuid: 8501, skill: 6501, wave: 5, name: "复活", info: "ap 代表复活的生命值百分比", is_inst: true, keep_waves: -1 },
|
||||
|
||||
// === 10波技能 ===
|
||||
{ uuid: 8708, skill: 0, wave: 10, name: "攻击加成", info: "英雄攻击力+10%", is_inst: false, keep_waves: -1, field: [7008] },
|
||||
{ uuid: 8709, skill: 0, wave: 10, name: "击晕加成", info: "英雄击晕概率+10%", is_inst: false, keep_waves: -1, field: [7009] },
|
||||
{ uuid: 8710, skill: 0, wave: 10, name: "暴击加成", info: "英雄暴击率+10%", is_inst: false, keep_waves: -1, field: [7010] },
|
||||
{ uuid: 8711, skill: 0, wave: 10, name: "暴伤加成", info: "英雄暴击伤害+20%", is_inst: false, keep_waves: -1, field: [7011] },
|
||||
{ uuid: 8712, skill: 0, wave: 10, name: "攻速加成", info: "英雄攻击速度+10%", is_inst: false, keep_waves: -1, field: [7012] },
|
||||
{ uuid: 8713, skill: 0, wave: 10, name: "购买优惠", info: "购买卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7013] },
|
||||
{ uuid: 8714, skill: 0, wave: 10, name: "刷新优惠", info: "刷新卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7014] },
|
||||
{ uuid: 8716, skill: 0, wave: 10, name: "生命加成", info: "英雄最大生命+10%", is_inst: false, keep_waves: -1, field: [7016] },
|
||||
{ uuid: 8717, skill: 0, wave: 10, name: "风怒加成", info: "英雄风怒概率+10%", is_inst: false, keep_waves: -1, field: [7017] },
|
||||
{ uuid: 8718, skill: 0, wave: 10, name: "穿刺加成", info: "英雄穿刺概率+10%", is_inst: false, keep_waves: -1, field: [7018] },
|
||||
{ uuid: 8708, skill: 0, wave: 10, name: "攻击加成", info: "英雄攻击力+10%", is_inst: false, keep_waves: -1, field: [7008], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8709, skill: 0, wave: 10, name: "击晕加成", info: "英雄击晕概率+10%", is_inst: false, keep_waves: -1, field: [7009], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8710, skill: 0, wave: 10, name: "暴击加成", info: "英雄暴击率+10%", is_inst: false, keep_waves: -1, field: [7010], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8711, skill: 0, wave: 10, name: "暴伤加成", info: "英雄暴击伤害+20%", is_inst: false, keep_waves: -1, field: [7011], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8712, skill: 0, wave: 10, name: "攻速加成", info: "英雄攻击速度+10%", is_inst: false, keep_waves: -1, field: [7012], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8713, skill: 0, wave: 10, name: "购买优惠", info: "购买卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7013], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8714, skill: 0, wave: 10, name: "刷新优惠", info: "刷新卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7014], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8716, skill: 0, wave: 10, name: "生命加成", info: "英雄最大生命+10%", is_inst: false, keep_waves: -1, field: [7016], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8717, skill: 0, wave: 10, name: "风怒加成", info: "英雄风怒概率+10%", is_inst: false, keep_waves: -1, field: [7017], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8718, skill: 0, wave: 10, name: "穿刺加成", info: "英雄穿刺概率+10%", is_inst: false, keep_waves: -1, field: [7018], trigger_type: CardTriggerType.Field },
|
||||
|
||||
// === 15波技能 ===
|
||||
{ uuid: 8701, skill: 0, wave: 15, name: "召唤强化", info: "召唤触发技能次数+1", is_inst: false, keep_waves: -1, field: [7001] },
|
||||
{ uuid: 8702, skill: 0, wave: 15, name: "死亡强化", info: "死亡触发技能次数+1", is_inst: false, keep_waves: -1, field: [7002] },
|
||||
{ uuid: 8703, skill: 0, wave: 15, name: "开场强化", info: "战斗开始触发技能次数+1", is_inst: false, keep_waves: -1, field: [7003] },
|
||||
{ uuid: 8704, skill: 0, wave: 15, name: "结束强化", info: "战斗结束触发技能次数+1", is_inst: false, keep_waves: -1, field: [7004] },
|
||||
{ uuid: 8701, skill: 0, wave: 15, name: "召唤强化", info: "召唤触发技能次数+1", is_inst: false, keep_waves: -1, field: [7001], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8702, skill: 0, wave: 15, name: "死亡强化", info: "死亡触发技能次数+1", is_inst: false, keep_waves: -1, field: [7002], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8703, skill: 0, wave: 15, name: "开场强化", info: "战斗开始触发技能次数+1", is_inst: false, keep_waves: -1, field: [7003], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8704, skill: 0, wave: 15, name: "结束强化", info: "战斗结束触发技能次数+1", is_inst: false, keep_waves: -1, field: [7004], trigger_type: CardTriggerType.Field },
|
||||
|
||||
// === 20波技能 ===
|
||||
{ uuid: 8201, skill: 6201, wave: 20, name: "雷墙", info: "召唤雷墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8202, skill: 6202, wave: 20, name: "火墙", info: "召唤火墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8203, skill: 6203, wave: 20, name: "飓风", info: "召唤飓风攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8204, skill: 6204, wave: 20, name: "水墙", info: "召唤水墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8205, skill: 6205, wave: 20, name: "风墙", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8206, skill: 6206, wave: 20, name: "陨石术", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8201, skill: 6201, wave: 20, name: "雷墙", info: "召唤雷墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval },
|
||||
{ uuid: 8202, skill: 6202, wave: 20, name: "火墙", info: "召唤火墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval },
|
||||
{ uuid: 8203, skill: 6203, wave: 20, name: "飓风", info: "召唤飓风攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval },
|
||||
{ uuid: 8204, skill: 6204, wave: 20, name: "水墙", info: "召唤水墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval },
|
||||
{ uuid: 8205, skill: 6205, wave: 20, name: "风墙", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval },
|
||||
{ uuid: 8206, skill: 6206, wave: 20, name: "陨石术", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval },
|
||||
];
|
||||
|
||||
SkillCardData.forEach(data => {
|
||||
@@ -211,7 +245,10 @@ SkillCardData.forEach(data => {
|
||||
t_times: data.t_times || (data.is_inst ? 1 : 999),
|
||||
t_inv: data.t_inv || 0,
|
||||
keep_waves: data.keep_waves,
|
||||
field: data.field
|
||||
field: data.field,
|
||||
overrides: data.overrides, // 【修复】原遗漏
|
||||
trigger_type: data.trigger_type, // 【新增】显式触发类型
|
||||
trigger_limit: data.trigger_limit, // 【新增】事件型触发次数上限
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export enum BoxSet {
|
||||
LETF_END = -360,
|
||||
RIGHT_END = 360,
|
||||
//游戏地平线
|
||||
GAME_LINE = -100,
|
||||
GAME_LINE = -90,
|
||||
}
|
||||
|
||||
export enum FacSet {
|
||||
|
||||
@@ -327,6 +327,10 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { SkillSet,} from "../common/config/SkillSet";
|
||||
import { HeroInfo } from "../common/config/heroSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { Tooltip } from "../skill/Tooltip";
|
||||
import { timedCom } from "../skill/timedCom";
|
||||
@@ -454,6 +455,12 @@ export class HeroViewComp extends CCComp {
|
||||
this.top_node.active = true;
|
||||
|
||||
this.status_change("idle");
|
||||
|
||||
// 【新增】仅英雄阵营派发复活成功事件,供卡牌技能(HeroCall 类型)监听
|
||||
// 统一在此派发可覆盖两条复活路径:复活技能触发 + 关卡战斗准备阶段恢复
|
||||
if (this.model && this.model.fac === FacSet.HERO && this.ent) {
|
||||
oops.message.dispatchEvent(GameEvent.ReviveSuccess, { eid: this.ent.eid });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -494,6 +494,9 @@ export class MissionComp extends CCComp {
|
||||
|
||||
// 战斗结束阶段,给予所有英雄恢复70%血量的技能效果
|
||||
this.healAllHeroes();
|
||||
|
||||
// 【新增】派发每波战斗结束事件,供卡牌技能监听(区别于整局结束的 MissionEnd)
|
||||
oops.message.dispatchEvent(GameEvent.FightEnd);
|
||||
break;
|
||||
|
||||
case MissionPhase.Settle:
|
||||
|
||||
@@ -55,12 +55,12 @@ export class MissionHeroComp extends CCComp {
|
||||
|
||||
/** 硬编码的6个英雄占位点 */
|
||||
public static readonly HERO_POSITIONS: Vec3[] = [
|
||||
v3(-210, BoxSet.GAME_LINE + 90, 0), // index 0 (node_index 1): Top Front
|
||||
v3(-160, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front
|
||||
v3(-210, BoxSet.GAME_LINE - 90, 0), // index 2 (node_index 3): Bot Front
|
||||
v3(-300, BoxSet.GAME_LINE + 90, 0), // index 3 (node_index 4): Top Back
|
||||
v3(-300, BoxSet.GAME_LINE, 0), // index 4 (node_index 5): Mid Back
|
||||
v3(-300, BoxSet.GAME_LINE - 90, 0), // index 5 (node_index 6): Bot Back
|
||||
v3(-175, BoxSet.GAME_LINE + 100, 0), // index 0 (node_index 1): Top Front
|
||||
v3(-170, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front
|
||||
v3(-175, BoxSet.GAME_LINE - 100, 0), // index 2 (node_index 3): Bot Front
|
||||
v3(-280, BoxSet.GAME_LINE + 100, 0), // index 3 (node_index 4): Top Back
|
||||
v3(-280, BoxSet.GAME_LINE, 0), // index 4 (node_index 5): Mid Back
|
||||
v3(-280, BoxSet.GAME_LINE - 100, 0), // index 5 (node_index 6): Bot Back
|
||||
];
|
||||
|
||||
/** 英雄出生时的掉落高度(从空中落到地面的像素差) */
|
||||
|
||||
@@ -59,21 +59,21 @@ export class MissionMonCompComp extends CCComp {
|
||||
/** 硬编码的 12 个怪物占位点 (3行4列) */
|
||||
public static readonly MON_POSITIONS: Vec3[] = [
|
||||
// 第 1 列 (X=60)
|
||||
v3(60, BoxSet.GAME_LINE + 90, 0), // index 0: Top
|
||||
v3(60, BoxSet.GAME_LINE, 0), // index 1: Mid
|
||||
v3(60, BoxSet.GAME_LINE - 90, 0), // index 2: Bot
|
||||
v3(0, BoxSet.GAME_LINE + 100, 0), // index 0: Top
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 1: Mid
|
||||
v3(0, BoxSet.GAME_LINE - 100, 0), // index 2: Bot
|
||||
// 第 2 列 (X=140)
|
||||
v3(140, BoxSet.GAME_LINE + 90, 0), // index 3: Top
|
||||
v3(140, BoxSet.GAME_LINE, 0), // index 4: Mid
|
||||
v3(140, BoxSet.GAME_LINE - 90, 0), // index 5: Bot
|
||||
v3(90, BoxSet.GAME_LINE + 100, 0), // index 3: Top
|
||||
v3(90, BoxSet.GAME_LINE, 0), // index 4: Mid
|
||||
v3(90, BoxSet.GAME_LINE - 100, 0), // index 5: Bot
|
||||
// 第 3 列 (X=220)
|
||||
v3(220, BoxSet.GAME_LINE + 90, 0), // index 6: Top
|
||||
v3(220, BoxSet.GAME_LINE, 0), // index 7: Mid
|
||||
v3(220, BoxSet.GAME_LINE - 90, 0), // index 8: Bot
|
||||
v3(180, BoxSet.GAME_LINE + 100, 0), // index 6: Top
|
||||
v3(180, BoxSet.GAME_LINE, 0), // index 7: Mid
|
||||
v3(180, BoxSet.GAME_LINE - 100, 0), // index 8: Bot
|
||||
// 第 4 列 (X=300)
|
||||
v3(300, BoxSet.GAME_LINE + 90, 0), // index 9: Top
|
||||
v3(300, BoxSet.GAME_LINE, 0), // index 10: Mid
|
||||
v3(300, BoxSet.GAME_LINE - 90, 0), // index 11: Bot
|
||||
v3(270, BoxSet.GAME_LINE + 100, 0), // index 9: Top
|
||||
v3(270, BoxSet.GAME_LINE, 0), // index 10: Mid
|
||||
v3(270, BoxSet.GAME_LINE - 100, 0), // index 11: Bot
|
||||
];
|
||||
|
||||
// ======================== 编辑器属性 ========================
|
||||
|
||||
@@ -4,23 +4,27 @@
|
||||
*
|
||||
* 职责:
|
||||
* 1. 表示一张已使用的技能卡在战场上的 **可视化实体**。
|
||||
* 2. 管理技能的 **触发逻辑**:即时触发 vs 定时触发(战斗中按间隔触发)。
|
||||
* 2. 按 trigger_type 类型化分发触发逻辑(即时 / 定时 / 驻场 / 事件型)。
|
||||
* 3. 显示技能图标和剩余触发次数。
|
||||
* 4. 触发结束后自动销毁。
|
||||
*
|
||||
* 关键设计:
|
||||
* - is_instant=true(即时技能):init 时立即触发一次,播放后延迟销毁。
|
||||
* - is_instant=false(持续技能):战斗中每隔 trigger_interval 秒触发一次,
|
||||
* 共触发 trigger_times 次后销毁。
|
||||
* - 新一波(NewWave)时如果持续技能的次数已用完则销毁。
|
||||
* - 销毁时通过 GameEvent.RemoveSkillBox 通知 MissSkillsComp 回收槽位。
|
||||
* 触发类型(CardTriggerType):
|
||||
* - Instant (1):init 时立即触发一次(按 t_times 控制次数,跨波次 NewWave 时再次触发)
|
||||
* - Interval (2):监听 FightStart → update 帧驱动按 t_inv 间隔重复触发(按 t_times 控制每波次数)
|
||||
* - Field (3):被动生效,不主动施法(实际由 FieldSkillSet 处理)
|
||||
* - FightStart (4):监听 FightStart 事件,按 trigger_limit 全局累计上限
|
||||
* - FightEnd (5):监听 FightEnd 事件(每波结束派发),按 trigger_limit 全局累计上限
|
||||
* - HeroDead (6):监听 HeroDead 事件(仅英雄阵营派发,怪物死亡不触发)
|
||||
* - HeroCall (7):监听 MasterCalled(主角/技能召唤)+ ReviveSuccess(复活)
|
||||
*
|
||||
* 触发技能的方式:
|
||||
* - 通过 GameEvent.TriggerSkill 事件,将技能 UUID、卡牌等级、
|
||||
* 触发位置等信息分发给技能系统。
|
||||
* 关键设计:
|
||||
* - 事件型(4-7)统一走 onEventTrigger 入口,仅作触发信号,不读取 payload
|
||||
* - 触发上限:Instant/Interval 按 t_times(每波内),事件型按 trigger_limit(全局)
|
||||
* - 跨波次:keep_waves 控制存活;事件型 trigger_count 不随波次重置
|
||||
* - 销毁时通过 GameEvent.RemoveSkillBox 通知 MissSkillsComp 回收槽位
|
||||
*
|
||||
* 依赖:
|
||||
* - CardPoolList(CardSet)—— 查询技能卡的触发配置(t_times / t_inv / is_inst)
|
||||
* - CardPoolList / CardTriggerType(CardSet)—— 查询技能卡的触发配置
|
||||
* - SkillSet —— 技能静态配置(icon 字段)
|
||||
* - GameEvent —— 各类游戏事件
|
||||
* - smc.mission —— 游戏运行状态
|
||||
@@ -29,7 +33,7 @@ import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas, tween, v3, Tween, NodeEventType } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { CardPoolList } from "../common/config/CardSet";
|
||||
import { CardPoolList, CardTriggerType } from "../common/config/CardSet";
|
||||
import { SkillSet, SkillOverrides } from "../common/config/SkillSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
@@ -79,6 +83,15 @@ export class SkillBoxComp extends CCComp {
|
||||
/** 驻场技能 UUID 列表 */
|
||||
public field: number[] = [];
|
||||
|
||||
// ======================== 触发类型化扩展 ========================
|
||||
|
||||
/** 触发类型(默认即时,保持向后兼容) */
|
||||
private trigger_type: CardTriggerType = CardTriggerType.Instant;
|
||||
/** 事件型触发的全局次数上限(Infinity 表示无上限) */
|
||||
private trigger_limit: number = Infinity;
|
||||
/** 事件型已触发次数 */
|
||||
private trigger_count: number = 0;
|
||||
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
/** 已触发次数 */
|
||||
@@ -92,9 +105,13 @@ export class SkillBoxComp extends CCComp {
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
/** 注册战斗开始、任务结束、新一波等事件 */
|
||||
/**
|
||||
* 注册全局事件:
|
||||
* - MissionEnd:所有类型都需要监听,任务结束时强制销毁
|
||||
* - NewWave:处理 keep_waves 跨波次逻辑(所有类型统一)
|
||||
* - 其它触发事件由 registerTrigger 按 trigger_type 动态注册
|
||||
*/
|
||||
onLoad() {
|
||||
oops.message.on(GameEvent.FightStart, this.onFightStart, this);
|
||||
oops.message.on(GameEvent.MissionEnd, this.onMissionEnd, this);
|
||||
this.node.on(GameEvent.NewWave, this.onNewWave, this);
|
||||
oops.message.on(GameEvent.NewWave, this.onNewWaveGlobal, this);
|
||||
@@ -104,8 +121,15 @@ export class SkillBoxComp extends CCComp {
|
||||
/** 销毁时移除所有事件监听并通知槽位管理器回收 */
|
||||
onDestroy() {
|
||||
super.onDestroy();
|
||||
oops.message.off(GameEvent.FightStart, this.onFightStart, this);
|
||||
// 统一 off 所有可能订阅的事件(即使未订阅也无副作用)
|
||||
// 注意:FightStart 可能由两种回调订阅(Interval→onFightStart / FightStart触发型→onEventTrigger),都需要 off
|
||||
oops.message.off(GameEvent.MissionEnd, this.onMissionEnd, this);
|
||||
oops.message.off(GameEvent.FightStart, this.onFightStart, this);
|
||||
oops.message.off(GameEvent.FightStart, this.onEventTrigger, this);
|
||||
oops.message.off(GameEvent.FightEnd, this.onEventTrigger, this);
|
||||
oops.message.off(GameEvent.HeroDead, this.onEventTrigger, this);
|
||||
oops.message.off(GameEvent.MasterCalled, this.onEventTrigger, this);
|
||||
oops.message.off(GameEvent.ReviveSuccess, this.onEventTrigger, this);
|
||||
if (this.node && this.node.isValid) {
|
||||
this.node.off(GameEvent.NewWave, this.onNewWave, this);
|
||||
this.node.off(NodeEventType.TOUCH_END, this.onNodeClicked, this);
|
||||
@@ -128,9 +152,9 @@ export class SkillBoxComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 初始化技能卡效果:
|
||||
* 1. 从 CardPoolList 查询技能卡的触发配置。
|
||||
* 1. 从 CardPoolList 查询技能卡的触发配置(含 trigger_type)。
|
||||
* 2. 更新 UI 显示(图标 + 次数)。
|
||||
* 3. 即时技能立即触发一次;若次数已满则延迟销毁。
|
||||
* 3. 按 trigger_type 注册对应事件监听并执行首次触发。
|
||||
*
|
||||
* @param uuid 卡牌 UUID
|
||||
* @param card_lv 技能卡等级
|
||||
@@ -148,37 +172,135 @@ export class SkillBoxComp extends CCComp {
|
||||
this.keep_waves = config.keep_waves ?? 0;
|
||||
this.overrides = config.overrides;
|
||||
this.field = config.field || [];
|
||||
// 读取触发类型与上限(兜底默认值,避免 undefined)
|
||||
this.trigger_type = config.trigger_type ?? CardTriggerType.Instant;
|
||||
this.trigger_limit = config.trigger_limit ?? Infinity;
|
||||
} else {
|
||||
this.s_uuid = uuid;
|
||||
}
|
||||
|
||||
this.current_trigger_times = 0;
|
||||
this.trigger_count = 0;
|
||||
this.timer = 0;
|
||||
this.initialized = true;
|
||||
|
||||
this.updateUI();
|
||||
|
||||
if (this.is_instant) {
|
||||
// 即时技能:立即触发
|
||||
// 按 trigger_type 注册事件监听 + 执行首次触发
|
||||
this.registerTrigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 trigger_type 注册对应事件监听:
|
||||
* - Instant: init 时立即触发一次(保持旧行为)
|
||||
* - Interval: 监听 FightStart,进入战斗后由 update 帧驱动计时
|
||||
* - Field: 不主动施法(实际生效由 FieldSkillSet 处理)
|
||||
* - FightStart: 监听 FightStart 事件
|
||||
* - FightEnd: 监听 FightEnd 事件
|
||||
* - HeroDead: 监听 HeroDead 事件(已在派发处做阵营过滤)
|
||||
* - HeroCall: 监听 MasterCalled(主角/技能召唤)+ ReviveSuccess(复活)
|
||||
*
|
||||
* 注意:MasterCalled 各派发点 payload 不一致,onEventTrigger 仅作触发信号使用。
|
||||
*/
|
||||
private registerTrigger(): void {
|
||||
switch (this.trigger_type) {
|
||||
case CardTriggerType.Instant:
|
||||
// 即时技能:立即触发一次
|
||||
this.onEventTrigger();
|
||||
break;
|
||||
|
||||
case CardTriggerType.Interval:
|
||||
// 定时循环:监听 FightStart 进入战斗后启动计时
|
||||
oops.message.on(GameEvent.FightStart, this.onFightStart, this);
|
||||
break;
|
||||
|
||||
case CardTriggerType.Field:
|
||||
// 驻场光环:不主动施法,由 FieldSkillSet 处理
|
||||
break;
|
||||
|
||||
case CardTriggerType.FightStart:
|
||||
oops.message.on(GameEvent.FightStart, this.onEventTrigger, this);
|
||||
break;
|
||||
|
||||
case CardTriggerType.FightEnd:
|
||||
oops.message.on(GameEvent.FightEnd, this.onEventTrigger, this);
|
||||
break;
|
||||
|
||||
case CardTriggerType.HeroDead:
|
||||
oops.message.on(GameEvent.HeroDead, this.onEventTrigger, this);
|
||||
break;
|
||||
|
||||
case CardTriggerType.HeroCall:
|
||||
// 同时监听召唤和复活两类英雄上场事件
|
||||
oops.message.on(GameEvent.MasterCalled, this.onEventTrigger, this);
|
||||
oops.message.on(GameEvent.ReviveSuccess, this.onEventTrigger, this);
|
||||
break;
|
||||
|
||||
default:
|
||||
mLogger.warn(true, 'SkillBoxComp', `[registerTrigger] unknown trigger_type: ${this.trigger_type}, fallback to Instant`);
|
||||
this.onEventTrigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件型触发的统一入口:
|
||||
* - Instant 类型:按 trigger_times 上限判定,复用 current_trigger_times 跟踪
|
||||
* (保持与原 is_instant 行为一致,且 NewWave 中也用 current_trigger_times)
|
||||
* - 事件型(FightStart/FightEnd/HeroDead/HeroCall):按 trigger_limit 上限判定,使用 trigger_count 跟踪
|
||||
*
|
||||
* 注意:本方法不读取事件 payload,仅作触发信号使用(避免 MasterCalled 不同 payload 字段引发的兼容问题)。
|
||||
*/
|
||||
private onEventTrigger(): void {
|
||||
if (!this.initialized) return;
|
||||
|
||||
if (this.trigger_type === CardTriggerType.Instant) {
|
||||
// 即时触发:上限由 trigger_times 控制(保持旧行为)
|
||||
if (this.current_trigger_times >= this.trigger_times) {
|
||||
this.destroySelf();
|
||||
return;
|
||||
}
|
||||
this.triggerSkill();
|
||||
this.current_trigger_times++;
|
||||
this.updateUI();
|
||||
|
||||
// 单次触发 + 不跨波次维持 → 延迟销毁(保留短暂视觉反馈)
|
||||
if (this.keep_waves === 0 && this.current_trigger_times >= this.trigger_times) {
|
||||
// 次数已满且不跨波次维持 → 延迟 1 秒后销毁(保留短暂视觉反馈)
|
||||
this.scheduleOnce(() => {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
}, 1.0);
|
||||
this.scheduleOnce(() => this.destroySelf(), 1.0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 事件型:上限由 trigger_limit 控制(全局累计,跨波次不重置)
|
||||
if (this.trigger_count >= this.trigger_limit) {
|
||||
this.destroySelf();
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerSkill();
|
||||
this.trigger_count++;
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一的节点销毁封装:
|
||||
* 优先通过 ECS 实体销毁;否则直接销毁节点。
|
||||
*/
|
||||
private destroySelf(): void {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 UI:
|
||||
* - 图标:从 uicons 图集获取。
|
||||
* - 剩余次数:持续技能显示剩余数字,即时技能不显示。
|
||||
* - 图标:从 uicons 图集获取
|
||||
* - 剩余次数标签:
|
||||
* * Interval / 事件型:显示剩余次数(按各自上限计算)
|
||||
* * Instant / Field:不显示
|
||||
* - CD 遮罩:仅 Interval 类型展示冷却进度
|
||||
*/
|
||||
updateUI() {
|
||||
// 加载技能图标
|
||||
@@ -193,13 +315,29 @@ export class SkillBoxComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新剩余次数标签
|
||||
// 是否需要展示剩余次数
|
||||
const showRemainCount =
|
||||
this.trigger_type === CardTriggerType.Interval ||
|
||||
this.trigger_type === CardTriggerType.FightStart ||
|
||||
this.trigger_type === CardTriggerType.FightEnd ||
|
||||
this.trigger_type === CardTriggerType.HeroDead ||
|
||||
this.trigger_type === CardTriggerType.HeroCall;
|
||||
|
||||
if (this.info_label) {
|
||||
if (!this.is_instant) {
|
||||
if (this.trigger_interval <= 0 && this.field && this.field.length > 0) {
|
||||
this.info_label.string = ""; // 纯驻场技能不显示剩余次数
|
||||
if (showRemainCount) {
|
||||
// 事件型按 trigger_limit;Interval 按 t_times
|
||||
const isEvent =
|
||||
this.trigger_type === CardTriggerType.FightStart ||
|
||||
this.trigger_type === CardTriggerType.FightEnd ||
|
||||
this.trigger_type === CardTriggerType.HeroDead ||
|
||||
this.trigger_type === CardTriggerType.HeroCall;
|
||||
const used = isEvent ? this.trigger_count : this.current_trigger_times;
|
||||
const total = isEvent ? this.trigger_limit : this.trigger_times;
|
||||
if (isEvent && !isFinite(total)) {
|
||||
// 无上限:显示已触发次数
|
||||
this.info_label.string = `${used}`;
|
||||
} else {
|
||||
const remain = Math.max(0, this.trigger_times - this.current_trigger_times);
|
||||
const remain = Math.max(0, Math.floor(total) - used);
|
||||
this.info_label.string = `${remain}`;
|
||||
}
|
||||
} else {
|
||||
@@ -207,14 +345,14 @@ export class SkillBoxComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化或重置 CD 遮罩表现
|
||||
// 初始化或重置 CD 遮罩表现(仅 Interval 类型有冷却进度)
|
||||
if (this.cd_mask && this.cd_mask.isValid) {
|
||||
let sprite = this.cd_mask.getComponent(Sprite);
|
||||
if (sprite) {
|
||||
if (this.is_instant || this.trigger_interval <= 0) {
|
||||
sprite.fillRange = 0; // 无需冷却(包括驻场光环卡),直接归 0
|
||||
} else {
|
||||
if (this.trigger_type === CardTriggerType.Interval && this.trigger_interval > 0) {
|
||||
sprite.fillRange = Math.max(0, 1 - (this.timer / this.trigger_interval));
|
||||
} else {
|
||||
sprite.fillRange = 0; // 非冷却类型直接归 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,14 +360,17 @@ export class SkillBoxComp extends CCComp {
|
||||
|
||||
// ======================== 战斗状态事件 ========================
|
||||
|
||||
/** 战斗开始:标记进入战斗状态,持续技能开始计时 */
|
||||
/**
|
||||
* 战斗开始回调:
|
||||
* - 仅 Interval 类型在 registerTrigger 中订阅此事件
|
||||
* - 标记进入战斗状态,启动计时器(实际触发由 update 帧驱动)
|
||||
*
|
||||
* 注意:FightStart 触发型(CardTriggerType.FightStart)的事件回调是 onEventTrigger,不是本方法。
|
||||
*/
|
||||
private onFightStart() {
|
||||
if (!this.initialized) return;
|
||||
this.in_combat = true;
|
||||
|
||||
if (!this.is_instant) {
|
||||
this.timer = 0; // 重置计时器
|
||||
}
|
||||
this.timer = 0; // 重置计时器
|
||||
}
|
||||
|
||||
/** 节点级新一波事件处理 */
|
||||
@@ -245,76 +386,79 @@ export class SkillBoxComp extends CCComp {
|
||||
/**
|
||||
* 新一波:退出战斗状态。
|
||||
* 处理维持波次逻辑:递减剩余波次,或者重置触发次数。
|
||||
*
|
||||
* 各类型在新一波的行为:
|
||||
* - Instant/Interval/FightStart/FightEnd:按 keep_waves 决定维持/销毁,并在新一波开始时重置本地计数
|
||||
* - Field:被动生效,跟随 keep_waves 决定存活
|
||||
* - HeroDead/HeroCall:跨波次触发的事件型,trigger_count(全局)不重置,仅 keep_waves 控制存活
|
||||
*/
|
||||
private handleNewWave() {
|
||||
if (!this.initialized) return;
|
||||
this.in_combat = false;
|
||||
|
||||
// 事件型触发(HeroDead / HeroCall):trigger_count 全局累计,不随波次重置
|
||||
const isGlobalEventType =
|
||||
this.trigger_type === CardTriggerType.HeroDead ||
|
||||
this.trigger_type === CardTriggerType.HeroCall;
|
||||
|
||||
if (this.keep_waves !== 0) {
|
||||
if (this.keep_waves > 0) {
|
||||
this.keep_waves--;
|
||||
if (this.keep_waves <= 0) {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
this.destroySelf();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 能够跨波次维持,重置触发次数和计时器,以便新一波继续触发
|
||||
this.current_trigger_times = 0;
|
||||
// 跨波次维持:重置本地计数与计时器(事件型 trigger_count 不重置)
|
||||
if (!isGlobalEventType) {
|
||||
this.current_trigger_times = 0;
|
||||
this.trigger_count = 0;
|
||||
}
|
||||
this.timer = 0;
|
||||
|
||||
// 即时技能在新一波开始时立即触发一次
|
||||
if (this.is_instant) {
|
||||
|
||||
// 即时/事件型触发一次(保持旧行为:Instant 在新一波开始立即触发一次)
|
||||
if (this.trigger_type === CardTriggerType.Instant) {
|
||||
this.triggerSkill();
|
||||
this.current_trigger_times++;
|
||||
}
|
||||
this.updateUI();
|
||||
} else {
|
||||
// 默认逻辑:不跨波次维持
|
||||
if (!this.is_instant) {
|
||||
if (this.current_trigger_times >= this.trigger_times) {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
}
|
||||
// 不跨波次维持:达到上限即销毁
|
||||
// - Interval / Instant:按 t_times 判定
|
||||
// - 事件型:按 trigger_limit 判定
|
||||
const reachedLimit = isGlobalEventType
|
||||
? this.trigger_count >= this.trigger_limit
|
||||
: this.current_trigger_times >= this.trigger_times;
|
||||
|
||||
if (reachedLimit) {
|
||||
this.destroySelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 任务结束:强制销毁 */
|
||||
private onMissionEnd() {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
this.destroySelf();
|
||||
}
|
||||
|
||||
// ======================== 帧更新 ========================
|
||||
|
||||
/**
|
||||
* 每帧更新(仅对持续技能生效):
|
||||
* - 累加计时器,达到 trigger_interval 时触发一次技能。
|
||||
* - 触发后重置计时器并更新 UI。
|
||||
* - 总次数用完后延迟销毁。
|
||||
* 每帧更新:
|
||||
* - 仅 Interval 类型走帧驱动计时逻辑(其它类型提前 return)
|
||||
* - 累加计时器,达到 trigger_interval 时触发一次技能
|
||||
* - 触发后重置计时器并更新 UI
|
||||
* - 总次数用完后延迟销毁
|
||||
*/
|
||||
update(dt: number) {
|
||||
if (!this.initialized || !this.in_combat || this.is_instant) return;
|
||||
// 收窄:仅 Interval 类型走帧驱动
|
||||
if (this.trigger_type !== CardTriggerType.Interval) return;
|
||||
if (!this.initialized || !this.in_combat) return;
|
||||
if (!smc.mission.play || smc.mission.pause) return;
|
||||
|
||||
// 如果是纯驻场光环技能且无触发间隔,则不执行定期触发逻辑
|
||||
if (this.trigger_interval <= 0 && this.field && this.field.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.current_trigger_times < this.trigger_times) {
|
||||
this.timer += dt;
|
||||
|
||||
|
||||
// 更新 CD 遮罩 (fillRange 从 1 降到 0)
|
||||
if (this.cd_mask && this.cd_mask.isValid && this.trigger_interval > 0) {
|
||||
let sprite = this.cd_mask.getComponent(Sprite);
|
||||
@@ -331,13 +475,7 @@ export class SkillBoxComp extends CCComp {
|
||||
|
||||
// 次数用完且不跨波次维持 → 延迟销毁
|
||||
if (this.keep_waves === 0 && this.current_trigger_times >= this.trigger_times) {
|
||||
this.scheduleOnce(() => {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
}, 0.5);
|
||||
this.scheduleOnce(() => this.destroySelf(), 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
docs/superpowers/skill_card_trigger_refactor.md
Normal file
59
docs/superpowers/skill_card_trigger_refactor.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 技能卡触发机制重构方案(已废弃)
|
||||
|
||||
> ⚠️ **本文档已废弃**,被以下执行计划取代:
|
||||
> [`2026-06-19-card-skill-trigger-type-refactor-plan.md`](./2026-06-19-card-skill-trigger-type-refactor-plan.md)
|
||||
>
|
||||
> 废弃原因:本草案存在 3 处关键错误,已在新计划中修正:
|
||||
> 1. 缺少 `Instant` 类型,导致现有即时卡牌无法归类
|
||||
> 2. `BattleEnd` 错误映射到 `MissionEnd`(整局结束),应为 `FightEnd`(每波结束)
|
||||
> 3. `HeroDead` 未提阵营过滤,会导致怪物死亡误触发
|
||||
>
|
||||
> 保留本文档仅作历史记录,请勿参考。
|
||||
|
||||
---
|
||||
|
||||
## 旧草案内容(仅供参考)
|
||||
|
||||
### 原始需求背景
|
||||
当前技能卡(SkillCardData)在 `CardSet.ts` 中仅通过 `is_inst`(是否即时)和 `t_inv`(触发间隔)隐式区分类型。随着技能丰富,需要:
|
||||
1. 明确定义卡牌技能的触发类型(如:驻场、定时)。
|
||||
2. 新增类似于英雄生命周期的触发时机:战斗开始、战斗结束、场上英雄死亡、英雄召唤上场。
|
||||
|
||||
### 原始修改方案
|
||||
|
||||
#### 1. 明确技能触发类型 (`CardSet.ts`)
|
||||
新增枚举 `CardSkillType`,用于明确区分卡牌技能的触发时机:
|
||||
```typescript
|
||||
export enum CardSkillType {
|
||||
Interval = 1, // 间隔定时触发 (战斗中每隔N秒执行)
|
||||
Field = 2, // 驻场技能 (被动光环)
|
||||
BattleStart = 3, // 战斗开始时触发一次
|
||||
BattleEnd = 4, // 战斗结束时触发一次
|
||||
HeroDead = 5, // 场上己方英雄死亡时触发
|
||||
HeroCall = 6, // 场上己方英雄召唤上场时触发
|
||||
}
|
||||
```
|
||||
|
||||
> ❌ **错误 1**:缺少 `Instant` 类型,现有 `is_inst: true` 的卡牌(8301护盾、8302治疗等)无法归类。
|
||||
|
||||
#### 2. 完善事件派发机制
|
||||
为支持新的触发类型,确保相关事件被正确派发:
|
||||
- **英雄召唤上场 (`GameEvent.MasterCalled`)**:已在 `Hero.ts` 中实现。
|
||||
- **英雄死亡 (`GameEvent.HeroDead`)**:需在 `HeroAtkSystem.ts` 中的英雄死亡逻辑里,补充派发 `GameEvent.HeroDead` 事件,供技能盒子监听。
|
||||
- **战斗开始/结束 (`GameEvent.FightStart` / `GameEvent.MissionEnd`)**:已支持。
|
||||
|
||||
> ❌ **错误 2**:`BattleEnd` 映射到 `MissionEnd` 是错的——MissionEnd 是整局任务结束,不是每波战斗结束。
|
||||
> ❌ **错误 3**:HeroDead 未提阵营过滤,怪物死亡会误触发。
|
||||
|
||||
#### 3. 重构技能盒子逻辑 (`SkillBoxComp.ts`)
|
||||
修改 `SkillBoxComp`,使其根据 `trigger_type` 进行不同的监听与触发:
|
||||
|
||||
- **属性定义**:新增解析并保存 `trigger_type`。
|
||||
- **事件监听**:在 `onLoad` 或 `init` 后根据 `trigger_type` 注册相应的监听:
|
||||
- `CardSkillType.BattleStart`: 监听 `GameEvent.FightStart`。
|
||||
- `CardSkillType.BattleEnd`: 监听 `GameEvent.MissionEnd`。
|
||||
- `CardSkillType.HeroDead`: 监听 `GameEvent.HeroDead`。
|
||||
- `CardSkillType.HeroCall`: 监听 `GameEvent.MasterCalled`。
|
||||
- **触发处理**:
|
||||
- 每当监听到对应事件,调用 `triggerSkill()` 释放技能,并累加触发次数。
|
||||
- 若已达最大触发次数,则销毁节点。
|
||||
@@ -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