import { isValid, Node, Tween, tween, UIOpacity, UITransform, Vec3 } from 'cc'; import { GBundle } from '../../game/ConfigRes'; /** 宝箱预制路径 */ const CHEST_TIP_PATH = 'prefab/'; /** 宝箱预制名 */ const CHEST_TIP_NAME = '宝箱'; /** 飞入时长(秒) */ const FLY_SEC = 0.55; /** 终点缩放 */ const FLY_END_SCALE = 10; /** 复用世界坐标 */ const _world = new Vec3(); /** 复用本地坐标 */ const _local = new Vec3(); /** 飞向层本地中心 */ const _endLocal = new Vec3(0, 0, 0); /** * 开挖宝箱飞向屏幕中央放大渐隐。 */ export class BattleDigChestFxPresenter { private _layer: Node | null = null; private _flying: Node | null = null; private _gen = 0; /** * @param layer 飞行层(ui_fly) */ bind(layer: Node | null): void { this.clear(); this._layer = layer; } /** 停止并回收进行中的飞入 */ clear(): void { this._gen++; this._recycleFlying(); this._layer = null; } /** * 宝箱从起点飞向屏中,放大到 10 倍并渐隐,结束后回调。 * @param worldPos 起点世界坐标 * @param onDone 结束回调 */ playFly(worldPos: Vec3 | null, onDone: () => void): void { this._recycleFlying(); const layer = this._layer; if (!layer?.isValid || !worldPos) { console.warn('[BattleDigChestFx] 飞入层或起点无效,跳过飞入'); onDone(); return; } if (typeof gg === 'undefined' || !gg.res?.getNode) { console.warn('[BattleDigChestFx] gg.res 未就绪,跳过飞入'); onDone(); return; } const tip = gg.res.getNode(CHEST_TIP_PATH, CHEST_TIP_NAME, GBundle.BattleGame); if (!tip?.isValid) { console.warn('[BattleDigChestFx] 缺少宝箱,跳过飞入'); onDone(); return; } const fly = tip; fly.parent = layer; fly.setSiblingIndex(layer.children.length - 1); fly.active = true; fly.setScale(1, 1, 1); let opacity = fly.getComponent(UIOpacity); if (!opacity) opacity = fly.addComponent(UIOpacity); opacity.opacity = 255; _world.set(worldPos.x, worldPos.y, worldPos.z); const ut = layer.getComponent(UITransform); if (ut) { ut.convertToNodeSpaceAR(_world, _local); fly.setPosition(_local); } else { fly.setWorldPosition(_world); } this._flying = fly; const gen = ++this._gen; // ui_fly 居中锚点时本地 (0,0) 即屏中 const endPos = _endLocal.clone(); Tween.stopAllByTarget(fly); tween(fly) .to(FLY_SEC, { position: endPos, scale: new Vec3(FLY_END_SCALE, FLY_END_SCALE, 1) }, { easing: 'sineOut' }) .start(); tween(opacity) .to(FLY_SEC, { opacity: 0 }, { easing: 'sineIn' }) .call(() => { if (gen !== this._gen) return; this._recycleFlying(); onDone(); }) .start(); } private _recycleFlying(): void { const fly = this._flying; this._flying = null; if (!fly || !isValid(fly)) return; Tween.stopAllByTarget(fly); const op = fly.getComponent(UIOpacity); if (op) Tween.stopAllByTarget(op); fly.setScale(1, 1, 1); if (op) op.opacity = 255; if (fly['resKey'] && typeof gg !== 'undefined' && gg.res?.putNode) { gg.res.putNode(fly); } else { fly.destroy(); } } }