Files
pixelheros/assets/script/game/map/IBoxComp.ts
panFD 4caf20d4b2 feat(ibox): 实现信息弹窗背景自适应高度功能
1. 重构IBoxComp组件,移除旧的标题名称渲染逻辑
2. 添加动态背景节点,根据RichText内容自动调整高度
3. 优化cardbg和herobox预制体样式,调整尺寸与透明度
4. 新增通用背景预制体hbg,支持多配色快速复用
5. 重写ibox预制体结构,适配新的组件逻辑
2026-08-07 21:51:48 +08:00

191 lines
7.2 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 IBoxComp.ts
* @description 技能/装备详情弹窗组件IBoxUI 视图层)
*
* 职责:
* 1. 作为详情弹窗,展示单个技能或装备的 **功能描述详情**。
* 2. 通过 init(args) 接收技能 UUID、装备 UUID 或运行时技能数据。
* 3. 使用单个 RichText 以 BBCode 展示名称蓝色、CD/持续时间(亮绿色)、结构化效果描述。
* 4. 点击弹窗任意区域关闭自身。
*
* 关键设计:
* - 复用 HeroSkillDesc 的结构化模板渲染,数值实时从配置读取,杜绝漂移。
* - 技能展示名称、等级、CD、效果描述SkillDescSet 模板)。
* - 装备展示名称、全部驻场光环词条FieldDescSet 模板)。
* - 不走 oops.gui由调用方手动实例化并添加到场景通过 init() 初始化。
*
* 依赖:
* - HeroSkillDescbuildSingleSkillRich / buildEquipRich—— 描述生成
* - SkillSet / FieldSkillSetSkillSet—— 技能/光环静态配置
* - EquipSetfindEquipByUuid—— 装备静态配置
*/
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<number, { uuid: number; lv: number; cd: number; ccd: number }>;
}
/**
* 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.yBg 中心 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();
}
}
}