Files
pixelheros/assets/script/game/map/IBoxComp.ts
walkpan e880613f8f docs: 为游戏地图模块添加详细的代码注释
为游戏地图模块的脚本文件添加全面的注释,说明每个组件的职责、关键设计、依赖关系和使用方式。注释覆盖了英雄信息面板、技能卡槽位管理器、排行榜弹窗、卡牌控制器、背景滚动组件等核心功能模块,提高了代码的可读性和维护性。

同时修复了英雄预制体的激活状态和技能效果预制体的尺寸参数。
2026-04-07 19:00:30 +08:00

231 lines
9.0 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. 通过 onAdded(args) 接收英雄 UUID、等级和运行时技能数据。
* 3. 自动计算技能行数并动态调整弹窗背景高度和名称位置。
* 4. 点击弹窗任意区域关闭自身。
*
* 关键设计:
* - Line1~Line5 为预设的 5 行技能节点,按需显示/隐藏。
* - 每行包含技能名、等级、CD、描述文本和类型图标近战/远程/辅助)。
* - 背景高度 = baseHeight + (行数 - 1) × extraLineHeight。
* - 若传入 args.skills运行时技能优先使用否则回退到英雄静态配置。
*
* 依赖:
* - HeroInfoheroSet—— 英雄静态配置
* - SkillSet / ITypeSkillSet—— 技能静态配置
* - UIID —— 在 oops.gui 系统中注册的弹窗 ID
*/
import { _decorator, Label, Node, NodeEventType, UITransform } 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 { HeroInfo } from "../common/config/heroSet";
import { IType, SkillSet } from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
const { ccclass, property } = _decorator;
/**
* IBoxComp —— 英雄信息弹窗视图组件
*
* 通过 oops.gui.open(UIID.IBox, { heroUuid, heroLv, skills? }) 打开。
* 展示英雄技能列表,支持最多 5 行。
*/
@ccclass('IBoxComp')
@ecs.register('IBoxComp', false)
export class IBoxComp extends CCComp {
// ======================== 编辑器绑定节点5 行技能) ========================
@property(Node)
Line1: Node = null!
@property(Node)
Line2: Node = null!
@property(Node)
Line3: Node = null!
@property(Node)
Line4: Node = null!
@property(Node)
Line5: Node = null!
// ======================== 布局常量 ========================
/** 弹窗背景基础高度(只有 1 行技能时的高度) */
private readonly baseHeight: number = 100;
/** 每增加一行技能,背景增加的高度 */
private readonly extraLineHeight: number = 50;
/** 英雄名称标签的基准 Y 坐标 */
private readonly nameBaseY: number = 50;
/** 每增加一行技能,名称标签额外上移的 Y 偏移 */
private readonly nameExtraLineOffsetY: number = 25;
/**
* ECS 实体挂载回调:接收外部传入的英雄参数并渲染。
* @param args.heroUuid 英雄 UUID
* @param args.heroLv 英雄当前等级
* @param args.skills (可选)运行时技能数据,若无则使用英雄静态配置
*/
onAdded(args: {
heroUuid?: number;
heroLv?: number;
skills?: Record<number, { uuid: number; lv: number; cd: number; ccd: number }>;
}) {
this.renderHeroInfo(args);
}
/** 绑定点击关闭事件 */
onLoad() {
this.node.on(NodeEventType.TOUCH_END, this.onTapClose, this);
}
/** 解绑点击事件 */
onDestroy() {
this.node.off(NodeEventType.TOUCH_END, this.onTapClose, this);
}
/** ECS 组件移除时销毁节点 */
reset() {
this.node.destroy();
}
// ======================== 渲染逻辑 ========================
/**
* 主渲染方法:解析英雄数据并填充技能行。
*
* 流程:
* 1. 从 args 中取英雄 UUID 和等级。
* 2. 优先使用运行时技能数据args.skills否则回退英雄静态配置。
* 3. 将每个技能映射为 { text, iType },过滤无效项。
* 4. 调用 applyLineData 渲染到 Line1~Line5。
*/
private renderHeroInfo(args: {
heroUuid?: number;
heroLv?: number;
skills?: Record<number, { uuid: number; lv: number; cd: number; ccd: number }>;
}) {
const heroUuid = Math.floor(args?.heroUuid ?? 0);
const heroLv = Math.max(1, Math.floor(args?.heroLv ?? 1));
const hero = HeroInfo[heroUuid];
if (!hero) {
this.setHeroName("");
this.applyLineData([{ text: "暂无技能信息" }]);
return;
}
this.setHeroName(hero.name);
// 运行时技能 vs 静态配置
const runtimeSkills = args?.skills ? Object.values(args.skills) : [];
const sourceSkills = runtimeSkills.length > 0 ? runtimeSkills : Object.values(hero.skills ?? {});
// 将每个技能转换为显示数据
const lineData = sourceSkills.map(skill => {
const skillId = Math.floor(skill?.uuid ?? 0);
if (!skillId) return null;
const config = SkillSet[skillId];
if (!config) return null;
// 运行时技能直接使用其等级;静态配置的技能等级需叠加英雄等级修正
const runtimeLv = runtimeSkills.length > 0 ? Math.max(0, Math.floor(skill.lv ?? 0)) : Math.max(0, Math.floor((skill.lv ?? 1) + heroLv - 2));
const cd = Number(skill?.cd ?? 0);
return {
text: `${config.name} Lv.${runtimeLv} CD:${cd}s ${config.info}`,
iType: config.IType
};
}).filter(item => !!item) as Array<{ text: string; iType: IType }>;
this.applyLineData(lineData.length > 0 ? lineData : [{ text: "暂无技能信息" }]);
}
/**
* 将技能数据应用到 Line1~Line5
* 1. 按顺序填充文本和类型图标。
* 2. 超出的行隐藏。
* 3. 根据显示行数调整弹窗高度和名称位置。
*
* @param skillLines 技能行数据数组
*/
private applyLineData(skillLines: Array<{ text: string; iType?: IType }>) {
const lines = [this.Line1, this.Line2, this.Line3, this.Line4, this.Line5];
const showCount = Math.max(1, Math.min(lines.length, skillLines.length));
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (!line) continue;
const active = i < showCount;
line.active = active;
if (!active) continue;
const data = skillLines[i];
const text = data?.text ?? "";
// 查找技能文本节点并设置内容
const noteNode = line.getChildByName("note");
const label = noteNode?.getComponent(Label) || noteNode?.getComponentInChildren(Label) || line.getComponentInChildren(Label);
if (label) label.string = text;
// 更新类型图标
this.updateLineTypeIcon(line, data?.iType);
}
// 动态调整弹窗背景高度
const targetHeight = this.baseHeight + Math.max(0, showCount - 1) * this.extraLineHeight;
this.updateIBoxHeight(targetHeight);
// 动态调整名称位置
this.updateNamePosition(showCount);
}
/**
* 设置弹窗背景 Bg 节点的高度
* @param height 目标高度
*/
private updateIBoxHeight(height: number) {
const bgNode = this.node.getChildByName("Bg");
const bgTransform = bgNode?.getComponent(UITransform);
if (bgTransform) {
bgTransform.setContentSize(bgTransform.contentSize.width, height);
}
}
/**
* 设置英雄名称标签
* @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;
}
}
/**
* 根据显示行数调整名称节点的 Y 坐标。
* 行数越多,名称越往上移,保持视觉居中。
*
* @param showCount 当前显示的技能行数
*/
private updateNamePosition(showCount: number) {
const bgNode = this.node.getChildByName("Bg");
const nameNode = bgNode?.getChildByName("name");
if (!nameNode) return;
const targetY = this.nameBaseY + Math.max(0, showCount - 1) * this.nameExtraLineOffsetY;
const current = nameNode.position;
nameNode.setPosition(current.x, targetY, current.z);
}
/**
* 更新技能行的类型图标(互斥显示)。
* @param line 技能行节点
* @param iType 技能类型(近战 / 远程 / 辅助)
*/
private updateLineTypeIcon(line: Node, iType?: IType) {
const meleeNode = line.getChildByName("Melee");
const remoteNode = line.getChildByName("remote");
const supportNode = line.getChildByName("support");
if (meleeNode) meleeNode.active = iType === IType.Melee;
if (remoteNode) remoteNode.active = iType === IType.remote;
if (supportNode) supportNode.active = iType === IType.support;
}
/** 点击弹窗任意区域关闭自身 */
private onTapClose() {
oops.gui.removeByNode(this.node);
}
}