为游戏地图模块的脚本文件添加全面的注释,说明每个组件的职责、关键设计、依赖关系和使用方式。注释覆盖了英雄信息面板、技能卡槽位管理器、排行榜弹窗、卡牌控制器、背景滚动组件等核心功能模块,提高了代码的可读性和维护性。 同时修复了英雄预制体的激活状态和技能效果预制体的尺寸参数。
231 lines
9.0 KiB
TypeScript
231 lines
9.0 KiB
TypeScript
/**
|
||
* @file IBoxComp.ts
|
||
* @description 英雄信息弹窗组件(IBox,UI 视图层)
|
||
*
|
||
* 职责:
|
||
* 1. 作为全局弹窗,展示某个英雄的 **详细技能信息**。
|
||
* 2. 通过 onAdded(args) 接收英雄 UUID、等级和运行时技能数据。
|
||
* 3. 自动计算技能行数并动态调整弹窗背景高度和名称位置。
|
||
* 4. 点击弹窗任意区域关闭自身。
|
||
*
|
||
* 关键设计:
|
||
* - Line1~Line5 为预设的 5 行技能节点,按需显示/隐藏。
|
||
* - 每行包含技能名、等级、CD、描述文本和类型图标(近战/远程/辅助)。
|
||
* - 背景高度 = baseHeight + (行数 - 1) × extraLineHeight。
|
||
* - 若传入 args.skills(运行时技能),优先使用;否则回退到英雄静态配置。
|
||
*
|
||
* 依赖:
|
||
* - HeroInfo(heroSet)—— 英雄静态配置
|
||
* - SkillSet / IType(SkillSet)—— 技能静态配置
|
||
* - 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);
|
||
}
|
||
}
|