Files
pixelheros/assets/script/game/map/ChipComp.ts
pan 4ad8b2e0b6 refactor(ChipComp): 优化英雄图鉴卡图标渲染逻辑
1. 移除动画播放逻辑,改为加载英雄 idle 动画首帧静态展示,降低运行时开销
2. 调整注释与变量命名,统一代码表述风格
3. 修复异步加载竞态保护逻辑
4. 同时删除了冗余的 buff_bg 预制体文件,更新了 maxw/maxb/maxr 预制体的位置与缩放参数,调整了 clips 预制体的显示属性与贴图

fix: 修复部分UI元素的显示异常问题
2026-09-01 10:41:36 +08:00

135 lines
5.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. 根据英雄 UUID 渲染 **图标**(idle 动画首帧静态展示,零运行时动画开销)。
* 2. 根据 HeroInfo.card_lv 切换 **背景底框颜色**(BG_node 下的 green/blue/purple/yellow/red 子节点)。
*
* 说明:
* - 仅处理英雄展示,数据源为 heroSet 的 HeroInfo,不依赖 CardSet。
* - 图标采用 CHeroComp 同款方案:加载 idle AnimationClip 取首帧 SpriteFrame 静止展示,
* 通过 iconVisualToken 防止异步加载竞态。
*/
import { mLogger } from "../common/Logger";
import { _decorator, AnimationClip, Node, Sprite, resources, SpriteFrame } 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 { CARD_LV_BG_COLORS } from "../common/config/GameSet";
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
private iconVisualToken: number = 0;
// ======================== 对外接口 ========================
/**
* 通过英雄 UUID 渲染英雄卡(静态图标 + 背景底框)。
* @param uuid 英雄 UUID(须存在于 HeroInfo)
*/
setHeroByUuid(uuid: number) {
const hero = HeroInfo[uuid];
if (!hero) return;
this.card_uuid = uuid;
this.node.active = true;
this.iconVisualToken += 1;
// 背景:按 card_lv 切换颜色底框(green/blue/purple/yellow/red)
if (this.BG_node) {
const color = CARD_LV_BG_COLORS[Math.min(Math.max(hero.card_lv ?? 1, 1), 5) - 1];
this.BG_node.children.forEach(child => child.active = (child.name === color));
}
// 图标:idle 动画首帧静态展示
if (this.icon_node) {
this.updateHeroIcon(this.icon_node, uuid, this.iconVisualToken);
}
mLogger.log(this.debugMode, "ChipComp", "setHeroByUuid", { uuid });
}
/** 清空并重置 UI */
clear() {
this.card_uuid = 0;
this.iconVisualToken += 1;
if (this.BG_node) {
this.BG_node.children.forEach(child => child.active = false);
}
if (this.icon_node) {
const sprite = this.icon_node.getComponent(Sprite) || this.icon_node.getComponentInChildren(Sprite);
if (sprite) sprite.spriteFrame = null;
}
this.node.active = false;
}
// ======================== 图标(静态首帧) ========================
/**
* 加载英雄 idle 动画剪辑,提取首帧 SpriteFrame 静止展示(零运行时动画开销)。
* 使用 token 做竞态保护,确保异步回调时不会覆盖已更新的图标。
*/
private updateHeroIcon(node: Node, uuid: number, token: number) {
if (!node || !node.isValid) return;
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
if (sprite) sprite.spriteFrame = null;
const hero = HeroInfo[uuid];
if (!hero) return;
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.card_uuid !== uuid) return;
if (!node || !node.isValid) return;
const target = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
if (!target || !target.isValid) return;
// 帧动画数据存于 spriteFrame 轨道的 _channel._curve._values(裸 SpriteFrame 数组)
let firstFrame: SpriteFrame | undefined;
for (const track of clip.tracks) {
const curve = (track as unknown as { channel?: { curve?: { _values?: unknown[] } } }).channel?.curve;
const frame = curve?._values?.[0];
if (frame instanceof SpriteFrame) {
firstFrame = frame;
break;
}
}
if (firstFrame) {
target.spriteFrame = firstFrame;
}
});
}
// ======================== 生命周期钩子 ========================
/** ECS 组件移除时的释放钩子:销毁节点 */
reset() {
this.node.destroy();
}
}