/** * @file SkillBoxComp.ts * @description 单个技能卡效果控制组件(UI 视图层 + 逻辑层) * * 职责: * 1. 表示一张已使用的技能卡在战场上的 **可视化实体**。 * 2. 管理技能的 **触发逻辑**:即时触发 vs 定时触发(战斗中按间隔触发)。 * 3. 显示技能图标和剩余触发次数。 * 4. 触发结束后自动销毁。 * * 关键设计: * - is_instant=true(即时技能):init 时立即触发一次,播放后延迟销毁。 * - is_instant=false(持续技能):战斗中每隔 trigger_interval 秒触发一次, * 共触发 trigger_times 次后销毁。 * - 新一波(NewWave)时如果持续技能的次数已用完则销毁。 * - 销毁时通过 GameEvent.RemoveSkillBox 通知 MissSkillsComp 回收槽位。 * * 触发技能的方式: * - 通过 GameEvent.TriggerSkill 事件,将技能 UUID、卡牌等级、 * 触发位置等信息分发给技能系统。 * * 依赖: * - CardPoolList(CardSet)—— 查询技能卡的触发配置(t_times / t_inv / is_inst) * - SkillSet —— 技能静态配置(icon 字段) * - GameEvent —— 各类游戏事件 * - smc.mission —— 游戏运行状态 */ 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 { oops } from "db://oops-framework/core/Oops"; import { GameEvent } from "../common/config/GameEvent"; import { smc } from "../common/SingletonModuleComp"; const { ccclass, property } = _decorator; /** * SkillBoxComp —— 单个技能卡效果视图 + 逻辑组件 * * 由 MissSkillsComp.addSkill() 实例化并初始化。 * 在战场上以图标 + 剩余次数的形式呈现。 */ @ccclass('SkillBoxComp') @ecs.register('SkillBoxComp', false) export class SkillBoxComp extends CCComp { /** 调试日志开关 */ private debugMode: boolean = true; /** 技能图标节点 */ @property({type: Node}) private icon_node:Node= null; /** 剩余次数标签 */ @property(Label) private info_label: Label = null; // ======================== 技能配置 ======================== /** 技能 UUID */ private s_uuid: number = 0; /** 卡牌等级 */ private card_lv: number = 1; /** 是否为即时技能(true=使用后立即触发,false=战斗中定时触发) */ private is_instant: boolean = true; /** 总触发次数 */ private trigger_times: number = 1; /** 触发间隔(秒,仅持续技能有效) */ private trigger_interval: number = 0; // ======================== 运行时状态 ======================== /** 已触发次数 */ private current_trigger_times: number = 0; /** 当前计时器(秒) */ private timer: number = 0; /** 是否处于战斗中(仅战斗中持续技能才计时) */ private in_combat: boolean = false; /** 是否已初始化 */ private initialized: boolean = false; // ======================== 生命周期 ======================== /** 注册战斗开始、任务结束、新一波等事件 */ 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); } /** 销毁时移除所有事件监听并通知槽位管理器回收 */ onDestroy() { oops.message.off(GameEvent.FightStart, this.onFightStart, this); oops.message.off(GameEvent.MissionEnd, this.onMissionEnd, this); this.node.off(GameEvent.NewWave, this.onNewWave, this); oops.message.off(GameEvent.NewWave, this.onNewWaveGlobal, this); // 通知 MissSkillsComp 回收该节点占用的槽位 oops.message.dispatchEvent(GameEvent.RemoveSkillBox, this.node); } /** * 初始化技能卡效果: * 1. 从 CardPoolList 查询技能卡的触发配置。 * 2. 更新 UI 显示(图标 + 次数)。 * 3. 即时技能立即触发一次;若次数已满则延迟销毁。 * * @param uuid 技能 UUID * @param card_lv 技能卡等级 */ 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) { this.is_instant = config.is_inst ?? true; this.trigger_times = config.t_times ?? 1; this.trigger_interval = config.t_inv ?? 0; } 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) { // 次数已满 → 延迟 1 秒后销毁(保留短暂视觉反馈) this.scheduleOnce(() => { this.node.destroy(); }, 1.0); } } } /** * 更新 UI: * - 图标:从 uicons 图集获取。 * - 剩余次数:持续技能显示剩余数字,即时技能不显示。 */ updateUI() { // 加载技能图标 if (this.icon_node) { const iconId = SkillSet[this.s_uuid]?.icon || `${this.s_uuid}`; resources.load("gui/uicons", SpriteAtlas, (err, atlas) => { if (err || !atlas) return; const frame = atlas.getSpriteFrame(iconId); if (frame && this.icon_node && this.icon_node.isValid) { const sprite = this.icon_node.getComponent(Sprite) || this.icon_node.addComponent(Sprite); sprite.spriteFrame = frame; } }); } // 更新剩余次数标签 if (this.info_label) { if (!this.is_instant) { const remain = Math.max(0, this.trigger_times - this.current_trigger_times); this.info_label.string = `${remain}次`; } else { this.info_label.string = ""; } } } // ======================== 战斗状态事件 ======================== /** 战斗开始:标记进入战斗状态,持续技能开始计时 */ private onFightStart() { if (!this.initialized) return; this.in_combat = true; if (!this.is_instant) { this.timer = 0; // 重置计时器 } } /** 节点级新一波事件处理 */ private onNewWave() { this.handleNewWave(); } /** 全局级新一波事件处理 */ private onNewWaveGlobal() { this.handleNewWave(); } /** * 新一波:退出战斗状态。 * 持续技能:若总次数已用完则销毁。 */ 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(); } } } /** 任务结束:强制销毁 */ private onMissionEnd() { this.node.destroy(); } // ======================== 帧更新 ======================== /** * 每帧更新(仅对持续技能生效): * - 累加计时器,达到 trigger_interval 时触发一次技能。 * - 触发后重置计时器并更新 UI。 * - 总次数用完后延迟销毁。 */ update(dt: number) { if (!this.initialized || !this.in_combat || this.is_instant) return; if (!smc.mission.play || smc.mission.pause) return; if (this.current_trigger_times < this.trigger_times) { this.timer += dt; if (this.timer >= this.trigger_interval) { this.timer = 0; this.triggerSkill(); this.current_trigger_times++; this.updateUI(); // 次数用完 → 延迟销毁 if (this.current_trigger_times >= this.trigger_times) { this.scheduleOnce(() => { if (this.node.isValid) this.node.destroy(); }, 0.5); } } } } // ======================== 技能触发 ======================== /** * 触发技能效果: * - 计算触发位置(节点局部坐标 + 父节点偏移)。 * - 通过 GameEvent.TriggerSkill 事件将技能数据分发给技能系统。 */ private triggerSkill() { let targetPos = new Vec3(); 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); oops.message.dispatchEvent(GameEvent.TriggerSkill, { s_uuid: this.s_uuid, isCardSkill: true, // 标记为卡牌技能(区别于英雄自身技能) card_lv: this.card_lv, targetPos: targetPos }); } /** ECS 组件移除时销毁节点 */ reset() { this.node.destroy(); } }