/** * @file HerosListComp.ts * @description 英雄图鉴页面(UI 视图层) * * 职责: * 1. 以卡片列表形式展示所有可用英雄(使用 CardLiteComp 预制体)。 * 2. 点击卡片打开英雄详情弹窗(UIID.HeroInfo),详情/升级在 HeroInfoComp 中处理。 * 3. 卡片排序:可升级优先 → 现有等级降序 → 品质降序(getSortedHeroList)。 * * 关键设计: * - cards_node 为卡片容器,通过 instantiate(card_lite_prefab) 动态生成卡片。 * - 升级/碎片变动后通过 HERO_GROWTH_UPDATE 事件统一刷新所有卡片的可升级标识。 * - 详情面板已从本组件剥离,由 HeroInfoComp 承载。 * * 依赖: * - getSortedHeroList(heroUpSet)—— 图鉴排序 * - 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 { getSortedHeroList } from "../common/config/heroUpSet"; import { UIID } from "../common/config/GameUIConfig"; import { CardLiteComp } from "./CardLiteComp"; import { smc } from "../common/SingletonModuleComp"; 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 spacing_x = 10 /** 卡片左右边距 */ @property margin_x = 15 // ======================== 运行时状态 ======================== debugMode: boolean = false start() { this.initCardList() } protected onEnable(): void { oops.message.on(GameEvent.HERO_GROWTH_UPDATE, this.onGrowthUpdate, this) } 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() // 排序与详情面板"前后切换"同源(getSortedHeroList),保证两个入口顺序一致 const sorted = getSortedHeroList(uuid => smc.getHeroGrowth(uuid)) 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 // 宽度 = 卡片总宽 + 卡片间隔 + 左右边距 const contentW = total * this.card_w + (total - 1) * this.spacing_x + this.margin_x * 2 const cols = 4 const rows = Math.ceil(total / cols) const cardH = 200 const spacingY = 10 const contentH = Math.max(1000, rows * (cardH + spacingY) + 50) const uiTrans = this.cards_node.getComponent(UITransform) if (uiTrans) { uiTrans.setContentSize(contentW, contentH) } } reset() { this.node.destroy() } }