/** * @file HerosListComp.ts * @description 英雄图鉴页面(UI 视图层) * * 职责: * 1. 以卡片列表形式展示所有可用英雄(使用 CardLiteComp 预制体)。 * 2. 点击卡片打开英雄详情弹窗(UIID.HeroInfo),详情/升级在 HeroInfoComp 中处理。 * 3. 卡片排序:仅按品质降序 → uuid 升序(与养成进度解耦,保持展示稳定)。 * * 关键设计: * - cards_node 为卡片容器,通过 instantiate(card_lite_prefab) 动态生成卡片。 * - 升级/碎片变动后通过 HERO_GROWTH_UPDATE 事件统一刷新所有卡片的可升级标识。 * - 详情面板已从本组件剥离,由 HeroInfoComp 承载。 * - 排序不依赖 smc,仅读取静态配置(HeroInfo.rarity)。 * * 依赖: * - HeroInfo / HeroList(heroSet)—— 英雄静态配置与全量 UUID 列表 * - CardLiteComp —— 轻量卡片组件 * - UIID.HeroInfo(oops.gui)—— 英雄详情弹窗 */ import { _decorator, Node, Prefab, UITransform, instantiate } 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 { oops } from "db://oops-framework/core/Oops"; import { mLogger } from "../common/Logger"; import { HeroInfo, HeroList } from "../common/config/heroSet"; import { GameEvent } from "../common/config/GameEvent"; import { UIID } from "../common/config/GameUIConfig"; import { CardLiteComp } from "./CardLiteComp"; const { property, ccclass } = _decorator; @ccclass('HerosListComp') @ecs.register('HerosListComp', false) export class HerosListComp extends CCComp { // ======================== 编辑器绑定节点 ======================== @property(Node) cards_node = null! @property(Prefab) card_lite_prefab = null! /** 卡片宽度 */ @property card_w = 120 /** 卡片高度 */ @property card_h = 200 /** 卡片横向间隔 */ @property spacing_x = 10 /** 卡片纵向间隔 */ @property spacing_y = 10 /** 每行卡片数(竖向滚动时按列排列) */ @property cols = 4 /** 卡片左右边距 */ @property margin_x = 15 /** 卡片上下边距 */ @property margin_y = 50 // ======================== 运行时状态 ======================== debugMode: boolean = false start() { this.initCardList() } protected onEnable(): void { oops.message.on(GameEvent.HERO_GROWTH_UPDATE, this.onGrowthUpdate, this) // 页面隐藏期间会错过事件(战斗结算发碎片时本节点 active=false 已注销监听), // 重新显示时必须补刷;首次 onEnable 早于 start,卡片未创建时 forEach 空数组安全 this.refreshAllCardsCanUp() } protected onDisable(): void { oops.message.off(GameEvent.HERO_GROWTH_UPDATE, this.onGrowthUpdate, this) } private onGrowthUpdate() { this.refreshAllCardsCanUp() } /** 升级/碎片变动后统一刷新所有卡片的可升级标识与等级数字 */ private refreshAllCardsCanUp() { if (!this.cards_node) return this.cards_node.children.forEach(cardNode => { const comp = cardNode.getComponent(CardLiteComp) comp?.refreshCanUp() comp?.refreshLv() }) } closeHeros() { this.node.active = false } // ======================== 卡片列表 ======================== private initCardList() { if (!this.cards_node || !this.card_lite_prefab) return this.cards_node.removeAllChildren() // 图鉴排序:按职业(HRole)升序 → uuid 升序(与养成进度解耦,保持展示稳定) const sorted = [...HeroList].sort((a, b) => { const heroA = HeroInfo[a]; const heroB = HeroInfo[b]; const roleA = heroA?.role ?? 0; const roleB = heroB?.role ?? 0; if (roleA !== roleB) return roleA - roleB; return a - b; }) for (const uuid of sorted) { const hero = HeroInfo[uuid] if (!hero) continue const cardNode = instantiate(this.card_lite_prefab) cardNode.name = `card_${uuid}` const comp = cardNode.getComponent(CardLiteComp) || cardNode.addComponent(CardLiteComp) comp.setHeroByUuid(uuid) comp.onClickCallback = () => { // 点击卡片:打开英雄详情弹窗(详情/升级/前后切换由 HeroInfoComp 承载) oops.audio.playEffect("music/button"); oops.gui.open(UIID.HeroInfo, { uuid }) } this.cards_node.addChild(cardNode) } mLogger.log(this.debugMode, "HerosListComp", "initCardList done", { totalChildren: this.cards_node.children.length, }) this.updateContentSize() } private updateContentSize() { const total = this.cards_node.children.length if (total === 0) return // 竖向滚动:宽度固定(每行 cols 列),高度按行数累计 const rows = Math.ceil(total / this.cols) // 宽度 = 每行卡片总宽 + 横向间隔 + 左右边距 const contentW = this.cols * this.card_w + (this.cols - 1) * this.spacing_x + this.margin_x * 2 // 高度 = 行数 × 卡片高 + 纵向间隔 + 上下边距 const contentH = rows * this.card_h + (rows - 1) * this.spacing_y + this.margin_y * 2 const uiTrans = this.cards_node.getComponent(UITransform) if (uiTrans) { uiTrans.setContentSize(contentW, contentH) } } reset() { this.node.destroy() } }