feat(map): 新增固定波次技能三选一弹窗系统

1.  新增MSkillBoxComp弹窗组件,实现固定波次触发的技能卡选择功能
2.  新增SkillBoxCardConfig配置与SkillBoxPool技能池,支持按波次配置技能
3.  重构MissionCardComp,将技能卡抽取改为固定波次弹窗触发
4.  扩展SingletonModuleComp与MissionComp,添加技能刷新次数持久化逻辑
5.  优化MissSkillsComp,新增SkillBox专属技能加载流程
6.  修复SkillBoxComp,支持自定义技能参数覆盖
7.  调整UIConfig与CardSet配置,适配新的技能卡流程
This commit is contained in:
pan
2026-06-03 16:36:22 +08:00
parent 1871551fca
commit e76cba7933
10 changed files with 2015 additions and 2773 deletions

View File

@@ -0,0 +1,266 @@
/**
* @file MSkillBoxComp.ts
* @description 固定波次触发的「技能卡三选一」弹窗组件UI 视图层 + 流程控制)
*
* 职责:
* 1. 由 MissionCardComp 在 NewWave 事件且当前波次 ∈ {1, 5, 10, 15, 20} 时
* 通过 `oops.gui.open(UIID.SkillBox, { wave, poolLv })` 弹出。
* 2. 从 SkillBoxPool[wave] 抽 3 张卡,渲染到 card1 / card2 / card3 三个 CardComp 槽位。
* 3. 玩家点中其中一张卡后,直接以该卡为 payload 分发 UseSkillCard 事件,
* 由 MissSkillsComp 接管并实例化 SkillBoxComp(走 SkillBoxCardConfig 路径)。
* 4. 提供「刷新」按钮(扣除 refreshRemain 一次)和「看广告刷新」按钮(回调留空,
* 待接入广告 SDK 后会把 adRefreshRemain 累加 +3)。
*
* 关键设计:
* - **强制必选**:不绑定关闭按钮,玩家只能点中 3 张卡之一才能关闭弹窗。
* - **免费领取**:cost 强制 0,dispatchEvent(UseSkillCard) 不走 CoinAdd 扣费。
* - **次数持久化**:refreshRemain / adRefreshRemain 存于 smc.vmdata.mission_data,
* 跨波次保留,每局 mission 开始时由 MissionComp.data_init() 重置为 1/0。
*
* 依赖:
* - CardComp —— 复用其渲染/交互逻辑(免费版:card_cost=0)
* - SkillBoxPool / getSkillBoxCards (CardSet) —— 5 级硬编码技能池
* - GameEvent.UseSkillCard —— 技能使用事件
* - UIID.SkillBox (GameUIConfig) —— 弹窗 prefab 路径
* - smc.vmdata.mission_data —— 刷新次数持久化
*/
import { mLogger } from "../common/Logger";
import { _decorator, Label, Node, Button } 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 { GameEvent } from "../common/config/GameEvent";
import { oops } from "db://oops-framework/core/Oops";
import { smc } from "../common/SingletonModuleComp";
import { getSkillBoxCards, SkillBoxCardConfig } from "../common/config/CardSet";
import { UIID } from "../common/config/GameUIConfig";
import { CardComp } from "./CardComp";
const { ccclass, property } = _decorator;
/**
* MSkillBoxComp —— 技能三选一弹窗控制器
*
* 由 oops.gui.open(UIID.SkillBox, { wave, poolLv }) 实例化。
*/
@ccclass('MSkillBoxComp')
@ecs.register('MSkillBoxComp', false)
export class MSkillBoxComp extends CCComp {
/** 是否启用调试日志 */
private debugMode: boolean = true;
// ======================== 编辑器绑定节点 ========================
/** 卡牌槽位 1 节点(需挂 CardComp) */
@property(Node)
card1: Node = null!
/** 卡牌槽位 2 节点(需挂 CardComp) */
@property(Node)
card2: Node = null!
/** 卡牌槽位 3 节点(需挂 CardComp) */
@property(Node)
card3: Node = null!
/** 刷新按钮(消耗 1 次 refreshRemain 重抽 3 张) */
@property(Node)
refreshBtn: Node = null!
/** 看广告刷新按钮(回调留空,后续接入广告 SDK 后累加 adRefreshRemain +3) */
@property(Node)
adRefreshBtn: Node = null!
/** 刷新次数显示标签(可选,显示 "1/1" 形式) */
@property(Label)
refreshCountLabel: Label = null!
// ======================== 运行时状态 ========================
/** 当前波次(1/5/10/15/20) */
private currentWave: number = 0;
/** 当前卡池等级(预留扩展,目前未使用) */
private currentPoolLv: number = 1;
/** 3 个 CardComp 子控制器引用(有序) */
private cardComps: CardComp[] = [];
/** 是否已派发 UseSkillCard(防重复触发关闭) */
private hasPicked: boolean = false;
// ======================== 生命周期 ========================
onLoad() {
// 监听任务结束,自动关闭弹窗(避免玩家关游戏时残留)
oops.message.on(GameEvent.MissionEnd, this.onMissionEnd, this);
this.bindEvents();
}
onAdded(args: { wave?: number; poolLv?: number }) {
this.currentWave = Math.max(0, Math.floor(Number(args?.wave ?? 0)));
this.currentPoolLv = Math.max(1, Math.floor(Number(args?.poolLv ?? 1)));
this.hasPicked = false;
this.cacheCardComps();
this.rollAndRender();
this.refreshRefreshCountUI();
mLogger.log(this.debugMode, "MSkillBoxComp", "opened", {
wave: this.currentWave,
poolLv: this.currentPoolLv,
refreshRemain: this.getRefreshRemain(),
adRefreshRemain: this.getAdRefreshRemain(),
});
}
onDestroy() {
super.onDestroy();
this.unbindEvents();
oops.message.off(GameEvent.MissionEnd, this.onMissionEnd, this);
}
onMissionEnd() {
// 任务结束时强制关闭弹窗
oops.gui.remove(UIID.SkillBox);
}
init() {
// 弹窗组件无需额外 init,onAdded 阶段完成所有初始化
}
update(dt: number) {
// 弹窗无帧更新逻辑
}
reset() {
// ECS 组件移除时清理
}
// ======================== 内部:CardComp 缓存 ========================
/**
* 缓存 3 个卡槽上的 CardComp 引用。
* 与 MissionCardComp.cacheCardComps() 同源实现,确保即开即用。
*/
private cacheCardComps() {
this.cardComps = [];
const slots: (Node | null)[] = [this.card1, this.card2, this.card3];
for (let i = 0; i < slots.length; i++) {
const node = slots[i];
if (!node) continue;
const comp = node.getComponent(CardComp) || node.addComponent(CardComp);
this.cardComps.push(comp);
}
}
// ======================== 内部:抽卡与渲染 ========================
/**
* 从 SkillBoxPool[currentWave] 抽 3 张并渲染到 3 个卡槽。
* 渲染时强制 cost=0,触发 free 路径。
*/
private rollAndRender() {
const cards = getSkillBoxCards(this.currentWave, 3);
for (let i = 0; i < this.cardComps.length; i++) {
const comp = this.cardComps[i];
if (!comp) continue;
if (i < cards.length) {
comp.applyDrawCard(cards[i]);
comp.card_cost = 0; // 强制免费
} else {
comp.clearBySystem();
}
}
}
/** 重新抽 3 张(玩家点 refreshBtn) */
private reroll() {
if (this.hasPicked) return;
this.rollAndRender();
mLogger.log(this.debugMode, "MSkillBoxComp", "reroll", {
refreshRemain: this.getRefreshRemain(),
adRefreshRemain: this.getAdRefreshRemain(),
});
}
// ======================== 内部:刷新次数持久化 ========================
/** 读取当前总可用刷新次数(普通 + 广告奖励) */
private getRefreshRemain(): number {
const d = smc.vmdata?.mission_data;
if (!d) return 0;
return Math.max(0, Math.floor(Number(d.skill_box_refresh_remain ?? 0)));
}
/** 读取广告奖励次数(待接入 SDK 后使用) */
private getAdRefreshRemain(): number {
const d = smc.vmdata?.mission_data;
if (!d) return 0;
return Math.max(0, Math.floor(Number(d.skill_box_ad_refresh_remain ?? 0)));
}
/** 消耗一次刷新(优先用普通次数,再用广告奖励) */
private consumeRefresh(): boolean {
const d = smc.vmdata?.mission_data;
if (!d) return false;
const remain = this.getRefreshRemain();
const adRemain = this.getAdRefreshRemain();
if (remain + adRemain <= 0) return false;
if (remain > 0) {
d.skill_box_refresh_remain = remain - 1;
} else {
d.skill_box_ad_refresh_remain = adRemain - 1;
}
return true;
}
/** 同步刷新次数显示 */
private refreshRefreshCountUI() {
if (!this.refreshCountLabel) return;
const total = this.getRefreshRemain() + this.getAdRefreshRemain();
this.refreshCountLabel.string = `${total}`;
}
// ======================== 内部:玩家选择 ========================
/**
* 监听每个 CardComp 的 UseSkillCard 派发,以关闭弹窗。
* 由于 CardComp.useCard 内部已经 dispatchEvent(UseSkillCard, payload),
* 这里只需监听事件并在识别为本弹窗的卡时关闭。
*/
private onSkillCardUsed(event: string, args: any) {
if (this.hasPicked) return;
const payload = args ?? event;
if (!payload) return;
const uuid = Number(payload?.uuid ?? 0);
// 仅处理本弹窗抽出的 SkillBox 卡(uuid >= 9000)
if (uuid < 9000) return;
this.hasPicked = true;
mLogger.log(this.debugMode, "MSkillBoxComp", "player picked skill card", { uuid });
oops.gui.remove(UIID.SkillBox);
}
// ======================== 按钮事件 ========================
private bindEvents() {
this.refreshBtn?.on(Button.EventType.CLICK, this.onRefreshClick, this);
this.adRefreshBtn?.on(Button.EventType.CLICK, this.onAdRefreshClick, this);
oops.message.on(GameEvent.UseSkillCard, this.onSkillCardUsed, this);
}
private unbindEvents() {
this.refreshBtn?.off(Button.EventType.CLICK, this.onRefreshClick, this);
this.adRefreshBtn?.off(Button.EventType.CLICK, this.onAdRefreshClick, this);
oops.message.off(GameEvent.UseSkillCard, this.onSkillCardUsed, this);
}
private onRefreshClick() {
if (this.hasPicked) return;
if (this.getRefreshRemain() + this.getAdRefreshRemain() <= 0) {
oops.gui.toast("刷新次数已用完,请观看广告获取");
return;
}
if (!this.consumeRefresh()) return;
this.reroll();
this.refreshRefreshCountUI();
}
private onAdRefreshClick() {
// TODO: 接入广告 SDK 后,在广告播放成功回调中:
// smc.vmdata.mission_data.skill_box_ad_refresh_remain += 3;
// this.refreshRefreshCountUI();
// 当前为占位实现,仅打印日志提示
mLogger.log(this.debugMode, "MSkillBoxComp", "onAdRefreshClick", "TODO: 接入广告 SDK,成功后 adRefreshRemain += 3");
oops.gui.toast("广告功能即将上线");
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "85b247fa-1f22-411a-b3a6-82e9797512e1",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -28,6 +28,7 @@ import { SkillBoxComp } from "./SkillBoxComp";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { smc } from "../common/SingletonModuleComp";
import { SkillBoxCardConfig } from "../common/config/CardSet";
const { ccclass, property } = _decorator;
/** 技能槽位数据结构 */
@@ -55,7 +56,7 @@ export class MissSkillsComp extends CCComp {
private debugMode: boolean = true;
/** 技能卡 Prefab在编辑器中赋值 */
@property({type: Prefab})
@property({ type: Prefab })
private skill_box: Prefab = null;
/**
@@ -67,13 +68,13 @@ export class MissSkillsComp extends CCComp {
{ x: -320, y: 240, used: false, node: null },
{ x: -240, y: 240, used: false, node: null },
{ x: -160, y: 240, used: false, node: null },
{ x: -80, y: 240, used: false, node: null },
{ x: 0, y: 240, used: false, node: null },
{ x: -80, y: 240, used: false, node: null },
{ x: 0, y: 240, used: false, node: null },
{ x: -320, y: 320, used: false, node: null },
{ x: -240, y: 320, used: false, node: null },
{ x: -160, y: 320, used: false, node: null },
{ x: -80, y: 320, used: false, node: null },
{ x: 0, y: 320, used: false, node: null },
{ x: -80, y: 320, used: false, node: null },
{ x: 0, y: 320, used: false, node: null },
];
/** 注册事件监听 */
@@ -141,17 +142,36 @@ export class MissSkillsComp extends CCComp {
* 处理使用技能卡事件:提取 uuid 和 card_lv 后调用 addSkill。
* @param event 事件名
* @param args 卡牌数据(含 uuid、card_lv
*
* 兼容两种数据源:
* - 普通卡池uuid < 9000:走 addSkill(uuid, card_lv) 旧流程
* - SkillBox 卡池uuid >= 9000 且 payload 含 s_uuid:
* 视为 SkillBoxCardConfig,使用 addSkillByConfig 走新流程
*/
private onUseSkillCard(event: string, args: any) {
const payload = args ?? event;
const uuid = Number(payload?.uuid ?? 0);
const card_lv = Math.max(1, Math.floor(Number(payload?.card_lv ?? 1)));
if (!uuid) return;
this.addSkill(uuid, card_lv);
if (this.isSkillBoxPayload(payload)) {
this.addSkillByConfig(payload as SkillBoxCardConfig);
} else {
this.addSkill(uuid, card_lv);
}
}
/**
* 判断 payload 是否为 SkillBox 三选一弹窗的卡牌配置。
* 识别规则:uuid >= 9000 且 payload 含 s_uuid 字段。
*/
private isSkillBoxPayload(payload: any): boolean {
if (!payload) return false;
const uuid = Number(payload.uuid ?? 0);
return uuid >= 9000 && Number(payload.s_uuid ?? 0) > 0;
}
start() {
}
/**
@@ -166,7 +186,7 @@ export class MissSkillsComp extends CCComp {
addSkill(uuid: number, card_lv: number) {
// 技能节点的父容器
var parent = smc.map.MapView.scene.entityLayer!.node!.getChildByName("SKILL")!;
if (!this.skill_box) {
mLogger.error(this.debugMode, "MissSkillsComp", "skill_box prefab not set");
return;
@@ -183,7 +203,7 @@ export class MissSkillsComp extends CCComp {
const node = instantiate(this.skill_box);
node.parent = parent;
node.setPosition(new Vec3(this.slots[emptyIndex].x, this.slots[emptyIndex].y, 0));
this.slots[emptyIndex].used = true;
this.slots[emptyIndex].node = node;
@@ -192,6 +212,37 @@ export class MissSkillsComp extends CCComp {
comp.init(uuid, card_lv);
}
/**
* 在场上添加一个 SkillBox 弹窗产出的技能卡uuid >= 9000
* 流程与 addSkill 相同,但初始化走 initWithConfig 以支持 overrides / s_uuid。
*
* @param skillBoxCard SkillBoxCardConfig 完整卡牌配置
*/
addSkillByConfig(skillBoxCard: SkillBoxCardConfig) {
var parent = smc.map.MapView.scene.entityLayer!.node!.getChildByName("SKILL")!;
if (!this.skill_box) {
mLogger.error(this.debugMode, "MissSkillsComp", "skill_box prefab not set");
return;
}
const emptyIndex = this.slots.findIndex(slot => !slot.used);
if (emptyIndex === -1) {
mLogger.warn(this.debugMode, "MissSkillsComp", "skill_box slots are full");
return;
}
const node = instantiate(this.skill_box);
node.parent = parent;
node.setPosition(new Vec3(this.slots[emptyIndex].x, this.slots[emptyIndex].y, 0));
this.slots[emptyIndex].used = true;
this.slots[emptyIndex].node = node;
const comp = node.getComponent(SkillBoxComp) || node.addComponent(SkillBoxComp);
comp.initWithConfig(skillBoxCard);
}
/** ECS 组件移除时销毁节点 */
reset() {
this.node.destroy();

View File

@@ -37,7 +37,7 @@ import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, SpriteAtla
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { GameEvent } from "../common/config/GameEvent";
import { CARD_POOL_INIT_LEVEL, CARD_POOL_MAX_LEVEL, CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE, CardConfig, CardType, CardsUpSet, drawCardsByRule, getCardsByLv, SpecialRefreshCardList, SpecialRefreshHeroType, SpecialUpgradeCardList } from "../common/config/CardSet";
import { CARD_POOL_INIT_LEVEL, CARD_POOL_MAX_LEVEL, CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE, CardConfig, CardType, CardsUpSet, drawCardsByRule, getCardsByLv, SKILL_BOX_TRIGGER_WAVES, SpecialRefreshCardList, SpecialRefreshHeroType, SpecialUpgradeCardList } from "../common/config/CardSet";
import { CardComp } from "./CardComp";
import { oops } from "db://oops-framework/core/Oops";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
@@ -48,6 +48,7 @@ import { FacSet, FightSet } from "../common/config/GameSet";
import { MoveComp } from "../hero/MoveComp";
import { MissionHeroComp } from "./MissionHeroComp";
import { MissionEconomy } from "./MissionEconomy";
import { UIID } from "../common/config/GameUIConfig";
const { ccclass, property } = _decorator;
@@ -323,13 +324,35 @@ export class MissionCardComp extends CCComp {
}
/** 新一波:展开面板 → 刷新费用 UI → 重新抽卡分发 */
private onNewWave() {
private onNewWave(event?: string, args?: any) {
this.isBattlePhase = false;
this.enterPreparePhase();
this.updateCoinAndCostUI();
this.layoutCardSlots();
const cards = this.buildDrawCards();
this.dispatchCardsToSlots(cards);
// 固定波次(1/5/10/15/20)弹出技能三选一弹窗
const wave = Number(args?.wave ?? 0);
if (this.isSkillBoxTriggerWave(wave)) {
this.openSkillBox(wave);
}
}
/** 判断 wave 是否属于技能弹窗触发波次 */
private isSkillBoxTriggerWave(wave: number): boolean {
if (!wave || wave <= 0) return false;
return SKILL_BOX_TRIGGER_WAVES.includes(wave);
}
/** 打开技能三选一弹窗(MSkillBoxComp) */
private openSkillBox(wave: number) {
if (smc.map?.MapView) {
oops.gui.open(UIID.SkillBox, { wave, poolLv: this.poolLv });
mLogger.log(this.debugMode, "MissionCardComp", "openSkillBox", { wave, poolLv: this.poolLv });
} else {
mLogger.warn(this.debugMode, "MissionCardComp", "openSkillBox skipped, smc.map.MapView not ready");
}
}
/** 解除按钮监听,避免节点销毁后回调泄漏 */
@@ -637,12 +660,8 @@ export class MissionCardComp extends CCComp {
/** 构建本次抽卡结果保证最终可分发3条数据 */
private buildDrawCards(): CardConfig[] {
let targetType: CardType | CardType[] | undefined = undefined;
if (this.isBattlePhase) {
targetType = CardType.Skill;
} else {
targetType = [CardType.Hero, CardType.SpecialRefresh];
}
// 技能卡已不再通过常规刷新分发,统一走 MSkillBoxComp 固定波次弹窗
const targetType: CardType[] = [CardType.Hero, CardType.SpecialRefresh];
const cards = getCardsByLv(this.poolLv, targetType);
/** 正常情况下直接取前3 */

View File

@@ -727,6 +727,9 @@ export class MissionComp extends CCComp {
smc.vmdata.mission_data.coin = Math.max(0, Math.floor(CardInitCoins));
// 【评分系统 - 效率分】记录初始获得的金币收入
smc.vmdata.scores.gold_earned += smc.vmdata.mission_data.coin;
// 技能三选一弹窗:每局重置 1 次普通刷新 + 清零广告累计次数
smc.vmdata.mission_data.skill_box_refresh_remain = 1;
smc.vmdata.mission_data.skill_box_ad_refresh_remain = 0;
}
// ======================== 波次管理 ========================

View File

@@ -29,8 +29,8 @@ import { mLogger } from "../common/Logger";
import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas } 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 { SkillSet } from "../common/config/SkillSet";
import { CardPoolList, SkillBoxCardConfig } from "../common/config/CardSet";
import { SkillOverrides, SkillSet } from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { smc } from "../common/SingletonModuleComp";
@@ -49,8 +49,8 @@ export class SkillBoxComp extends CCComp {
private debugMode: boolean = true;
/** 技能图标节点 */
@property({type: Node})
private icon_node:Node= null;
@property({ type: Node })
private icon_node: Node = null;
/** 剩余次数标签 */
@property(Label)
@@ -68,6 +68,8 @@ export class SkillBoxComp extends CCComp {
private trigger_times: number = 1;
/** 触发间隔(秒,仅持续技能有效) */
private trigger_interval: number = 0;
/** 技能参数覆盖(来自 SkillBoxCardConfig.overrides,触发时随事件派发) */
private skill_overrides: SkillOverrides | null = null;
// ======================== 运行时状态 ========================
@@ -115,7 +117,7 @@ export class SkillBoxComp extends CCComp {
init(uuid: number, card_lv: number) {
this.s_uuid = uuid;
this.card_lv = card_lv;
// 查询触发配置
const config = CardPoolList.find(c => c.uuid === uuid);
if (config) {
@@ -123,6 +125,7 @@ export class SkillBoxComp extends CCComp {
this.trigger_times = config.t_times ?? 1;
this.trigger_interval = config.t_inv ?? 0;
}
this.skill_overrides = null;
this.current_trigger_times = 0;
this.timer = 0;
@@ -143,6 +146,39 @@ export class SkillBoxComp extends CCComp {
}
}
/**
* 使用 SkillBoxCardConfig 初始化MSkillBoxComp 三选一弹窗场景)
*
* 与 init(uuid, card_lv) 的区别:
* - 直接以 s_uuid 字段作为实际生效技能
* - 触发参数is_inst / t_times / t_inv取自 SkillBoxCardConfig
* - overrides 在 triggerSkill 时随事件一起派发,由技能执行系统按需应用
*/
initWithConfig(skillBoxCard: SkillBoxCardConfig) {
this.s_uuid = skillBoxCard.s_uuid;
this.card_lv = Math.max(1, Math.floor(skillBoxCard.card_lv ?? 1));
this.is_instant = skillBoxCard.is_inst ?? true;
this.trigger_times = skillBoxCard.t_num ?? skillBoxCard.t_times ?? 1;
this.trigger_interval = skillBoxCard.t_inv ?? 0;
this.skill_overrides = skillBoxCard.overrides ?? null;
this.current_trigger_times = 0;
this.timer = 0;
this.initialized = true;
this.updateUI();
if (this.is_instant) {
this.triggerSkill();
this.current_trigger_times++;
if (this.current_trigger_times >= this.trigger_times) {
this.scheduleOnce(() => {
this.node.destroy();
}, 1.0);
}
}
}
/**
* 更新 UI
* - 图标:从 uicons 图集获取。
@@ -160,7 +196,7 @@ export class SkillBoxComp extends CCComp {
}
}
}
// 更新剩余次数标签
if (this.info_label) {
if (!this.is_instant) {
@@ -178,7 +214,7 @@ export class SkillBoxComp extends CCComp {
private onFightStart() {
if (!this.initialized) return;
this.in_combat = true;
if (!this.is_instant) {
this.timer = 0; // 重置计时器
}
@@ -201,7 +237,7 @@ export class SkillBoxComp extends CCComp {
private handleNewWave() {
if (!this.initialized) return;
this.in_combat = false;
if (!this.is_instant) {
if (this.current_trigger_times >= this.trigger_times) {
this.node.destroy();
@@ -233,7 +269,7 @@ export class SkillBoxComp extends CCComp {
this.triggerSkill();
this.current_trigger_times++;
this.updateUI();
// 次数用完 → 延迟销毁
if (this.current_trigger_times >= this.trigger_times) {
this.scheduleOnce(() => {
@@ -256,12 +292,14 @@ export class SkillBoxComp extends CCComp {
const localPos = this.node.position;
const parentPos = this.node.parent ? this.node.parent.position : new Vec3(0, 0, 0);
targetPos.set(parentPos.x + localPos.x, parentPos.y + localPos.y, 0);
// 将 SkillBoxCardConfig 的 overrides 一起派发,技能执行系统可按需合并到基础 SkillSet 配置
oops.message.dispatchEvent(GameEvent.TriggerSkill, {
s_uuid: this.s_uuid,
isCardSkill: true, // 标记为卡牌技能(区别于英雄自身技能)
card_lv: this.card_lv,
targetPos: targetPos
targetPos: targetPos,
overrides: this.skill_overrides ?? undefined
});
}