/** * @file IBoxComp.ts * @description 技能/装备详情弹窗组件(IBox,UI 视图层) * * 职责: * 1. 作为详情弹窗,展示单个技能或装备的 **功能描述详情**。 * 2. 通过 init(args) 接收技能 UUID、装备 UUID 或运行时技能数据。 * 3. 使用单个 RichText 以 BBCode 展示名称(蓝色)、CD/持续时间(亮绿色)、结构化效果描述。 * 4. 点击弹窗任意区域关闭自身。 * * 关键设计: * - 复用 HeroSkillDesc 的结构化模板渲染,数值实时从配置读取,杜绝漂移。 * - 技能:展示名称、等级、CD、效果描述(SkillDescSet 模板)。 * - 装备:展示名称、全部驻场光环词条(FieldDescSet 模板)。 * - 不走 oops.gui,由调用方手动实例化并添加到场景,通过 init() 初始化。 * * 依赖: * - HeroSkillDesc(buildSingleSkillRich / buildEquipRich)—— 描述生成 * - SkillSet / FieldSkillSet(SkillSet)—— 技能/光环静态配置 * - EquipSet(findEquipByUuid)—— 装备静态配置 */ import { _decorator, Label, NodeEventType, RichText } 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 { SkillSet, SkillOverrides } from "../common/config/SkillSet"; import { buildEquipRich, buildSingleSkillRich } from "../common/config/HeroSkillDesc"; import { findEquipByUuid } from "../common/config/EquipSet"; const { ccclass, property } = _decorator; /** IBox 初始化参数 */ export interface IBoxInitArgs { /** SkillSet 技能 UUID(技能详情模式) */ skillUuid?: number; /** 技能等级(默认 1) */ skillLv?: number; /** 技能覆写参数(如药水卡自定义档位) */ overrides?: SkillOverrides; /** EquipSet 装备 UUID(装备详情模式) */ equipUuid?: number; /** 运行时技能数据(运行时技能详情模式,取第一个技能展示) */ skills?: Record; } /** * IBoxComp —— 技能/装备详情弹窗视图组件 * * 由调用方通过 instantiate + addChild 手动实例化,然后调用 init(args) 初始化。 * 示例: * const node = instantiate(this.iboxPrefab); * this.node.addChild(node); * node.getComponent(IBoxComp).init({ skillUuid: 6301, anchorPos: this.node.worldPosition }); */ @ccclass('IBoxComp') @ecs.register('IBoxComp', false) export class IBoxComp extends CCComp { // ======================== 编辑器绑定节点 ======================== @property(RichText) massage_node: RichText = null! /** * 初始化并渲染弹窗内容。 * @param args 见 IBoxInitArgs */ public init(args: IBoxInitArgs) { this.render(args); } /** 绑定点击关闭事件 */ onLoad() { this.node.on(NodeEventType.TOUCH_END, this.onTapClose, this); } /** 解绑点击事件 */ onDestroy() { super.onDestroy(); if (this.node && this.node.isValid) { this.node.off(NodeEventType.TOUCH_END, this.onTapClose, this); } } /** ECS 组件移除时销毁节点 */ reset() { this.node.destroy(); } // ======================== 渲染逻辑 ======================== /** * 主渲染方法:根据 args 模式分发到技能详情或装备详情。 */ private render(args: IBoxInitArgs) { // 装备详情模式 if (args?.equipUuid !== undefined) { this.renderEquip(args.equipUuid); return; } // 运行时技能详情模式(取第一个技能) if (args?.skills) { const first = Object.values(args.skills)[0]; if (first) { this.renderSkill(first.uuid, first.lv); return; } } // 技能详情模式 if (args?.skillUuid !== undefined) { this.renderSkill(args.skillUuid, args.skillLv ?? 1, args.overrides); return; } this.setHeroName(""); this.setRichText("暂无信息"); } /** * 渲染技能详情:名称、等级、CD、结构化效果描述。 * @param skillUuid SkillSet 技能 UUID * @param lv 技能等级 * @param overrides 可选覆写参数 */ private renderSkill(skillUuid: number, lv: number, overrides?: SkillOverrides) { const skill = SkillSet[skillUuid]; if (!skill) { this.setHeroName(""); this.setRichText("未知技能"); return; } this.setHeroName(skill.name); this.setRichText(buildSingleSkillRich(skillUuid, overrides, lv)); } /** * 渲染装备详情:名称、全部驻场光环词条。 * @param equipUuid EquipSet 装备 UUID */ private renderEquip(equipUuid: number) { const equip = findEquipByUuid(equipUuid); if (!equip) { this.setHeroName(""); this.setRichText("未知装备"); return; } this.setHeroName(equip.name); this.setRichText(buildEquipRich(equip.field ?? [])); } /** 将 BBCode 文本写入 RichText */ private setRichText(text: string) { if (this.massage_node && this.massage_node.isValid) { this.massage_node.string = text; } } /** * 设置标题名称标签 * @param name 技能/装备名称 */ private setHeroName(name: string) { const bgNode = this.node.getChildByName("Bg"); const nameNode = bgNode?.getChildByName("name"); const valNode = nameNode?.getChildByName("val"); const label = valNode?.getComponent(Label) || valNode?.getComponentInChildren(Label); if (label) { label.string = name; } } /** 点击弹窗任意区域关闭自身 */ private onTapClose() { if (this.node && this.node.isValid) { this.node.destroy(); } } }