refactor: 重构英雄列表UI与图标加载逻辑

1.  替换list-me预制体为list_me,统一命名规范
2.  调整ui5.plist的精灵边框参数,修复UI显示精度
3.  重写英雄图标加载逻辑,新增异步加载竞态保护与动画首帧缓存
4.  优化cSkill预制体的布局与样式参数
This commit is contained in:
pan
2026-07-27 09:44:02 +08:00
parent 336175c421
commit f634b48eb4
10 changed files with 7000 additions and 2540 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,12 @@
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "b2a3067a-af88-4ff7-acdd-7474b8a163d8",
"uuid": "b0828069-23cf-49f1-be72-7986cc97f33c",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "list-me"
"syncNodeName": "list_me"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2370,10 +2370,10 @@
"height": 67,
"rawWidth": 61,
"rawHeight": 67,
"borderTop": 33.5,
"borderBottom": 33.5,
"borderLeft": 30.5,
"borderRight": 30.5,
"borderTop": 33,
"borderBottom": 33,
"borderLeft": 30,
"borderRight": 30,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
@@ -2462,10 +2462,10 @@
"height": 55,
"rawWidth": 53,
"rawHeight": 55,
"borderTop": 27.5,
"borderBottom": 27.5,
"borderLeft": 27.5,
"borderRight": 25.5,
"borderTop": 27,
"borderBottom": 27,
"borderLeft": 27,
"borderRight": 25,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
@@ -5130,8 +5130,8 @@
"height": 25,
"rawWidth": 26,
"rawHeight": 25,
"borderTop": 12.5,
"borderBottom": 12.5,
"borderTop": 12,
"borderBottom": 12,
"borderLeft": 13,
"borderRight": 13,
"packable": true,
@@ -5176,8 +5176,8 @@
"height": 25,
"rawWidth": 26,
"rawHeight": 25,
"borderTop": 12.5,
"borderBottom": 12.5,
"borderTop": 12,
"borderBottom": 12,
"borderLeft": 13,
"borderRight": 13,
"packable": true,
@@ -5222,8 +5222,8 @@
"height": 33,
"rawWidth": 30,
"rawHeight": 33,
"borderTop": 16.5,
"borderBottom": 16.5,
"borderTop": 16,
"borderBottom": 16,
"borderLeft": 15,
"borderRight": 15,
"packable": true,
@@ -5314,10 +5314,10 @@
"height": 30,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"borderTop": 15,
"borderBottom": 15,
"borderLeft": 15,
"borderRight": 15,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
@@ -5360,10 +5360,10 @@
"height": 30,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"borderTop": 15,
"borderBottom": 15,
"borderLeft": 15,
"borderRight": 15,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
@@ -5774,10 +5774,10 @@
"height": 75,
"rawWidth": 77,
"rawHeight": 75,
"borderTop": 37.5,
"borderBottom": 37.5,
"borderLeft": 37.5,
"borderRight": 37.5,
"borderTop": 37,
"borderBottom": 37,
"borderLeft": 37,
"borderRight": 37,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
@@ -5820,10 +5820,10 @@
"height": 75,
"rawWidth": 77,
"rawHeight": 75,
"borderTop": 38.5,
"borderBottom": 38.5,
"borderLeft": 37.5,
"borderRight": 37.5,
"borderTop": 38,
"borderBottom": 38,
"borderLeft": 37,
"borderRight": 37,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,

View File

@@ -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;
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
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;
}
/** 更新图标精灵 */
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", {
const path = `game/heros/hero/${hero.path}/idle`;
mLogger.log(this.debugMode, "CHeroComp", "updateHeroAnimation load", {
nodeName: this.node?.name,
iconId,
spriteFound: !!sprite,
atlasReady: !!smc.uiconsAtlas,
frameFound: !!frame
uuid,
path,
token
});
if (!sprite) return;
if (smc.uiconsAtlas) {
sprite.spriteFrame = frame || null;
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;
}
});
}
// ======================== 长按信息框 ========================