refactor: 重构英雄列表UI与图标加载逻辑
1. 替换list-me预制体为list_me,统一命名规范 2. 调整ui5.plist的精灵边框参数,修复UI显示精度 3. 重写英雄图标加载逻辑,新增异步加载竞态保护与动画首帧缓存 4. 优化cSkill预制体的布局与样式参数
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
* - 英雄卡效果事件为 GameEvent.CallHero,而非 UseSkillCard。
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Node, Sprite, Label, NodeEventType, Vec3, Tween, tween, UIOpacity } from "cc";
|
||||
import { _decorator, Node, Sprite, Label, NodeEventType, Vec3, Tween, tween, UIOpacity, SpriteFrame, resources, AnimationClip } 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 } from "../common/config/CardSet";
|
||||
@@ -91,6 +91,10 @@ export class CHeroComp extends CCComp {
|
||||
private restPosition: Vec3 = new Vec3();
|
||||
/** 透明度组件(用于淡入淡出) */
|
||||
private opacityComp: UIOpacity | null = null;
|
||||
/** 图标视觉令牌(异步加载竞态保护) */
|
||||
private iconVisualToken: number = 0;
|
||||
/** 当前显示的英雄 UUID(避免相同 UUID 重复加载动画) */
|
||||
private iconHeroUuid: number = 0;
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
@@ -299,10 +303,8 @@ export class CHeroComp extends CCComp {
|
||||
|
||||
// 图标
|
||||
if (this.icon_node) {
|
||||
const iconId = this.getIconId(renderUuid);
|
||||
if (iconId) {
|
||||
this.updateIcon(this.icon_node, iconId);
|
||||
}
|
||||
this.iconVisualToken += 1;
|
||||
this.updateHeroAnimation(this.icon_node, renderUuid, this.iconVisualToken);
|
||||
}
|
||||
|
||||
// 购买按钮费用显示
|
||||
@@ -446,30 +448,70 @@ export class CHeroComp extends CCComp {
|
||||
return model.hero_uuid ?? null;
|
||||
}
|
||||
|
||||
/** 获取英雄图标 id:优先卡牌自定义 icon,其次英雄 path,兜底 uuid */
|
||||
private getIconId(renderUuid: number): string {
|
||||
if (!this.cardData) return "";
|
||||
if (this.cardData.icon) return this.cardData.icon;
|
||||
const hero = HeroInfo[renderUuid];
|
||||
return hero?.path || `${renderUuid}`;
|
||||
}
|
||||
/**
|
||||
* 为英雄图标加载 idle 动画剪辑,提取首帧 SpriteFrame 静止展示。
|
||||
* 与 HInfoComp 的 updateHeroAnimation 一致,零运行时开销。
|
||||
* @param node 图标节点
|
||||
* @param uuid 英雄 UUID
|
||||
* @param token 视觉令牌
|
||||
*/
|
||||
private updateHeroAnimation(node: Node, uuid: number, token: number) {
|
||||
if (!node || !node.isValid) return;
|
||||
|
||||
// 相同英雄 UUID 不重复加载,避免刷新卡池时闪烁
|
||||
if (this.iconHeroUuid === uuid) return;
|
||||
this.iconHeroUuid = uuid;
|
||||
|
||||
/** 更新图标精灵 */
|
||||
private updateIcon(node: Node, iconId: string) {
|
||||
if (!node || !iconId) return;
|
||||
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
|
||||
const frame = smc.uiconsAtlas ? smc.uiconsAtlas.getSpriteFrame(iconId) : null;
|
||||
mLogger.log(this.debugMode, "CHeroComp", "updateIcon", {
|
||||
nodeName: this.node?.name,
|
||||
iconId,
|
||||
spriteFound: !!sprite,
|
||||
atlasReady: !!smc.uiconsAtlas,
|
||||
frameFound: !!frame
|
||||
});
|
||||
if (!sprite) return;
|
||||
if (smc.uiconsAtlas) {
|
||||
sprite.spriteFrame = frame || null;
|
||||
if (sprite) sprite.spriteFrame = null;
|
||||
|
||||
const hero = HeroInfo[uuid];
|
||||
if (!hero) {
|
||||
mLogger.log(this.debugMode, "CHeroComp", "updateHeroAnimation hero not found", {
|
||||
nodeName: this.node?.name,
|
||||
uuid
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const path = `game/heros/hero/${hero.path}/idle`;
|
||||
mLogger.log(this.debugMode, "CHeroComp", "updateHeroAnimation load", {
|
||||
nodeName: this.node?.name,
|
||||
uuid,
|
||||
path,
|
||||
token
|
||||
});
|
||||
|
||||
resources.load(path, AnimationClip, (err, clip) => {
|
||||
if (err || !clip) {
|
||||
mLogger.log(this.debugMode, "CHeroComp", "updateHeroAnimation load failed", {
|
||||
nodeName: this.node?.name,
|
||||
uuid,
|
||||
path,
|
||||
message: err?.message ?? null
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 竞态保护:仅允许最新一次图标请求落地
|
||||
if (token !== this.iconVisualToken) 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ======================== 长按信息框 ========================
|
||||
|
||||
Reference in New Issue
Block a user