From 4ad8b2e0b6551ee17f29ac4d9af2e9b19322ebc5 Mon Sep 17 00:00:00 2001 From: pan Date: Tue, 1 Sep 2026 10:41:36 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ChipComp):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=8B=B1=E9=9B=84=E5=9B=BE=E9=89=B4=E5=8D=A1=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 移除动画播放逻辑,改为加载英雄 idle 动画首帧静态展示,降低运行时开销 2. 调整注释与变量命名,统一代码表述风格 3. 修复异步加载竞态保护逻辑 4. 同时删除了冗余的 buff_bg 预制体文件,更新了 maxw/maxb/maxr 预制体的位置与缩放参数,调整了 clips 预制体的显示属性与贴图 fix: 修复部分UI元素的显示异常问题 --- assets/resources/game/skill/buff/buff.prefab | 7 +- .../resources/game/skill/buff/buff_bg.prefab | 274 ------------------ .../game/skill/buff/buff_bg.prefab.meta | 13 - assets/resources/game/skill/buff/maxb.prefab | 6 +- assets/resources/game/skill/buff/maxr.prefab | 6 +- assets/resources/game/skill/buff/maxw.prefab | 6 +- assets/resources/gui/element/clips.prefab | 14 +- assets/script/game/map/ChipComp.ts | 68 ++--- 8 files changed, 52 insertions(+), 342 deletions(-) delete mode 100644 assets/resources/game/skill/buff/buff_bg.prefab delete mode 100644 assets/resources/game/skill/buff/buff_bg.prefab.meta diff --git a/assets/resources/game/skill/buff/buff.prefab b/assets/resources/game/skill/buff/buff.prefab index c75fdfd8..d7223fed 100644 --- a/assets/resources/game/skill/buff/buff.prefab +++ b/assets/resources/game/skill/buff/buff.prefab @@ -163,13 +163,10 @@ "b": 255, "a": 255 }, - "_spriteFrame": { - "__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@ce362", - "__expectedType__": "cc.SpriteFrame" - }, + "_spriteFrame": null, "_type": 0, "_fillType": 1, - "_sizeMode": 1, + "_sizeMode": 0, "_fillCenter": { "__type__": "cc.Vec2", "x": 0, diff --git a/assets/resources/game/skill/buff/buff_bg.prefab b/assets/resources/game/skill/buff/buff_bg.prefab deleted file mode 100644 index ea4dfb44..00000000 --- a/assets/resources/game/skill/buff/buff_bg.prefab +++ /dev/null @@ -1,274 +0,0 @@ -[ - { - "__type__": "cc.Prefab", - "_name": "buff_bg", - "_objFlags": 0, - "__editorExtras__": {}, - "_native": "", - "data": { - "__id__": 1 - }, - "optimizationPolicy": 0, - "persistent": false - }, - { - "__type__": "cc.Node", - "_name": "buff_bg", - "_objFlags": 0, - "__editorExtras__": {}, - "_parent": null, - "_children": [ - { - "__id__": 2 - } - ], - "_active": true, - "_components": [ - { - "__id__": 8 - }, - { - "__id__": 10 - } - ], - "_prefab": { - "__id__": 12 - }, - "_lpos": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_lrot": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_lscale": { - "__type__": "cc.Vec3", - "x": 1, - "y": 1, - "z": 1 - }, - "_mobility": 0, - "_layer": 1, - "_euler": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_id": "" - }, - { - "__type__": "cc.Node", - "_name": "skill", - "_objFlags": 0, - "__editorExtras__": {}, - "_parent": { - "__id__": 1 - }, - "_children": [], - "_active": true, - "_components": [ - { - "__id__": 3 - }, - { - "__id__": 5 - } - ], - "_prefab": { - "__id__": 7 - }, - "_lpos": { - "__type__": "cc.Vec3", - "x": -2, - "y": -10, - "z": 0 - }, - "_lrot": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 0, - "w": 1 - }, - "_lscale": { - "__type__": "cc.Vec3", - "x": 0.8, - "y": 0.8, - "z": 1 - }, - "_mobility": 0, - "_layer": 1, - "_euler": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 0 - }, - "_id": "" - }, - { - "__type__": "cc.UITransform", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 2 - }, - "_enabled": true, - "__prefab": { - "__id__": 4 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 113, - "height": 126 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "b4sNQPJWFKha7x75SWXmRj" - }, - { - "__type__": "cc.Sprite", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 2 - }, - "_enabled": true, - "__prefab": { - "__id__": 6 - }, - "_customMaterial": null, - "_srcBlendFactor": 2, - "_dstBlendFactor": 4, - "_color": { - "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, - "a": 255 - }, - "_spriteFrame": { - "__uuid__": "2423272e-e63b-4736-b15b-30b40cf98a23@35919", - "__expectedType__": "cc.SpriteFrame" - }, - "_type": 0, - "_fillType": 1, - "_sizeMode": 1, - "_fillCenter": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_fillStart": 0.2, - "_fillRange": 1, - "_isTrimmedMode": true, - "_useGrayscale": false, - "_atlas": { - "__uuid__": "2423272e-e63b-4736-b15b-30b40cf98a23", - "__expectedType__": "cc.SpriteAtlas" - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "9a+65VIghBm4HQxuHPQ/mg" - }, - { - "__type__": "cc.PrefabInfo", - "root": { - "__id__": 1 - }, - "asset": { - "__id__": 0 - }, - "fileId": "7a5LvbdlxEc6FKa7cun2oB", - "instance": null, - "targetOverrides": null, - "nestedPrefabInstanceRoots": null - }, - { - "__type__": "cc.UITransform", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 9 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 80, - "height": 110 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "63NP9yq3hEUKD/OZZZ5t7x" - }, - { - "__type__": "cc.Animation", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 1 - }, - "_enabled": true, - "__prefab": { - "__id__": 11 - }, - "playOnLoad": true, - "_clips": [ - { - "__uuid__": "a732ebac-b3c8-4f43-ac73-67a90a02ecfa", - "__expectedType__": "cc.AnimationClip" - } - ], - "_defaultClip": { - "__uuid__": "a732ebac-b3c8-4f43-ac73-67a90a02ecfa", - "__expectedType__": "cc.AnimationClip" - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "c6LOemuvJKyYCqlF/yUJcr" - }, - { - "__type__": "cc.PrefabInfo", - "root": { - "__id__": 1 - }, - "asset": { - "__id__": 0 - }, - "fileId": "c46/YsCPVOJYA4mWEpNYRx", - "instance": null, - "targetOverrides": null - } -] \ No newline at end of file diff --git a/assets/resources/game/skill/buff/buff_bg.prefab.meta b/assets/resources/game/skill/buff/buff_bg.prefab.meta deleted file mode 100644 index efceba74..00000000 --- a/assets/resources/game/skill/buff/buff_bg.prefab.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.50", - "importer": "prefab", - "imported": true, - "uuid": "9528e2ff-e9a0-42f8-88bd-0f172275e351", - "files": [ - ".json" - ], - "subMetas": {}, - "userData": { - "syncNodeName": "buff_bg" - } -} diff --git a/assets/resources/game/skill/buff/maxb.prefab b/assets/resources/game/skill/buff/maxb.prefab index f3d55f60..8740e773 100644 --- a/assets/resources/game/skill/buff/maxb.prefab +++ b/assets/resources/game/skill/buff/maxb.prefab @@ -87,7 +87,7 @@ "_lpos": { "__type__": "cc.Vec3", "x": 0, - "y": -25.982, + "y": -37.784, "z": 0 }, "_lrot": { @@ -99,8 +99,8 @@ }, "_lscale": { "__type__": "cc.Vec3", - "x": 1, - "y": 1, + "x": 1.2, + "y": 1.2, "z": 1 }, "_mobility": 0, diff --git a/assets/resources/game/skill/buff/maxr.prefab b/assets/resources/game/skill/buff/maxr.prefab index ee2fb516..0425dc0e 100644 --- a/assets/resources/game/skill/buff/maxr.prefab +++ b/assets/resources/game/skill/buff/maxr.prefab @@ -87,7 +87,7 @@ "_lpos": { "__type__": "cc.Vec3", "x": 0, - "y": -25.982, + "y": -36.941, "z": 0 }, "_lrot": { @@ -99,8 +99,8 @@ }, "_lscale": { "__type__": "cc.Vec3", - "x": 1, - "y": 1, + "x": 1.2, + "y": 1.2, "z": 1 }, "_mobility": 0, diff --git a/assets/resources/game/skill/buff/maxw.prefab b/assets/resources/game/skill/buff/maxw.prefab index c9829949..00987624 100644 --- a/assets/resources/game/skill/buff/maxw.prefab +++ b/assets/resources/game/skill/buff/maxw.prefab @@ -87,7 +87,7 @@ "_lpos": { "__type__": "cc.Vec3", "x": 0, - "y": -30, + "y": -37.394, "z": 0 }, "_lrot": { @@ -99,8 +99,8 @@ }, "_lscale": { "__type__": "cc.Vec3", - "x": 0.8, - "y": 1, + "x": 1.2, + "y": 1.2, "z": 1 }, "_mobility": 0, diff --git a/assets/resources/gui/element/clips.prefab b/assets/resources/gui/element/clips.prefab index 3c1d000b..7325d726 100644 --- a/assets/resources/gui/element/clips.prefab +++ b/assets/resources/gui/element/clips.prefab @@ -497,7 +497,7 @@ "__id__": 2 }, "_children": [], - "_active": false, + "_active": true, "_components": [ { "__id__": 20 @@ -555,8 +555,8 @@ }, "_contentSize": { "__type__": "cc.Size", - "width": 114.28571428571429, - "height": 114.28571428571429 + "width": 140, + "height": 140 }, "_anchorPoint": { "__type__": "cc.Vec2", @@ -586,13 +586,13 @@ "_dstBlendFactor": 4, "_color": { "__type__": "cc.Color", - "r": 255, - "g": 255, - "b": 255, + "r": 211, + "g": 30, + "b": 30, "a": 255 }, "_spriteFrame": { - "__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@36a81", + "__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@b36c4", "__expectedType__": "cc.SpriteFrame" }, "_type": 1, diff --git a/assets/script/game/map/ChipComp.ts b/assets/script/game/map/ChipComp.ts index d208ee98..c45a598e 100644 --- a/assets/script/game/map/ChipComp.ts +++ b/assets/script/game/map/ChipComp.ts @@ -3,15 +3,16 @@ * @description 英雄图鉴卡预制体组件(UI 视图层·精简版) * * 职责: - * 1. 根据英雄 UUID 渲染 **图标**(idle 动画)。 + * 1. 根据英雄 UUID 渲染 **图标**(idle 动画首帧静态展示,零运行时动画开销)。 * 2. 根据 HeroInfo.card_lv 切换 **背景底框颜色**(BG_node 下的 green/blue/purple/yellow/red 子节点)。 * * 说明: * - 仅处理英雄展示,数据源为 heroSet 的 HeroInfo,不依赖 CardSet。 - * - 通过 iconVisualToken 防止异步动画加载竞态。 + * - 图标采用 CHeroComp 同款方案:加载 idle AnimationClip 取首帧 SpriteFrame 静止展示, + * 通过 iconVisualToken 防止异步加载竞态。 */ import { mLogger } from "../common/Logger"; -import { _decorator, Animation, AnimationClip, Node, Sprite, Vec3, resources } from "cc"; +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"; @@ -22,7 +23,7 @@ const { ccclass, property } = _decorator; /** * ChipComp —— 单张英雄卡图标/背景视图组件 * - * 挂载在图鉴卡预制体上,仅负责英雄图标(idle 动画)与背景底框的渲染。 + * 挂载在图鉴卡预制体上,仅负责英雄图标(静态首帧)与背景底框的渲染。 */ @ccclass('ChipComp') @ecs.register('ChipComp', false) @@ -44,7 +45,7 @@ export class ChipComp extends CCComp { // ======================== 对外接口 ======================== /** - * 通过英雄 UUID 渲染英雄卡(idle 动画图标 + 背景底框)。 + * 通过英雄 UUID 渲染英雄卡(静态图标 + 背景底框)。 * @param uuid 英雄 UUID(须存在于 HeroInfo) */ setHeroByUuid(uuid: number) { @@ -60,9 +61,9 @@ export class ChipComp extends CCComp { this.BG_node.children.forEach(child => child.active = (child.name === color)); } - // 图标:idle 动画(镜像翻转,原图朝右,卡面需朝左) + // 图标:idle 动画首帧静态展示 if (this.icon_node) { - this.updateHeroAnimation(this.icon_node, uuid, this.iconVisualToken); + this.updateHeroIcon(this.icon_node, uuid, this.iconVisualToken); } mLogger.log(this.debugMode, "ChipComp", "setHeroByUuid", { uuid }); } @@ -75,56 +76,55 @@ export class ChipComp extends CCComp { this.BG_node.children.forEach(child => child.active = false); } if (this.icon_node) { - this.clearIconAnimation(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 动画。 + * 加载英雄 idle 动画剪辑,提取首帧 SpriteFrame 静止展示(零运行时动画开销)。 * 使用 token 做竞态保护,确保异步回调时不会覆盖已更新的图标。 */ - private updateHeroAnimation(node: Node, uuid: number, token: number) { - const sprite = node?.getComponent(Sprite) || node?.getComponentInChildren(Sprite); + 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 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.card_uuid !== uuid) { - 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; } - 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 组件移除时的释放钩子:销毁节点 */