import { _decorator, Component, Node, Vec3, tween, instantiate, Prefab, math, Label, director } from 'cc'; import { SaveDataManager } from '../LevelSystem/SaveDataManager'; import { TopBarController } from '../FourUI/TopBarController'; import EventBus, { GameEvents } from '../Core/EventBus'; import { Audio } from '../AudioManager/AudioManager'; const { ccclass, property } = _decorator; /** * 奖励动画系统 * 负责在关卡结束后播放钞票和钻石的奖励动画 */ @ccclass('MoneyAni') export class MoneyAni extends Component { @property({ type: Prefab, tooltip: '钞票预制体' }) public coinPrefab: Prefab = null; @property({ type: Prefab, tooltip: '钻石预制体' }) public diamondPrefab: Prefab = null; @property({ type: Node, tooltip: '奖励生成起始位置节点(MainUI中的奖励显示区域)' }) public rewardStartNode: Node = null; @property({ type: Node, tooltip: '钞票动画起始位置节点(Canvas/ShopUI/ScrollView/view/content/bill/Sprite/Sprite)' }) public coinStartNode: Node = null; @property({ type: Node, tooltip: '钻石动画起始位置节点(Canvas/ShopUI/ScrollView/view/content/diamond/Sprite/Sprite)' }) public diamondStartNode: Node = null; @property({ type: Node, tooltip: 'Canvas节点,用于添加动画元素' }) public canvasNode: Node = null; @property({ type: Node, tooltip: '钞票目标节点(TopBar中的钞票标签)' }) public coinTargetNode: Node = null; @property({ type: Node, tooltip: '钻石目标节点(TopBar中的钻石标签)' }) public diamondTargetNode: Node = null; private saveDataManager: SaveDataManager = null; // 新增:奖励动画延迟的状态与挂起数据 private gatingEnabled: boolean = false; private pendingReward: { money: number; diamonds: number } | null = null; private pendingOnComplete: (() => void) | null = null; // 新增:统一派发奖励动画完成事件 private emitRewardCompleted(money: number, diamonds: number): void { EventBus.getInstance().emit(GameEvents.REWARD_ANIMATION_COMPLETED, { money, diamonds }); } onLoad() { this.saveDataManager = SaveDataManager.getInstance(); // 监听奖励动画事件 EventBus.getInstance().on('PLAY_REWARD_ANIMATION', this.onPlayRewardAnimation, this); // 新增:监听显示GainUI与确认事件 EventBus.getInstance().on(GameEvents.SHOW_GAIN_UI, this.onShowGainUI, this); EventBus.getInstance().on(GameEvents.GAIN_UI_CONFIRMED, this.onGainUIConfirmed, this); } /** * 处理奖励动画事件 */ private onPlayRewardAnimation(data: {money: number, diamonds: number}) { console.log('[MoneyAni] 接收到奖励动画事件:', data); this.playRewardAnimation(data.money, data.diamonds); } onDestroy() { // 移除事件监听器 EventBus.getInstance().off('PLAY_REWARD_ANIMATION', this.onPlayRewardAnimation, this); EventBus.getInstance().off(GameEvents.SHOW_GAIN_UI, this.onShowGainUI, this); EventBus.getInstance().off(GameEvents.GAIN_UI_CONFIRMED, this.onGainUIConfirmed, this); } /** * 播放奖励动画 * @param coinAmount 钞票数量 * @param diamondAmount 钻石数量 * @param onComplete 动画完成回调 */ public playRewardAnimation(coinAmount: number, diamondAmount: number, onComplete?: () => void) { if (this.gatingEnabled) { this.pendingReward = { money: coinAmount, diamonds: diamondAmount }; this.pendingOnComplete = onComplete || null; console.log('[MoneyAni] 已延迟奖励动画,等待GainUI确认'); return; } console.log(`[MoneyAni] 开始播放奖励动画 - 钞票: ${coinAmount}, 钻石: ${diamondAmount}`); // 如果奖励为0,直接返回不播放动画 if (coinAmount <= 0 && diamondAmount <= 0) { console.log('[MoneyAni] 奖励为0,跳过动画播放'); this.emitRewardCompleted(coinAmount, diamondAmount); if (onComplete) onComplete(); return; } if (!this.canvasNode) { console.error('[MoneyAni] Canvas节点未设置,请在编辑器中拖拽Canvas节点到canvasNode属性'); this.emitRewardCompleted(coinAmount, diamondAmount); if (onComplete) onComplete(); return; } let animationsCompleted = 0; const totalAnimations = (coinAmount > 0 ? 1 : 0) + (diamondAmount > 0 ? 1 : 0); const onAnimationComplete = () => { animationsCompleted++; if (animationsCompleted >= totalAnimations) { console.log('[MoneyAni] 所有奖励动画播放完成'); this.emitRewardCompleted(coinAmount, diamondAmount); if (onComplete) onComplete(); } }; // 播放钞票动画 if (coinAmount > 0) { this.playCoinAnimation(coinAmount, onAnimationComplete); } // 播放钻石动画(延迟0.2秒开始) if (diamondAmount > 0) { this.scheduleOnce(() => { this.playDiamondAnimation(diamondAmount, onAnimationComplete); }, 0.2); } // 如果没有奖励,直接调用完成回调 if (totalAnimations === 0) { this.emitRewardCompleted(coinAmount, diamondAmount); if (onComplete) onComplete(); } } /** * 播放钞票动画 */ private playCoinAnimation(amount: number, onComplete?: () => void) { if (!this.coinPrefab) { console.error('[MoneyAni] 钞票预制体未设置'); if (onComplete) onComplete(); return; } // 如果钞票数量为0或负数,直接返回不播放动画和音效 if (amount <= 0) { console.log('[MoneyAni] 钞票数量为0,跳过钞票动画'); if (onComplete) onComplete(); return; } // 使用装饰器引用的钞票目标节点 if (!this.coinTargetNode) { console.error('[MoneyAni] 钞票目标节点未设置,请在编辑器中拖拽TopBar中的钞票标签节点'); if (onComplete) onComplete(); return; } const targetNode = this.coinTargetNode; // 生成钞票数量(最多10个,超过则按比例显示) const coinCount = Math.min(amount, 10); const startPos = this.getCoinStartPosition(); console.log(`[MoneyAni] 生成${coinCount}个钞票动画`); let coinsCompleted = 0; for (let i = 0; i < coinCount; i++) { // 延迟生成,创造连续效果 this.scheduleOnce(() => { this.createAndAnimateCoin(startPos, targetNode, () => { coinsCompleted++; if (coinsCompleted >= coinCount) { // 所有钞票动画完成 if (onComplete) onComplete(); } }); }, i * 0.1); } } /** * 播放钻石动画 */ private playDiamondAnimation(amount: number, onComplete?: () => void) { if (!this.diamondPrefab) { console.error('[MoneyAni] 钻石预制体未设置'); if (onComplete) onComplete(); return; } // 如果钻石数量为0或负数,直接返回不播放动画和音效 if (amount <= 0) { console.log('[MoneyAni] 钻石数量为0,跳过钻石动画'); if (onComplete) onComplete(); return; } // 使用装饰器引用的钻石目标节点 if (!this.diamondTargetNode) { console.error('[MoneyAni] 钻石目标节点未设置,请在编辑器中拖拽TopBar中的钻石标签节点'); if (onComplete) onComplete(); return; } const targetNode = this.diamondTargetNode; // 生成钻石数量(最多5个) const diamondCount = Math.min(amount, 5); const startPos = this.getDiamondStartPosition(); console.log(`[MoneyAni] 生成${diamondCount}个钻石动画`); let diamondsCompleted = 0; for (let i = 0; i < diamondCount; i++) { // 延迟生成 this.scheduleOnce(() => { this.createAndAnimateDiamond(startPos, targetNode, () => { diamondsCompleted++; if (diamondsCompleted >= diamondCount) { // 所有钻石动画完成 if (onComplete) onComplete(); } }); }, i * 0.15); } } /** * 创建并播放单个钞票动画 */ private createAndAnimateCoin(startPos: Vec3, targetNode: Node, onComplete?: () => void) { const coinNode = instantiate(this.coinPrefab); this.canvasNode.addChild(coinNode); // 设置初始位置 coinNode.setWorldPosition(startPos); // 生成随机散开位置 const scatterPos = this.getScatterPosition(startPos); // 获取目标世界位置 const targetWorldPos = new Vec3(); targetNode.getWorldPosition(targetWorldPos); // 动画序列:散开 -> 飞向目标 tween(coinNode) // 第一阶段:散开(带旋转和缩放) .parallel( tween().to(0.3, { worldPosition: scatterPos }, { easing: 'quadOut' }), tween().to(0.3, { scale: new Vec3(1.2, 1.2, 1.2) }, { easing: 'quadOut' }), tween().by(0.3, { eulerAngles: new Vec3(0, 0, 180) }) ) // 短暂停留 .delay(0.1) // 播放音效并开始第二阶段:飞向目标(带缩小和旋转) .call(() => { Audio.playUISound('data/弹球音效/get money'); }) .parallel( tween().to(0.5, { worldPosition: targetWorldPos }, { easing: 'quadIn' }), tween().to(0.5, { scale: new Vec3(0.3, 0.3, 0.3) }, { easing: 'quadIn' }), tween().by(0.5, { eulerAngles: new Vec3(0, 0, 360) }) ) .call(() => { // 动画完成,销毁节点 coinNode.destroy(); if (onComplete) onComplete(); }) .start(); } /** * 创建并播放单个钻石动画 */ private createAndAnimateDiamond(startPos: Vec3, targetNode: Node, onComplete?: () => void) { const diamondNode = instantiate(this.diamondPrefab); this.canvasNode.addChild(diamondNode); // 设置初始位置 diamondNode.setWorldPosition(startPos); // 生成随机散开位置 const scatterPos = this.getScatterPosition(startPos); // 获取目标世界位置 const targetWorldPos = new Vec3(); targetNode.getWorldPosition(targetWorldPos); // 动画序列:散开 -> 飞向目标 tween(diamondNode) // 第一阶段:散开(带旋转和缩放) .parallel( tween().to(0.4, { worldPosition: scatterPos }, { easing: 'quadOut' }), tween().to(0.4, { scale: new Vec3(1.3, 1.3, 1.3) }, { easing: 'quadOut' }), tween().by(0.4, { eulerAngles: new Vec3(0, 0, -180) }) ) // 短暂停留 .delay(0.15) // 播放音效并开始第二阶段:飞向目标(带缩小和旋转) .call(() => { Audio.playUISound('data/弹球音效/get money'); }) .parallel( tween().to(0.6, { worldPosition: targetWorldPos }, { easing: 'quadIn' }), tween().to(0.6, { scale: new Vec3(0.4, 0.4, 0.4) }, { easing: 'quadIn' }), tween().by(0.6, { eulerAngles: new Vec3(0, 0, -360) }) ) .call(() => { // 动画完成,销毁节点 diamondNode.destroy(); if (onComplete) onComplete(); }) .start(); } /** * 获取奖励生成起始位置 */ private getRewardStartPosition(): Vec3 { if (this.rewardStartNode) { const worldPos = new Vec3(); this.rewardStartNode.getWorldPosition(worldPos); return worldPos; } // 默认位置(屏幕中央偏下) return new Vec3(0, -200, 0); } /** * 获取钞票动画起始位置 */ private getCoinStartPosition(): Vec3 { if (this.coinStartNode) { const worldPos = new Vec3(); this.coinStartNode.getWorldPosition(worldPos); return worldPos; } // 如果没有设置专门的钞票起始节点,使用通用的奖励起始位置 return this.getRewardStartPosition(); } /** * 获取钻石动画起始位置 */ private getDiamondStartPosition(): Vec3 { if (this.diamondStartNode) { const worldPos = new Vec3(); this.diamondStartNode.getWorldPosition(worldPos); return worldPos; } // 如果没有设置专门的钻石起始节点,使用通用的奖励起始位置 return this.getRewardStartPosition(); } /** * 获取散开位置 */ private getScatterPosition(startPos: Vec3): Vec3 { const scatterRadius = 150; // 散开半径 const angle = math.randomRange(0, Math.PI * 2); // 随机角度 const distance = math.randomRange(50, scatterRadius); // 随机距离 return new Vec3( startPos.x + Math.cos(angle) * distance, startPos.y + Math.sin(angle) * distance, startPos.z ); } /** * 静态方法:播放奖励动画(需要传入实例) * @param moneyAniInstance MoneyAni组件实例 * @param coinAmount 钞票数量 * @param diamondAmount 钻石数量 * @param onComplete 完成回调 */ public static playRewardWithInstance(moneyAniInstance: MoneyAni, coinAmount: number, diamondAmount: number, onComplete?: () => void) { if (!moneyAniInstance) { console.error('[MoneyAni] MoneyAni实例为空,请传入有效的MoneyAni组件实例'); if (onComplete) onComplete(); return; } moneyAniInstance.playRewardAnimation(coinAmount, diamondAmount, onComplete); } /** * 静态方法:播放奖励动画(简化版本) * 自动查找场景中的MoneyAni组件 * @param coinAmount 钞票数量 * @param diamondAmount 钻石数量 * @param onComplete 完成回调 */ public static playReward(coinAmount: number, diamondAmount: number, onComplete?: () => void) { // 尝试从director获取场景中的MoneyAni组件 const scene = director.getScene(); if (!scene) { console.error('[MoneyAni] 无法获取当前场景'); if (onComplete) onComplete(); return; } // 递归查找MoneyAni组件 const findMoneyAni = (node: Node): MoneyAni | null => { const moneyAni = node.getComponent(MoneyAni); if (moneyAni) return moneyAni; for (const child of node.children) { const result = findMoneyAni(child); if (result) return result; } return null; }; const moneyAni = findMoneyAni(scene); if (!moneyAni) { console.error('[MoneyAni] 场景中未找到MoneyAni组件,请确保场景中存在MoneyAni组件'); if (onComplete) onComplete(); return; } moneyAni.playRewardAnimation(coinAmount, diamondAmount, onComplete); } /** * 根据关卡配置播放奖励动画 */ public async playLevelReward(level: number, onComplete?: () => void) { try { const rewards = await this.saveDataManager.getLevelRewardsFromConfig(level); if (rewards) { if (this.gatingEnabled) { this.pendingReward = { money: rewards.money, diamonds: rewards.diamonds }; this.pendingOnComplete = onComplete || null; console.log('[MoneyAni] 已延迟关卡奖励动画,等待GainUI确认'); } else { this.playRewardAnimation(rewards.money, rewards.diamonds, onComplete); } } else { console.warn(`[MoneyAni] 无法获取关卡${level}的奖励配置,使用默认奖励`); this.playRewardAnimation(50, 5, onComplete); } } catch (error) { console.error('[MoneyAni] 获取关卡奖励配置时出错:', error); this.playRewardAnimation(50, 5, onComplete); } } private onShowGainUI = () => { this.gatingEnabled = true; console.log('[MoneyAni] 检测到SHOW_GAIN_UI,启用奖励动画延迟'); } private onGainUIConfirmed = () => { console.log('[MoneyAni] 检测到GAIN_UI_CONFIRMED,准备播放延迟的奖励动画'); const pending = this.pendingReward; const onComplete = this.pendingOnComplete || undefined; this.pendingReward = null; this.pendingOnComplete = null; this.gatingEnabled = false; if (pending) { this.playRewardAnimation(pending.money, pending.diamonds, onComplete); } } }