Files
pixelheros/assets/script/game/map/HerosListComp.ts
panw ad0539d238 refactor(ui): 调整UI页面管理方式,改为节点显隐控制
1. 注释并禁用了Ranks、Heros、Talents三个UIID的全局弹窗配置
2. 将三个页面改为通过MissionHomeComp控制节点显隐切换
3. 移除了原有的gui.open/remove弹窗调用逻辑,改为设置active状态
4. 调整了组件生命周期,改用onEnable替代onAdded处理显示逻辑
5. 更新了对应组件的注释和文档说明
2026-05-27 16:18:26 +08:00

300 lines
9.9 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 HerosListComp.ts
* @description 英雄图鉴弹出页面UI 视图层)
*
* 职责:
* 1. 以卡片列表形式展示所有可用英雄(使用 CardLiteComp 预制体)。
* 2. 点击卡片选中英雄,右侧详情面板显示 idle 动画、名称、AP、HP、CD、技能描述。
* 3. 支持卡池等级筛选(全部 / Lv1 / Lv2 / Lv3
*
* 关键设计:
* - cards_node 为卡片容器,通过 instantiate(card_lite_prefab) 动态生成卡片。
* - 选中状态通过 selectNode 高亮管理,同一时间只有一张卡片高亮。
* - hero_icon 使用 Animation + iconVisualToken 机制防止异步加载竞态。
*
* 依赖:
* - HeroInfo / HeroListheroSet—— 英雄静态配置与全量 UUID 列表
* - CardLiteComp —— 轻量卡片组件
* - buildSkillDescHeroSkillDesc—— 技能描述生成器
*/
import { _decorator, Animation, AnimationClip, Label, Node, Prefab, Sprite, UITransform, Widget, instantiate, resources } 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 { buildSkillDesc } from "../common/config/HeroSkillDesc";
import { CardLiteComp } from "./CardLiteComp";
const { property, ccclass } = _decorator;
@ccclass('HerosListComp')
@ecs.register('HerosListComp', false)
export class HerosListComp extends CCComp {
// ======================== 编辑器绑定节点 ========================
@property(Node)
hero_icon = null!
@property(Node)
ap_node = null!
@property(Node)
hp_node = null!
@property(Node)
cd_node = null!
@property(Node)
info_node = null!
@property(Node)
name_node = null!
@property(Node)
cards_node = null!
@property(Prefab)
card_lite_prefab = null!
@property(Node)
lv_node = null!
@property(Node)
type_node = null!
// ======================== 运行时状态 ========================
huuid: number = 0
private iconVisualToken: number = 0
private selectNode: Node | null = null
debugMode: boolean = false
start() {
this.initCardList()
if (HeroList.length > 0) {
this.onCardSelect(HeroList[0])
}
}
protected onEnable(): void {
if (this.cards_node && this.cards_node.children.length > 0) {
this.onCardSelect(this.huuid || HeroList[0])
}
}
closeHeros() {
this.node.active = false
}
// ======================== 卡片列表 ========================
private initCardList() {
mLogger.log(this.debugMode, "HerosListComp", "initCardList start", {
hasCardsNode: !!this.cards_node,
hasPrefab: !!this.card_lite_prefab,
heroListLen: HeroList.length,
})
if (!this.cards_node || !this.card_lite_prefab) return
mLogger.log(this.debugMode, "HerosListComp", "cards_node info", {
name: this.cards_node.name,
active: this.cards_node.active,
childrenCount: this.cards_node.children.length,
pos: this.cards_node.position.toString(),
scale: this.cards_node.scale.toString(),
parentName: this.cards_node.parent?.name || "none",
})
this.cards_node.removeAllChildren()
for (const uuid of HeroList) {
const hero = HeroInfo[uuid]
if (!hero) continue
const cardNode = instantiate(this.card_lite_prefab)
cardNode.name = `card_${uuid}`
mLogger.log(this.debugMode, "HerosListComp", `card instantiated ${uuid}`, {
cardActive: cardNode.active,
cardPos: cardNode.position.toString(),
cardScale: cardNode.scale.toString(),
cardChildren: cardNode.children.map(c => c.name),
cardWidth: cardNode.getComponent("cc.UITransform")?.width,
cardHeight: cardNode.getComponent("cc.UITransform")?.height,
})
const comp = cardNode.getComponent(CardLiteComp) || cardNode.addComponent(CardLiteComp)
comp.setHeroByUuid(uuid, hero.pool_lv ?? 1)
comp.onClickCallback = (cardComp: CardLiteComp) => {
this.onCardSelect(uuid)
this.highlightCard(cardNode)
}
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 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(uiTrans.width, contentH)
}
mLogger.log(this.debugMode, "HerosListComp", "updateContentSize", {
total, cols, rows, contentH,
})
}
private highlightCard(cardNode: Node) {
if (this.selectNode) {
const oldWidget = this.selectNode.getComponent(Widget)
if (oldWidget) oldWidget.enabled = false
this.selectNode.setScale(1, 1, 1)
}
this.selectNode = cardNode
cardNode.setScale(1.1, 1.1, 1)
}
private onCardSelect(uuid: number) {
this.huuid = uuid
this.updateHeroDetail(uuid)
}
// ======================== 详情面板 ========================
private updateHeroDetail(uuid: number) {
const hero = HeroInfo[uuid]
if (!hero) return
const heroLv = Math.max(1, Math.floor(hero.lv ?? 1))
const suffix = heroLv >= 2 ? "★".repeat(heroLv - 1) : ""
this.setLabelText(this.name_node, `${suffix}${hero.name || ""}${suffix}`)
this.setLabelText(this.ap_node, `${(hero.ap ?? 0) * heroLv}`)
this.setLabelText(this.hp_node, `${(hero.hp ?? 0) * heroLv}`)
this.updateCdDisplay(hero)
if (this.info_node) {
const desc = buildSkillDesc(hero)
const infoLabel = this.info_node.getChildByName("info")?.getComponent(Label)
|| this.info_node.getComponent(Label)
|| this.info_node.getComponentInChildren(Label)
if (infoLabel) infoLabel.string = desc || hero.info || ""
}
this.updateLvDisplay(hero)
this.updateTypeDisplay(hero)
this.updateHeroAnimation(uuid)
}
private updateCdDisplay(hero: typeof HeroInfo[number]) {
if (!this.cd_node) return
const skillKeys = Object.keys(hero.skills)
if (skillKeys.length === 0) return
const firstSkill = hero.skills[Number(skillKeys[0])]
if (firstSkill) {
this.setLabelText(this.cd_node, `${firstSkill.cd}s`)
}
}
private updateLvDisplay(hero: typeof HeroInfo[number]) {
if (!this.lv_node) return
const cardLvStr = `lv${hero.pool_lv ?? 1}`
this.lv_node.active = true
this.lv_node.children.forEach(child => {
if (child.name === "light") {
child.active = false
} else if (child.name === "bg") {
child.active = true
} else {
child.active = (child.name === cardLvStr)
}
})
const widget = this.lv_node.getComponent(Widget)
if (widget) widget.updateAlignment()
this.lv_node.children.forEach(child => {
const childWidget = child.getComponent(Widget)
if (childWidget) childWidget.updateAlignment()
})
}
private updateTypeDisplay(hero: typeof HeroInfo[number]) {
if (!this.type_node) return
this.type_node.active = true
const typeStr = `${hero.type ?? 0}`
this.type_node.children.forEach(child => {
child.active = (child.name === typeStr)
})
}
// ======================== 英雄动画 ========================
private updateHeroAnimation(uuid: number) {
if (!this.hero_icon) return
const hero = HeroInfo[uuid]
if (!hero) return
const sprite = this.hero_icon.getComponent(Sprite) || this.hero_icon.getComponentInChildren(Sprite)
if (sprite) sprite.spriteFrame = null
const anim = this.hero_icon.getComponent(Animation) || this.hero_icon.addComponent(Animation)
this.clearAnimationClips(anim)
this.iconVisualToken += 1
const token = this.iconVisualToken
const path = `game/heros/hero/${hero.path}/idle`
resources.load(path, AnimationClip, (err, clip) => {
if (err || !clip) {
mLogger.log(this.debugMode, "HerosListComp", `load hero animation failed ${uuid}`, err)
return
}
if (token !== this.iconVisualToken) return
this.clearAnimationClips(anim)
anim.addClip(clip)
anim.play("idle")
})
}
private clearAnimationClips(anim: Animation) {
const clips = anim.clips
if (clips && clips.length > 0) {
for (let i = clips.length - 1; i >= 0; i--) {
const clip = clips[i]
if (clip) anim.removeClip(clip, true)
}
}
}
// ======================== UI 工具 ========================
private setLabelText(node: Node, text: string) {
if (!node) return
const label = node.getComponent(Label) || node.getComponentInChildren(Label)
if (label) {
label.string = text
}
}
reset() {
this.node.destroy()
}
}