| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- 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 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);
- }
-
- /**
- * 处理奖励动画事件
- */
- 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);
- }
-
- /**
- * 播放奖励动画
- * @param coinAmount 钞票数量
- * @param diamondAmount 钻石数量
- * @param onComplete 动画完成回调
- */
- public playRewardAnimation(coinAmount: number, diamondAmount: number, onComplete?: () => void) {
- 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) {
- // 所有钞票动画完成,更新数据
- this.saveDataManager.addMoney(amount, 'level_reward');
- // 通过事件系统通知UI更新
- EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
- 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) {
- // 所有钻石动画完成,更新数据
- this.saveDataManager.addDiamonds(amount, 'level_reward');
- // 通过事件系统通知UI更新
- EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
- 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) {
- 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);
- }
- }
- }
|