Files
pixelheros/assets/script/game/map/SkillBoxComp.ts
pan e76cba7933 feat(map): 新增固定波次技能三选一弹窗系统
1.  新增MSkillBoxComp弹窗组件,实现固定波次触发的技能卡选择功能
2.  新增SkillBoxCardConfig配置与SkillBoxPool技能池,支持按波次配置技能
3.  重构MissionCardComp,将技能卡抽取改为固定波次弹窗触发
4.  扩展SingletonModuleComp与MissionComp,添加技能刷新次数持久化逻辑
5.  优化MissSkillsComp,新增SkillBox专属技能加载流程
6.  修复SkillBoxComp,支持自定义技能参数覆盖
7.  调整UIConfig与CardSet配置,适配新的技能卡流程
2026-06-03 16:36:22 +08:00

311 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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、卡牌等级、
* 触发位置等信息分发给技能系统。
*
* 依赖:
* - CardPoolListCardSet—— 查询技能卡的触发配置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, 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";
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;
/** 技能参数覆盖(来自 SkillBoxCardConfig.overrides,触发时随事件派发) */
private skill_overrides: SkillOverrides | null = null;
// ======================== 运行时状态 ========================
/** 已触发次数 */
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() {
super.onDestroy();
oops.message.off(GameEvent.FightStart, this.onFightStart, this);
oops.message.off(GameEvent.MissionEnd, this.onMissionEnd, this);
if (this.node && this.node.isValid) {
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.skill_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) {
// 次数已满 → 延迟 1 秒后销毁(保留短暂视觉反馈)
this.scheduleOnce(() => {
this.node.destroy();
}, 1.0);
}
}
}
/**
* 使用 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 图集获取。
* - 剩余次数:持续技能显示剩余数字,即时技能不显示。
*/
updateUI() {
// 加载技能图标
if (this.icon_node) {
const iconId = SkillSet[this.s_uuid]?.icon || `${this.s_uuid}`;
if (smc.uiconsAtlas) {
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
if (frame && this.icon_node && this.icon_node.isValid) {
let 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);
// 将 SkillBoxCardConfig 的 overrides 一起派发,技能执行系统可按需合并到基础 SkillSet 配置
oops.message.dispatchEvent(GameEvent.TriggerSkill, {
s_uuid: this.s_uuid,
isCardSkill: true, // 标记为卡牌技能(区别于英雄自身技能)
card_lv: this.card_lv,
targetPos: targetPos,
overrides: this.skill_overrides ?? undefined
});
}
/** ECS 组件移除时销毁节点 */
reset() {
this.node.destroy();
}
}