feat(map): add hero card chip view component
新增了图鉴卡牌预制体组件ChipComp,负责渲染卡牌图标与背景底框,支持英雄卡动画播放和普通卡牌静态图标展示,同时提供了数据设置、清空等对外接口。
This commit is contained in:
2927
assets/resources/gui/element/clips.prefab
Normal file
2927
assets/resources/gui/element/clips.prefab
Normal file
File diff suppressed because it is too large
Load Diff
13
assets/resources/gui/element/clips.prefab.meta
Normal file
13
assets/resources/gui/element/clips.prefab.meta
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.1.50",
|
||||||
|
"importer": "prefab",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "4b8fb686-b649-4525-9aae-df0e7369205b",
|
||||||
|
"files": [
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {
|
||||||
|
"syncNodeName": "clips"
|
||||||
|
}
|
||||||
|
}
|
||||||
258
assets/script/game/map/ChipComp.ts
Normal file
258
assets/script/game/map/ChipComp.ts
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
/**
|
||||||
|
* @file ChipComp.ts
|
||||||
|
* @description 英雄图鉴卡预制体组件(UI 视图层·精简版)
|
||||||
|
*
|
||||||
|
* 职责:
|
||||||
|
* 1. 根据卡牌配置渲染 **图标**(英雄卡为 idle 动画,其他卡为静态图集帧)。
|
||||||
|
* 2. 根据 card_lv / kind 渲染 **背景底框**。
|
||||||
|
*
|
||||||
|
* 说明:
|
||||||
|
* - 仅保留图标与背景渲染能力,名称、费用、等级等展示由外部组件负责。
|
||||||
|
* - 通过 iconVisualToken 防止异步动画加载竞态。
|
||||||
|
*
|
||||||
|
* 依赖:
|
||||||
|
* - CardConfig / CardType / CKind / CardPoolList(CardSet)
|
||||||
|
* - HeroInfo(heroSet)、SkillSet(SkillSet)
|
||||||
|
* - 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 和卡池等级直接设置英雄卡。
|
||||||
|
* 自动从 CardPoolList 查找匹配的卡牌配置。
|
||||||
|
* @param uuid 英雄 UUID
|
||||||
|
* @param cardLv 卡牌等级(默认 1)
|
||||||
|
*/
|
||||||
|
setHeroByUuid(uuid: number, cardLv: number = 1) {
|
||||||
|
const card = CardPoolList.find(c => c.uuid === uuid && c.card_lv === cardLv);
|
||||||
|
if (card) {
|
||||||
|
this.setData(card);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hero = HeroInfo[uuid];
|
||||||
|
if (hero) {
|
||||||
|
this.setData({
|
||||||
|
uuid: hero.uuid,
|
||||||
|
type: CardType.Hero,
|
||||||
|
cost: 0,
|
||||||
|
weight: 0,
|
||||||
|
card_lv: cardLv,
|
||||||
|
kind: CKind.Hero,
|
||||||
|
hero_lv: smc.getHeroOuterLv(hero.uuid)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询当前是否有卡牌数据 */
|
||||||
|
hasCard(): boolean {
|
||||||
|
return !!this.cardData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取当前卡牌数据 */
|
||||||
|
getCardData(): CardConfig | null {
|
||||||
|
return this.cardData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清空卡牌数据并重置 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user