本次提交完成了全项目范围内术语的统一替换,将原本指代卡牌品质/等级的card_lv字段全部重命名为rarity,对齐了设计文档中的命名规范,避免了术语混淆: 1. 更新了所有配置文件、组件类、数据结构中的字段定义 2. 同步修改了注释、文档说明中的相关表述 3. 修正了英雄、技能、装备卡牌的品质显示逻辑 4. 调整了背景颜色切换、属性计算等相关业务逻辑
135 lines
5.1 KiB
TypeScript
135 lines
5.1 KiB
TypeScript
/**
|
||
* @file ChipComp.ts
|
||
* @description 英雄图鉴卡预制体组件(UI 视图层·精简版)
|
||
*
|
||
* 职责:
|
||
* 1. 根据英雄 UUID 渲染 **图标**(idle 动画首帧静态展示,零运行时动画开销)。
|
||
* 2. 根据 HeroInfo.rarity 切换 **背景底框颜色**(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 { RARITY_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;
|
||
|
||
// 背景:按 rarity 切换颜色底框(green/blue/purple/yellow/red)
|
||
if (this.BG_node) {
|
||
const color = RARITY_BG_COLORS[Math.min(Math.max(hero.rarity ?? 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();
|
||
}
|
||
}
|