/** * @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, Node, NodeEventType, RichText, UITransform, Widget } 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! /** 背景节点(高度随 RichText 内容动态调整) */ @property(Node) bg_node: Node = null! /** Bg 节点高度上下留白(RichText 顶部间距 + 底部间距) */ private static readonly BG_PADDING_V: number = 24; /** Bg 最小高度(防止内容过少时背景塌缩) */ private static readonly BG_MIN_HEIGHT: number = 60; /** * 初始化并渲染弹窗内容。 * @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); } else if (args?.skills) { // 运行时技能详情模式(取第一个技能) const first = Object.values(args.skills)[0]; if (first) { this.renderSkill(first.uuid, first.lv); } } else if (args?.skillUuid !== undefined) { // 技能详情模式 this.renderSkill(args.skillUuid, args.skillLv ?? 1, args.overrides); } else { this.setRichText("暂无信息"); } // RichText 排版需一帧完成,延迟同步 Bg 高度 this.scheduleOnce(() => this.syncBgHeight(), 0); } /** * 根据 RichText 实际内容高度,动态调整 Bg 节点高度与位置。 * RichText 锚点已设为 (0.5, 1),从顶部向下生长; * Bg 锚点 (0.5, 0.5),向上居中扩展,顶部对齐 RichText 顶。 */ private syncBgHeight() { if (!this.massage_node?.isValid) return; const richUT = this.massage_node.getComponent(UITransform); if (!richUT) return; const contentH = Math.max(richUT.height, 0); const bgH = Math.max(contentH + IBoxComp.BG_PADDING_V, IBoxComp.BG_MIN_HEIGHT); const bgNode = this.bg_node ?? this.node.getChildByName("bg"); if (!bgNode?.isValid) return; const bgUT = bgNode.getComponent(UITransform); if (!bgUT) return; // Bg 节点在 prefab 中挂了 Widget(顶底对齐),会覆盖代码设置的高度,先禁用 const widget = bgNode.getComponent(Widget); if (widget) widget.enabled = false; bgUT.height = bgH; // Bg 锚点在中心,高度为 bgH 时其中心应位于 RichText 顶部向下 bgH/2 处 // RichText 锚点 (0.5,1),其顶部 y = pos.y;Bg 中心 y = pos.y - bgH/2 const richPos = this.massage_node.node.position; bgNode.setPosition(bgNode.position.x, richPos.y - bgH / 2, bgNode.position.z); } /** * 渲染技能详情:名称、等级、CD、结构化效果描述。 * @param skillUuid SkillSet 技能 UUID * @param lv 技能等级 * @param overrides 可选覆写参数 */ private renderSkill(skillUuid: number, lv: number, overrides?: SkillOverrides) { const skill = SkillSet[skillUuid]; if (!skill) { this.setRichText("未知技能"); return; } this.setRichText(buildSingleSkillRich(skillUuid, overrides, lv)); } /** * 渲染装备详情:名称、全部驻场光环词条。 * @param equipUuid EquipSet 装备 UUID */ private renderEquip(equipUuid: number) { const equip = findEquipByUuid(equipUuid); if (!equip) { this.setRichText("未知装备"); return; } this.setRichText(buildEquipRich(equip.field ?? [])); } /** 将 BBCode 文本写入 RichText */ private setRichText(text: string) { if (this.massage_node && this.massage_node.isValid) { this.massage_node.string = text; } } /** 点击弹窗任意区域关闭自身 */ private onTapClose() { if (this.node && this.node.isValid) { this.node.destroy(); } } }