diff --git a/assets/bundles/battleHero/prefab/weapon/宝石掉落.prefab b/assets/bundles/battleHero/prefab/weapon/宝石掉落.prefab index 218f304..f5bf46b 100644 --- a/assets/bundles/battleHero/prefab/weapon/宝石掉落.prefab +++ b/assets/bundles/battleHero/prefab/weapon/宝石掉落.prefab @@ -22,17 +22,17 @@ "__id__": 2 }, { - "__id__": 38 + "__id__": 46 } ], "_active": true, "_components": [ { - "__id__": 46 + "__id__": 54 } ], "_prefab": { - "__id__": 48 + "__id__": 56 }, "_lpos": { "__type__": "cc.Vec3", @@ -83,16 +83,19 @@ }, { "__id__": 27 + }, + { + "__id__": 35 } ], "_active": true, "_components": [ { - "__id__": 35 + "__id__": 43 } ], "_prefab": { - "__id__": 37 + "__id__": 45 }, "_lpos": { "__type__": "cc.Vec3", @@ -759,6 +762,165 @@ "targetOverrides": null, "nestedPrefabInstanceRoots": null }, + { + "__type__": "cc.Node", + "_name": "5", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 2 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 36 + }, + { + "__id__": 38 + }, + { + "__id__": 40 + } + ], + "_prefab": { + "__id__": 42 + }, + "_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": 33554432, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 35 + }, + "_enabled": true, + "__prefab": { + "__id__": 37 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 205.99000549316406, + "height": 205.3800048828125 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5165784786856596, + "y": 0.2529944388409205 + }, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "95Z03EoLpHT7J9nZK/g6+I" + }, + { + "__type__": "sp.Skeleton", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 35 + }, + "_enabled": true, + "__prefab": { + "__id__": 39 + }, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_skeletonData": { + "__uuid__": "a5c692b3-1b31-4447-a831-b0791c57cd67", + "__expectedType__": "sp.SkeletonData" + }, + "defaultSkin": "default", + "defaultAnimation": "animation", + "_premultipliedAlpha": true, + "_timeScale": 1, + "_preCacheMode": 0, + "_cacheMode": 0, + "_sockets": [], + "_useTint": false, + "_debugMesh": false, + "_debugBones": false, + "_debugSlots": false, + "_enableBatch": false, + "loop": true, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "10iUYkQstMSYYRsMKKeNRL" + }, + { + "__type__": "cc.Sorting2D", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 35 + }, + "_enabled": true, + "__prefab": { + "__id__": 41 + }, + "_sortingLayer": 4, + "_sortingOrder": 0, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "a6BRlCBYVGboHOfagfwurw" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "7f4os6fglC4Kw93wqIzKgF", + "instance": null, + "targetOverrides": null, + "nestedPrefabInstanceRoots": null + }, { "__type__": "cc.UITransform", "_name": "", @@ -769,7 +931,7 @@ }, "_enabled": true, "__prefab": { - "__id__": 36 + "__id__": 44 }, "_contentSize": { "__type__": "cc.Size", @@ -812,17 +974,17 @@ "_active": true, "_components": [ { - "__id__": 39 + "__id__": 47 }, { - "__id__": 41 + "__id__": 49 }, { - "__id__": 43 + "__id__": 51 } ], "_prefab": { - "__id__": 45 + "__id__": 53 }, "_lpos": { "__type__": "cc.Vec3", @@ -859,11 +1021,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 38 + "__id__": 46 }, "_enabled": true, "__prefab": { - "__id__": 40 + "__id__": 48 }, "_contentSize": { "__type__": "cc.Size", @@ -887,11 +1049,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 38 + "__id__": 46 }, "_enabled": true, "__prefab": { - "__id__": 42 + "__id__": 50 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -929,11 +1091,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 38 + "__id__": 46 }, "_enabled": true, "__prefab": { - "__id__": 44 + "__id__": 52 }, "_sortingLayer": 4, "_sortingOrder": 1, @@ -966,7 +1128,7 @@ }, "_enabled": true, "__prefab": { - "__id__": 47 + "__id__": 55 }, "_contentSize": { "__type__": "cc.Size", diff --git a/assets/mx/module/res/ResourcesManager.ts b/assets/mx/module/res/ResourcesManager.ts index 0c91f0b..558aa7e 100644 --- a/assets/mx/module/res/ResourcesManager.ts +++ b/assets/mx/module/res/ResourcesManager.ts @@ -194,25 +194,19 @@ export class ResourcesManager extends Singleton { private _readPrefabRootScale(name: string, path: string, bundle: string): { x: number; y: number } | null { const p = this.getRes(name, path, bundle, Prefab) as Prefab | null; if (!p?.isValid || !p.data?.isValid) return null; - const x = p.data.scale.x; - const y = p.data.scale.y; - if (Math.abs(x - 1) < 0.01 && Math.abs(y - 1) < 0.01) return null; + const x = Number(p.data.scale.x); + const y = Number(p.data.scale.y); + if (!(x > 0) || !(y > 0) || !Number.isFinite(x) || !Number.isFinite(y)) return null; return { x, y }; } - /** 出池恢复预制体根 scale;优先读 Prefab 设计值,纠正旧池污染为 1 的节点 */ + /** 出池恢复预制体根 scale;有设计值则始终对齐,避免飞入缩小等脏 scale 污染池基准 */ private _restorePoolNodeBaseline(node: Node, prefabName: string, path: string, bundle: string): void { const design = this._readPrefabRootScale(prefabName, path, bundle); - const storedX = node["poolBaseScaleX"] as number | undefined; if (design) { - if ( - storedX == null - || (Math.abs(storedX - 1) < 0.01 && Math.abs(design.x - 1) > 0.01) - ) { - node["poolBaseScaleX"] = design.x; - node["poolBaseScaleY"] = design.y; - } - } else if (storedX == null) { + node["poolBaseScaleX"] = design.x; + node["poolBaseScaleY"] = design.y; + } else if (node["poolBaseScaleX"] == null) { this._markPoolNodeBaseline(node); } const sx = node["poolBaseScaleX"] ?? 1; diff --git a/assets/script/battle/combat/MonsterSkillHost.ts b/assets/script/battle/combat/MonsterSkillHost.ts index 0ff665f..666daa5 100644 --- a/assets/script/battle/combat/MonsterSkillHost.ts +++ b/assets/script/battle/combat/MonsterSkillHost.ts @@ -1205,7 +1205,7 @@ export class MonsterSkillHost { console.log('[MonsterSkillHost] 千里眼缺少 Skeleton', label); return; } - const name = String((sk as sp.Skeleton & { defaultAnimation?: string }).defaultAnimation ?? '').trim(); + const name = BattleSpinePlayback.resolvePrefabAnim(sk); if (!name) { console.log('[MonsterSkillHost] 千里眼无默认动画', label); return; @@ -1521,7 +1521,7 @@ export class MonsterSkillHost { gg.res.putNode(node); return false; } - const animName = String((sk as sp.Skeleton & { defaultAnimation?: string }).defaultAnimation ?? '').trim(); + const animName = BattleSpinePlayback.resolvePrefabAnim(sk); if (!animName) { console.log('[MonsterSkillHost] 损毁文字无默认动画', DESTROY_PREFAB); gg.res.putNode(node); @@ -1702,7 +1702,7 @@ export class MonsterSkillHost { gg.res.putNode(node); return false; } - const animName = String((sk as sp.Skeleton & { defaultAnimation?: string }).defaultAnimation ?? '').trim(); + const animName = BattleSpinePlayback.resolvePrefabAnim(sk); if (!animName) { console.log('[MonsterSkillHost] 降雨无默认动画', RAIN_PREFAB); gg.res.putNode(node); diff --git a/assets/script/battle/presentation/AllySkillExtraHost.ts b/assets/script/battle/presentation/AllySkillExtraHost.ts index 7a2eea3..1ba9ac4 100644 --- a/assets/script/battle/presentation/AllySkillExtraHost.ts +++ b/assets/script/battle/presentation/AllySkillExtraHost.ts @@ -300,8 +300,7 @@ export class AllySkillExtraHost { const sk = node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton); let wait = duration; if (sk?.isValid) { - const typed = sk as sp.Skeleton & { defaultAnimation?: string }; - const anim = String(typed.defaultAnimation || sk.animation || '').trim(); + const anim = BattleSpinePlayback.resolvePrefabAnim(sk); if (!anim) { console.log('[AllySkillExtra] 特效无默认动画', prefab); } else { diff --git a/assets/script/battle/presentation/BattleSpinePlayback.ts b/assets/script/battle/presentation/BattleSpinePlayback.ts index 5cb154b..128e6a8 100644 --- a/assets/script/battle/presentation/BattleSpinePlayback.ts +++ b/assets/script/battle/presentation/BattleSpinePlayback.ts @@ -205,6 +205,16 @@ export class BattleSpinePlayback { return duration / Math.max(0.01, userTimeScale); } + /** + * 预制体编辑器默认动画名(Cocos Spine 字段为 `animation`,无 `defaultAnimation`)。 + * @param skeleton 骨架 + * @param fallback 空时回退名 + */ + static resolvePrefabAnim(skeleton: sp.Skeleton | null | undefined, fallback = ''): string { + const name = String(skeleton?.animation ?? '').trim(); + return name || String(fallback ?? '').trim(); + } + /** 取消 pending 完成回调(并清除 Spine complete 监听) */ static clearPendingComplete(skeleton: sp.Skeleton | null | undefined): void { if (!skeleton?.isValid) return; diff --git a/assets/script/battle/presentation/EarthChargePresenter.ts b/assets/script/battle/presentation/EarthChargePresenter.ts index 12f0cb3..28fb807 100644 --- a/assets/script/battle/presentation/EarthChargePresenter.ts +++ b/assets/script/battle/presentation/EarthChargePresenter.ts @@ -483,8 +483,7 @@ export class EarthChargeHost { return; } if (sk.skeletonData) ensureSpineSkeletonDataRuntime(sk.skeletonData); - const typed = sk as sp.Skeleton & { defaultAnimation?: string }; - const name = String(typed.defaultAnimation || sk.animation || '').trim(); + const name = BattleSpinePlayback.resolvePrefabAnim(sk); if (!name) { console.warn('[EarthCharge] 无默认动画'); return; diff --git a/assets/script/battle/presentation/IceBallAtkPresenter.ts b/assets/script/battle/presentation/IceBallAtkPresenter.ts index aa180a9..03afde2 100644 --- a/assets/script/battle/presentation/IceBallAtkPresenter.ts +++ b/assets/script/battle/presentation/IceBallAtkPresenter.ts @@ -223,8 +223,7 @@ export class IceBallHost { gg.res.putNode(node); return null; } - const typed = sk as sp.Skeleton & { defaultAnimation?: string }; - const anim = String(typed.defaultAnimation || ICE_BALL_ANIM).trim() || ICE_BALL_ANIM; + const anim = BattleSpinePlayback.resolvePrefabAnim(sk, ICE_BALL_ANIM); if (!BattleSpinePlayback.play(sk, anim, { loop: false })) { console.log('[IceBall] 动画起播失败', anim); gg.res.putNode(node); diff --git a/assets/script/battle/presentation/NezhaChargePresenter.ts b/assets/script/battle/presentation/NezhaChargePresenter.ts index 67c3fa0..3ceebfe 100644 --- a/assets/script/battle/presentation/NezhaChargePresenter.ts +++ b/assets/script/battle/presentation/NezhaChargePresenter.ts @@ -660,15 +660,14 @@ export class NezhaChargeHost { } /** - * 重播该节点自身 defaultAnimation。 + * 重播该节点自身预制体动画(`sk.animation`)。 * @param node 身体或冲锋枪 */ private _replayOwnDefault(node: Node): void { const sk = node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton); if (!sk?.isValid) return; if (sk.skeletonData) ensureSpineSkeletonDataRuntime(sk.skeletonData); - const typed = sk as sp.Skeleton & { defaultAnimation?: string }; - const name = String(typed.defaultAnimation || '').trim(); + const name = BattleSpinePlayback.resolvePrefabAnim(sk); if (!name) return; BattleSpinePlayback.play(sk, name, { loop: true, optional: true }); } diff --git a/assets/script/battle/presentation/PiercingGiantSwordAtkPresenter.ts b/assets/script/battle/presentation/PiercingGiantSwordAtkPresenter.ts index 44a123e..ae11ebd 100644 --- a/assets/script/battle/presentation/PiercingGiantSwordAtkPresenter.ts +++ b/assets/script/battle/presentation/PiercingGiantSwordAtkPresenter.ts @@ -243,8 +243,7 @@ export class PiercingGiantSwordHost { return; } if (sk.skeletonData) ensureSpineSkeletonDataRuntime(sk.skeletonData); - const typed = sk as sp.Skeleton & { defaultAnimation?: string }; - const name = String(typed.defaultAnimation || SWORD_ANIM).trim() || SWORD_ANIM; + const name = BattleSpinePlayback.resolvePrefabAnim(sk, SWORD_ANIM); BattleSpinePlayback.play(sk, name, { loop: true }); } diff --git a/assets/script/battle/presentation/RingProjectilesPresenter.ts b/assets/script/battle/presentation/RingProjectilesPresenter.ts index cabb17c..9c5d489 100644 --- a/assets/script/battle/presentation/RingProjectilesPresenter.ts +++ b/assets/script/battle/presentation/RingProjectilesPresenter.ts @@ -13,6 +13,7 @@ import { SkillPresentationEnv } from '../skill/SkillPresentationEnv'; import { SkillTransformDriver } from '../skill/SkillTransformDriver'; import { SkillPresenterRouter } from './SkillPresenterRouter'; import { buildShadowDealContext } from './SkillShadowContext'; +import { BattleSpinePlayback } from './BattleSpinePlayback'; import type { Skill } from '../skill/Skill'; import type { SkillPresentationContext } from '../skill/SkillPresentationTypes'; import type { ISkillPresenter } from './SkillPresenter'; @@ -293,8 +294,7 @@ export class ExtraRingHost { const skel = clone.getComponentInChildren(sp.Skeleton); if (skel?.isValid) { skel.timeScale = 1; - const typed = skel as sp.Skeleton & { defaultAnimation?: string }; - skel.setAnimation(0, String(typed.defaultAnimation || RING_ANIM), true); + skel.setAnimation(0, BattleSpinePlayback.resolvePrefabAnim(skel, RING_ANIM), true); } return clone; } diff --git a/assets/script/battle/presentation/ThrownCircleAtkPresenter.ts b/assets/script/battle/presentation/ThrownCircleAtkPresenter.ts index 4afe0c4..c328060 100644 --- a/assets/script/battle/presentation/ThrownCircleAtkPresenter.ts +++ b/assets/script/battle/presentation/ThrownCircleAtkPresenter.ts @@ -621,7 +621,7 @@ export class GroundCircleHost { } /** - * 出池后恢复 Spine:只播预制体 defaultAnimation(循环),不硬编码技能动画名。 + * 出池后恢复 Spine:只播预制体 `animation`(循环),不硬编码技能动画名。 * @param root 暴风雪根 */ private _resumePrefabDefaultLoop(root: Node): void { @@ -631,8 +631,7 @@ export class GroundCircleHost { if (!sk?.isValid) continue; sk.enabled = true; sk.paused = false; - const typed = sk as sp.Skeleton & { defaultAnimation?: string }; - const name = String(typed.defaultAnimation || sk.animation || '').trim(); + const name = BattleSpinePlayback.resolvePrefabAnim(sk); if (!name) continue; if (!BattleSpinePlayback.play(sk, name, { loop: true })) { console.log('[GroundCircle] 暴风雪默认动画失败', name); diff --git a/assets/script/battle/skill/SkillShowEffect.ts b/assets/script/battle/skill/SkillShowEffect.ts index 3d90a30..3936ae0 100644 --- a/assets/script/battle/skill/SkillShowEffect.ts +++ b/assets/script/battle/skill/SkillShowEffect.ts @@ -215,8 +215,7 @@ export class SkillShowEffectPlayer { this._failSpawn('缺少 Skeleton'); return; } - const typed = sk as sp.Skeleton & { defaultAnimation?: string }; - const name = String(typed.defaultAnimation || sk.animation || '').trim(); + const name = BattleSpinePlayback.resolvePrefabAnim(sk); if (!name) { this._recycle(); this._failSpawn('无默认动画'); diff --git a/assets/script/battle/view/CocosBattleCombatPresenter.ts b/assets/script/battle/view/CocosBattleCombatPresenter.ts index a1e887b..5e759a1 100644 --- a/assets/script/battle/view/CocosBattleCombatPresenter.ts +++ b/assets/script/battle/view/CocosBattleCombatPresenter.ts @@ -492,8 +492,7 @@ export class CocosBattleCombatPresenter { return; } if (sk.skeletonData) ensureSpineSkeletonDataRuntime(sk.skeletonData); - const typed = sk as sp.Skeleton & { defaultAnimation?: string }; - const name = String(typed.defaultAnimation || sk.animation || '').trim(); + const name = BattleSpinePlayback.resolvePrefabAnim(sk); if (!name) { console.log('[CombatPresenter] 预制体无默认动画'); return; diff --git a/assets/script/battle/view/HeroWeaponMount.ts b/assets/script/battle/view/HeroWeaponMount.ts index bdde468..ed67e34 100644 --- a/assets/script/battle/view/HeroWeaponMount.ts +++ b/assets/script/battle/view/HeroWeaponMount.ts @@ -301,7 +301,7 @@ export class HeroWeaponMount { return this._playNamed(name, true, timeScale); } - /** 播放武器待机(无「待机」则播预制体 defaultAnimation) */ + /** 播放武器待机(无「待机」则播预制体 `animation`) */ playIdle(timeScale: number = 1): void { this.hideBowAtk(true); this.setArrowVisible(false); diff --git a/assets/script/battle/view/TextGrid.ts b/assets/script/battle/view/TextGrid.ts index 4407546..3f480fb 100644 --- a/assets/script/battle/view/TextGrid.ts +++ b/assets/script/battle/view/TextGrid.ts @@ -363,7 +363,7 @@ export class TextGrid extends Component { for (const sk of node.getComponentsInChildren(sp.Skeleton)) { sk.setAnimationCacheMode(sp.SpineAnimationCacheMode.REALTIME); - const anim = (sk as sp.Skeleton & { defaultAnimation?: string }).defaultAnimation || 'animation'; + const anim = BattleSpinePlayback.resolvePrefabAnim(sk, 'animation'); sk.setAnimation(0, anim, true); } this._ensureDragSurface(); @@ -408,7 +408,7 @@ export class TextGrid extends Component { for (const sk of node.getComponentsInChildren(sp.Skeleton)) { sk.setAnimationCacheMode(sp.SpineAnimationCacheMode.REALTIME); - const anim = (sk as sp.Skeleton & { defaultAnimation?: string }).defaultAnimation || 'animation'; + const anim = BattleSpinePlayback.resolvePrefabAnim(sk, 'animation'); sk.setAnimation(0, anim, true); } this._ensureDragSurface(); @@ -460,7 +460,7 @@ export class TextGrid extends Component { this.clearConfusionFx(); return; } - const anim = String((sk as sp.Skeleton & { defaultAnimation?: string }).defaultAnimation || 'animation').trim(); + const anim = BattleSpinePlayback.resolvePrefabAnim(sk, 'animation'); sk.setAnimationCacheMode(sp.SpineAnimationCacheMode.REALTIME); if (!BattleSpinePlayback.play(sk, anim, { loop: true })) { console.log('[TextGrid] 混乱效果无动画', anim); @@ -2628,7 +2628,7 @@ export class TextGrid extends Component { skill.applyComposeTextSkin(true, this._resolveDisplayLevel()); } else if (!active) { this.resetSkinPresentation(); - // 背包/未上场:强制待机(预制体 defaultAnimation 可能是「攻击」) + // 背包/未上场:强制待机(预制体 animation 可能是「攻击」) if (useGlyphSkin) { skill.applyComposeTextSkin(false, this._resolveDisplayLevel()); } diff --git a/assets/script/ui/BattleGame/BattleWeaponDropPresenter.ts b/assets/script/ui/BattleGame/BattleWeaponDropPresenter.ts index 35c02f2..4e818d3 100644 --- a/assets/script/ui/BattleGame/BattleWeaponDropPresenter.ts +++ b/assets/script/ui/BattleGame/BattleWeaponDropPresenter.ts @@ -1,5 +1,5 @@ import { - _decorator, Button, Canvas, Component, instantiate, Label, Node, Rect, Sprite, Tween, tween, UIOpacity, UITransform, Vec3, view, Widget, + _decorator, Button, Canvas, Component, instantiate, Label, Node, Rect, sp, Sprite, Tween, tween, UIOpacity, UITransform, Vec3, view, Widget, } from 'cc'; import { SpriteLoad } from 'db://assets/mx/components/SpriteLoad'; import { GBundle } from '../../game/ConfigRes'; @@ -8,6 +8,7 @@ import type { ProjectBattleHandle } from '../../battle/runtime/BattleRuntime'; import type { EntityId } from 'db://assets/mx/module/ecs'; import type { WeaponDropRecord } from '../../battle/combat/WeaponDropService'; import { BattleAudio } from '../../battle/audio/BattleAudio'; +import { BattleSpinePlayback } from '../../battle/presentation/BattleSpinePlayback'; const { ccclass } = _decorator; @@ -21,6 +22,10 @@ const ICON_NODE = '图标'; const QUALITY_ROOT = '品质'; /** 品质子节点档位数 */ const QUALITY_MAX = 4; +/** 品质光柱正式动画(Spine 另含美术误导出的「效果预览」,禁止播放) */ +const QUALITY_DROP_ANIM = 'animation'; +/** 品质光柱禁用动画名 */ +const QUALITY_PREVIEW_ANIM = '效果预览'; /** 结算模板图标子节点名(ui_gemTemp/icon) */ const SETTLE_ICON_NODE = 'icon'; /** 碎片数量底节点名 */ @@ -46,6 +51,8 @@ const SCREEN_PAD = 8; const FLY_SEC = 0.55; /** 飞入终点相对预制体基准 scale 的倍率 */ const FLY_END_SCALE_MUL = 0.45; +/** 「宝石掉落」预制体根设计 scale(与 prefab `_lscale` 一致;池基准缺失时兜底) */ +const DROP_PREFAB_SCALE = 0.5; /** 到达后渐隐时长 */ const FADE_SEC = 0.18; @@ -283,7 +290,7 @@ export class WeaponDropView extends Component { } /** - * 切换「品质」下 1~4 子节点(表品质 5 钳到 4)。 + * 切换「品质」下 1~4 子节点(表品质 5 钳到 4),并强制播正式光柱动画。 * @param node 掉落根 * @param quality Weapon.quality * @param weaponId 宝石 id(日志) @@ -304,7 +311,40 @@ export class WeaponDropView extends Component { for (let i = 0; i < children.length; i++) { const child = children[i]; if (!child?.isValid) continue; - child.active = child.name === name; + const on = child.name === name; + if (on) WeaponDropView._prepareQualityDropAnim(child); + child.active = on; + if (on) WeaponDropView._playQualityDropAnim(child, weaponId); + } + } + + /** + * 激活前写入 `animation`,避免 onEnable 误播「效果预览」。 + * @param qualityNode 品质子节点 1~4 + */ + private static _prepareQualityDropAnim(qualityNode: Node): void { + const sk = qualityNode.getComponent(sp.Skeleton) ?? qualityNode.getComponentInChildren(sp.Skeleton); + if (!sk?.isValid) return; + sk.loop = true; + sk.animation = QUALITY_DROP_ANIM; + } + + /** + * 品质光柱只播 animation,避开「效果预览」。 + * @param qualityNode 品质子节点 1~4 + * @param weaponId 宝石 id(日志) + */ + private static _playQualityDropAnim(qualityNode: Node, weaponId: number): void { + const sk = qualityNode.getComponent(sp.Skeleton) ?? qualityNode.getComponentInChildren(sp.Skeleton); + if (!sk?.isValid) { + console.warn('[WeaponDrop] 品质缺少 Spine', weaponId, qualityNode.name); + return; + } + sk.paused = false; + sk.loop = true; + sk.animation = QUALITY_DROP_ANIM; + if (!BattleSpinePlayback.play(sk, QUALITY_DROP_ANIM, { loop: true, optional: true })) { + console.warn('[WeaponDrop] 品质动画缺失', QUALITY_DROP_ANIM, weaponId, qualityNode.name, QUALITY_PREVIEW_ANIM); } } @@ -556,6 +596,8 @@ export class WeaponDropView extends Component { return; } Tween.stopAllByTarget(node); + // 先回到基准再飞入缩小,避免以上一次飞入末态为起点连乘 + this._restorePrefabScale(); const base = this._prefabBaseScale(); tween(node) .to( @@ -591,7 +633,7 @@ export class WeaponDropView extends Component { /** * 预制体根设计 scale(出池时 ResourcesManager 写入 poolBaseScale)。 - * 禁止统一洗成 1,否则改预制体缩放无效。 + * 禁止回退到当前 scale:飞入后已缩小,会把基准越写越小。 */ private _prefabBaseScale(): { x: number; y: number } { const n = this.node; @@ -601,11 +643,10 @@ export class WeaponDropView extends Component { return { x: sx, y: sy }; } if (n?.isValid) { - const cx = n.scale.x; - const cy = n.scale.y; - if (cx > 0 && cy > 0) return { x: cx, y: cy }; + n['poolBaseScaleX'] = DROP_PREFAB_SCALE; + n['poolBaseScaleY'] = DROP_PREFAB_SCALE; } - return { x: 1, y: 1 }; + return { x: DROP_PREFAB_SCALE, y: DROP_PREFAB_SCALE }; } /** 恢复为预制体基准缩放 */ @@ -615,6 +656,11 @@ export class WeaponDropView extends Component { this.node.setScale(base.x, base.y, 1); } + /** 停动画并恢复基准缩放后回池(unbind 清场用) */ + recycleToPool(): void { + this._recycle(); + } + /** * 设置透明度。 * @param value 0~255 @@ -721,8 +767,10 @@ export class BattleWeaponDropPresenter { const node = children[i]; if (!node?.isValid) continue; const view = node.getComponent(WeaponDropView); - if (!view) continue; - view.resetMotion(); + if (view) { + view.recycleToPool(); + continue; + } gg.res.putNode(node); } } diff --git a/assets/script/ui/BattleGame/Monster.ts b/assets/script/ui/BattleGame/Monster.ts index 4cae90c..edc7468 100644 --- a/assets/script/ui/BattleGame/Monster.ts +++ b/assets/script/ui/BattleGame/Monster.ts @@ -820,7 +820,7 @@ export class Monster extends Component { const sks = node.getComponentsInChildren(sp.Skeleton); for (let i = 0; i < sks.length; i++) { const sk = sks[i]; - const anim = String((sk as sp.Skeleton & { defaultAnimation?: string }).defaultAnimation || '').trim(); + const anim = BattleSpinePlayback.resolvePrefabAnim(sk); if (!anim) continue; sk.setAnimation(0, anim, false); const dur = BattleSpinePlayback.getAnimDuration(sk, anim); @@ -1226,7 +1226,7 @@ export class Monster extends Component { this._bleedFx = node; for (const sk of node.getComponentsInChildren(sp.Skeleton)) { sk.setAnimationCacheMode(sp.SpineAnimationCacheMode.SHARED_CACHE); - const anim = (sk as sp.Skeleton & { defaultAnimation?: string }).defaultAnimation; + const anim = BattleSpinePlayback.resolvePrefabAnim(sk); if (anim) sk.setAnimation(0, anim, true); } } else if (this._bleedFx.parent !== parent) {