Files
pixelheros/assets/script/game/map/ChipComp.ts
panFD fb2c67ccab refactor(hero system): 重构英雄升级与卡片展示逻辑
1.  重构英雄升级方法,移除外部传入所需碎片参数,改为从配置表自动读取
2.  简化CardLiteComp.setHeroByUuid,移除卡池等级参数,直接读取英雄静态配置
3.  新增ChipComp.setHeroIconByUuid,重构英雄图标渲染逻辑
4.  新增英雄详情面板的升级功能与碎片展示
5.  新增英雄成长变化的事件监听与面板刷新逻辑
2026-08-23 16:27:58 +08:00

255 lines
9.1 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 ChipComp.ts
* @description 英雄图鉴卡预制体组件UI 视图层·精简版)
*
* 职责:
* 1. 根据卡牌配置渲染 **图标**(英雄卡为 idle 动画,其他卡为静态图集帧)。
* 2. 根据 card_lv / kind 渲染 **背景底框**。
*
* 说明:
* - 仅保留图标与背景渲染能力,名称、费用、等级等展示由外部组件负责。
* - 通过 iconVisualToken 防止异步动画加载竞态。
*
* 依赖:
* - CardConfig / CardType / CKind / CardPoolListCardSet
* - HeroInfoheroSet、SkillSetSkillSet
* - smc —— 全局单例,访问图标图集缓存
*/
import { mLogger } from "../common/Logger";
import { _decorator, Animation, AnimationClip, Node, Sprite, Vec3, 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 { CardConfig, CardType, CKind, CardPoolList } from "../common/config/CardSet";
import { CardBgComp } from "./CardBgComp";
import { HeroInfo } from "../common/config/heroSet";
import { SkillSet } from "../common/config/SkillSet";
import { smc } from "../common/SingletonModuleComp";
const { ccclass, property } = _decorator;
/**
* ChipComp —— 单张卡牌图标/背景视图组件
*
* 挂载在图鉴卡预制体上,仅负责图标与背景的渲染。
*/
@ccclass('ChipComp')
@ecs.register('ChipComp', false)
export class ChipComp extends CCComp {
private debugMode: boolean = false;
// ======================== 编辑器绑定节点 ========================
@property(Node)
icon_node: Node = null!
@property(Node)
BG_node: Node = null!
// ======================== 运行时状态 ========================
card_uuid: number = 0
card_type: CardType = CardType.Hero
private cardData: CardConfig | null = null;
private iconVisualToken: number = 0;
// ======================== 生命周期 ========================
onLoad() {
if (!this.cardData) {
this.applyEmptyUI();
}
}
// ======================== 对外接口 ========================
/**
* 设置卡牌数据并渲染图标与背景。
* @param data 卡牌配置数据
*/
setData(data: CardConfig) {
if (!data) return;
this.cardData = data;
this.card_uuid = data.uuid;
this.card_type = data.type;
this.node.active = true;
this.applyCardUI();
mLogger.log(this.debugMode, "ChipComp", "setData", {
uuid: this.card_uuid,
type: this.card_type
});
}
/**
* 通过英雄 UUID 直接渲染英雄图标(自动播放 idle 动画并应用背景)。
* 适用于碎片、头像、图鉴等纯展示场景。
* @param uuid 英雄 UUID
*/
setHeroIconByUuid(uuid: number) {
const hero = HeroInfo[uuid];
if (!hero) return;
this.cardData = null;
this.card_uuid = uuid;
this.card_type = CardType.Hero;
this.node.active = true;
this.iconVisualToken += 1;
// 背景:按 HeroInfo.card_lv 应用英雄底框样式
if (this.BG_node) {
const cardLv = hero.card_lv ?? 1;
const kindName = CKind[CKind.Hero];
this.BG_node.children.forEach(child => {
child.active = (child.name === kindName);
const bg = child.getComponent(CardBgComp);
if (bg) child.active ? bg.apply(cardLv) : bg.clear();
});
}
// 图标:英雄 idle 动画(带镜像翻转)
if (this.icon_node) {
this.icon_node.setScale(new Vec3(-1.5, 1.5, 1));
this.updateHeroAnimation(this.icon_node, uuid, this.iconVisualToken);
}
}
/** 清空卡牌数据并重置 UI */
clear() {
this.cardData = null;
this.card_uuid = 0;
this.card_type = CardType.Hero;
this.applyEmptyUI();
this.node.active = false;
}
// ======================== UI 渲染 ========================
/** 渲染图标与背景 */
private applyCardUI() {
if (!this.cardData) {
this.applyEmptyUI();
return;
}
this.iconVisualToken += 1;
const kindName = CKind[this.cardData.kind];
// 背景:按 kind 显示对应底框,按 card_lv 应用等级样式
if (this.BG_node) {
const bgLv = this.cardData.base_card_lv ?? this.cardData.card_lv;
this.BG_node.children.forEach(child => {
child.active = (child.name === kindName);
const bg = child.getComponent(CardBgComp);
if (bg) child.active ? bg.apply(bgLv) : bg.clear();
});
}
// 图标:英雄卡播放 idle 动画,其他卡使用静态图集帧
if (this.icon_node) {
const iconNode = this.icon_node;
if (this.card_type === CardType.Hero) {
// 英雄图标做镜像翻转(原图朝右,卡面需朝左)
iconNode.setScale(new Vec3(-1.5, 1.5, 1));
this.updateHeroAnimation(iconNode, this.card_uuid, this.iconVisualToken);
return;
}
iconNode.setScale(new Vec3(1, 1, 1));
this.clearIconAnimation(iconNode);
const iconId = this.resolveCardIconId(this.card_type, this.card_uuid);
if (iconId) {
this.updateIcon(iconNode, iconId);
} else {
const sprite = iconNode.getComponent(Sprite) || iconNode.getComponentInChildren(Sprite);
if (sprite) sprite.spriteFrame = null;
}
}
}
/** 渲染空槽状态:清空图标与背景 */
private applyEmptyUI() {
this.iconVisualToken += 1;
if (this.BG_node) {
this.BG_node.children.forEach(child => {
child.active = false;
const bg = child.getComponent(CardBgComp);
if (bg) bg.clear();
});
}
if (this.icon_node) {
this.icon_node.setScale(new Vec3(1, 1, 1));
this.clearIconAnimation(this.icon_node);
const sprite = this.icon_node.getComponent(Sprite) || this.icon_node.getComponentInChildren(Sprite);
if (sprite) sprite.spriteFrame = null;
}
}
// ======================== 工具方法 ========================
/** 更新图标:从全局缓存图集中获取对应帧 */
private updateIcon(node: Node, iconId: string) {
if (!node || !iconId) return;
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
if (!sprite) return;
if (smc.uiconsAtlas) {
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
sprite.spriteFrame = frame || null;
}
}
/** 根据卡牌类型和 UUID 解析出图标 ID在 SpriteAtlas 中的帧名) */
private resolveCardIconId(type: CardType, uuid: number): string {
if (type === CardType.Skill) {
return SkillSet[uuid]?.icon || `${uuid}`;
}
if (type === CardType.Hero) {
return HeroInfo[uuid]?.icon || `${uuid}`;
}
return `${uuid}`;
}
/**
* 为英雄卡图标加载并播放 idle 动画。
* 使用 token 做竞态保护,确保异步回调时不会覆盖已更新的图标。
*/
private updateHeroAnimation(node: Node, uuid: number, token: number) {
const sprite = node?.getComponent(Sprite) || node?.getComponentInChildren(Sprite);
if (sprite) sprite.spriteFrame = null;
const hero = HeroInfo[uuid];
if (!hero) return;
const anim = node.getComponent(Animation) || node.addComponent(Animation);
this.clearAnimationClips(anim);
const path = `game/heros/hero/${hero.path}/idle`;
resources.load(path, AnimationClip, (err, clip) => {
if (err || !clip) {
mLogger.log(this.debugMode, "ChipComp", `load hero animation failed ${uuid}`, err);
return;
}
if (token !== this.iconVisualToken || !this.cardData || this.card_type !== CardType.Hero || this.card_uuid !== uuid) {
return;
}
this.clearAnimationClips(anim);
anim.addClip(clip);
anim.play("idle");
});
}
/** 清除图标节点上的动画(停止播放并移除所有 clip */
private clearIconAnimation(node: Node) {
const anim = node?.getComponent(Animation);
if (!anim) return;
anim.stop();
this.clearAnimationClips(anim);
}
/** 移除 Animation 组件上的全部 AnimationClip */
private clearAnimationClips(anim: Animation) {
const clips = anim.clips as AnimationClip[] | undefined;
if (!clips || clips.length === 0) return;
[...clips].forEach(clip => anim.removeClip(clip, true));
}
// ======================== 生命周期钩子 ========================
/** ECS 组件移除时的释放钩子:销毁节点 */
reset() {
this.node.destroy();
}
}