MoneyAni.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. import { _decorator, Component, Node, Vec3, tween, instantiate, Prefab, math, Label, director } from 'cc';
  2. import { SaveDataManager } from '../LevelSystem/SaveDataManager';
  3. import { TopBarController } from '../FourUI/TopBarController';
  4. import EventBus, { GameEvents } from '../Core/EventBus';
  5. import { Audio } from '../AudioManager/AudioManager';
  6. const { ccclass, property } = _decorator;
  7. /**
  8. * 奖励动画系统
  9. * 负责在关卡结束后播放钞票和钻石的奖励动画
  10. */
  11. @ccclass('MoneyAni')
  12. export class MoneyAni extends Component {
  13. @property({
  14. type: Prefab,
  15. tooltip: '钞票预制体'
  16. })
  17. public coinPrefab: Prefab = null;
  18. @property({
  19. type: Prefab,
  20. tooltip: '钻石预制体'
  21. })
  22. public diamondPrefab: Prefab = null;
  23. @property({
  24. type: Node,
  25. tooltip: '奖励生成起始位置节点(MainUI中的奖励显示区域)'
  26. })
  27. public rewardStartNode: Node = null;
  28. @property({
  29. type: Node,
  30. tooltip: 'Canvas节点,用于添加动画元素'
  31. })
  32. public canvasNode: Node = null;
  33. @property({
  34. type: Node,
  35. tooltip: '钞票目标节点(TopBar中的钞票标签)'
  36. })
  37. public coinTargetNode: Node = null;
  38. @property({
  39. type: Node,
  40. tooltip: '钻石目标节点(TopBar中的钻石标签)'
  41. })
  42. public diamondTargetNode: Node = null;
  43. private saveDataManager: SaveDataManager = null;
  44. onLoad() {
  45. this.saveDataManager = SaveDataManager.getInstance();
  46. // 监听奖励动画事件
  47. EventBus.getInstance().on('PLAY_REWARD_ANIMATION', this.onPlayRewardAnimation, this);
  48. }
  49. /**
  50. * 处理奖励动画事件
  51. */
  52. private onPlayRewardAnimation(data: {money: number, diamonds: number}) {
  53. console.log('[MoneyAni] 接收到奖励动画事件:', data);
  54. this.playRewardAnimation(data.money, data.diamonds);
  55. }
  56. onDestroy() {
  57. // 移除事件监听器
  58. EventBus.getInstance().off('PLAY_REWARD_ANIMATION', this.onPlayRewardAnimation, this);
  59. }
  60. /**
  61. * 播放奖励动画
  62. * @param coinAmount 钞票数量
  63. * @param diamondAmount 钻石数量
  64. * @param onComplete 动画完成回调
  65. */
  66. public playRewardAnimation(coinAmount: number, diamondAmount: number, onComplete?: () => void) {
  67. console.log(`[MoneyAni] 开始播放奖励动画 - 钞票: ${coinAmount}, 钻石: ${diamondAmount}`);
  68. if (!this.canvasNode) {
  69. console.error('[MoneyAni] Canvas节点未设置,请在编辑器中拖拽Canvas节点到canvasNode属性');
  70. if (onComplete) onComplete();
  71. return;
  72. }
  73. let animationsCompleted = 0;
  74. const totalAnimations = (coinAmount > 0 ? 1 : 0) + (diamondAmount > 0 ? 1 : 0);
  75. const onAnimationComplete = () => {
  76. animationsCompleted++;
  77. if (animationsCompleted >= totalAnimations) {
  78. console.log('[MoneyAni] 所有奖励动画播放完成');
  79. if (onComplete) onComplete();
  80. }
  81. };
  82. // 播放钞票动画
  83. if (coinAmount > 0) {
  84. this.playCoinAnimation(coinAmount, onAnimationComplete);
  85. }
  86. // 播放钻石动画(延迟0.2秒开始)
  87. if (diamondAmount > 0) {
  88. this.scheduleOnce(() => {
  89. this.playDiamondAnimation(diamondAmount, onAnimationComplete);
  90. }, 0.2);
  91. }
  92. // 如果没有奖励,直接调用完成回调
  93. if (totalAnimations === 0) {
  94. if (onComplete) onComplete();
  95. }
  96. }
  97. /**
  98. * 播放钞票动画
  99. */
  100. private playCoinAnimation(amount: number, onComplete?: () => void) {
  101. if (!this.coinPrefab) {
  102. console.error('[MoneyAni] 钞票预制体未设置');
  103. if (onComplete) onComplete();
  104. return;
  105. }
  106. // 使用装饰器引用的钞票目标节点
  107. if (!this.coinTargetNode) {
  108. console.error('[MoneyAni] 钞票目标节点未设置,请在编辑器中拖拽TopBar中的钞票标签节点');
  109. if (onComplete) onComplete();
  110. return;
  111. }
  112. const targetNode = this.coinTargetNode;
  113. // 生成钞票数量(最多10个,超过则按比例显示)
  114. const coinCount = Math.min(amount, 10);
  115. const startPos = this.getRewardStartPosition();
  116. console.log(`[MoneyAni] 生成${coinCount}个钞票动画`);
  117. let coinsCompleted = 0;
  118. for (let i = 0; i < coinCount; i++) {
  119. // 延迟生成,创造连续效果
  120. this.scheduleOnce(() => {
  121. this.createAndAnimateCoin(startPos, targetNode, () => {
  122. coinsCompleted++;
  123. if (coinsCompleted >= coinCount) {
  124. // 所有钞票动画完成,更新数据
  125. this.saveDataManager.addMoney(amount, 'level_reward');
  126. // 通过事件系统通知UI更新
  127. EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
  128. if (onComplete) onComplete();
  129. }
  130. });
  131. }, i * 0.1);
  132. }
  133. }
  134. /**
  135. * 播放钻石动画
  136. */
  137. private playDiamondAnimation(amount: number, onComplete?: () => void) {
  138. if (!this.diamondPrefab) {
  139. console.error('[MoneyAni] 钻石预制体未设置');
  140. if (onComplete) onComplete();
  141. return;
  142. }
  143. // 使用装饰器引用的钻石目标节点
  144. if (!this.diamondTargetNode) {
  145. console.error('[MoneyAni] 钻石目标节点未设置,请在编辑器中拖拽TopBar中的钻石标签节点');
  146. if (onComplete) onComplete();
  147. return;
  148. }
  149. const targetNode = this.diamondTargetNode;
  150. // 生成钻石数量(最多5个)
  151. const diamondCount = Math.min(amount, 5);
  152. const startPos = this.getRewardStartPosition();
  153. console.log(`[MoneyAni] 生成${diamondCount}个钻石动画`);
  154. let diamondsCompleted = 0;
  155. for (let i = 0; i < diamondCount; i++) {
  156. // 延迟生成
  157. this.scheduleOnce(() => {
  158. this.createAndAnimateDiamond(startPos, targetNode, () => {
  159. diamondsCompleted++;
  160. if (diamondsCompleted >= diamondCount) {
  161. // 所有钻石动画完成,更新数据
  162. this.saveDataManager.addDiamonds(amount, 'level_reward');
  163. // 通过事件系统通知UI更新
  164. EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
  165. if (onComplete) onComplete();
  166. }
  167. });
  168. }, i * 0.15);
  169. }
  170. }
  171. /**
  172. * 创建并播放单个钞票动画
  173. */
  174. private createAndAnimateCoin(startPos: Vec3, targetNode: Node, onComplete?: () => void) {
  175. const coinNode = instantiate(this.coinPrefab);
  176. this.canvasNode.addChild(coinNode);
  177. // 设置初始位置
  178. coinNode.setWorldPosition(startPos);
  179. // 生成随机散开位置
  180. const scatterPos = this.getScatterPosition(startPos);
  181. // 获取目标世界位置
  182. const targetWorldPos = new Vec3();
  183. targetNode.getWorldPosition(targetWorldPos);
  184. // 动画序列:散开 -> 飞向目标
  185. tween(coinNode)
  186. // 第一阶段:散开(带旋转和缩放)
  187. .parallel(
  188. tween().to(0.3, { worldPosition: scatterPos }, { easing: 'quadOut' }),
  189. tween().to(0.3, { scale: new Vec3(1.2, 1.2, 1.2) }, { easing: 'quadOut' }),
  190. tween().by(0.3, { eulerAngles: new Vec3(0, 0, 180) })
  191. )
  192. // 短暂停留
  193. .delay(0.1)
  194. // 播放音效并开始第二阶段:飞向目标(带缩小和旋转)
  195. .call(() => {
  196. Audio.playUISound('data/弹球音效/get money');
  197. })
  198. .parallel(
  199. tween().to(0.5, { worldPosition: targetWorldPos }, { easing: 'quadIn' }),
  200. tween().to(0.5, { scale: new Vec3(0.3, 0.3, 0.3) }, { easing: 'quadIn' }),
  201. tween().by(0.5, { eulerAngles: new Vec3(0, 0, 360) })
  202. )
  203. .call(() => {
  204. // 动画完成,销毁节点
  205. coinNode.destroy();
  206. if (onComplete) onComplete();
  207. })
  208. .start();
  209. }
  210. /**
  211. * 创建并播放单个钻石动画
  212. */
  213. private createAndAnimateDiamond(startPos: Vec3, targetNode: Node, onComplete?: () => void) {
  214. const diamondNode = instantiate(this.diamondPrefab);
  215. this.canvasNode.addChild(diamondNode);
  216. // 设置初始位置
  217. diamondNode.setWorldPosition(startPos);
  218. // 生成随机散开位置
  219. const scatterPos = this.getScatterPosition(startPos);
  220. // 获取目标世界位置
  221. const targetWorldPos = new Vec3();
  222. targetNode.getWorldPosition(targetWorldPos);
  223. // 动画序列:散开 -> 飞向目标
  224. tween(diamondNode)
  225. // 第一阶段:散开(带旋转和缩放)
  226. .parallel(
  227. tween().to(0.4, { worldPosition: scatterPos }, { easing: 'quadOut' }),
  228. tween().to(0.4, { scale: new Vec3(1.3, 1.3, 1.3) }, { easing: 'quadOut' }),
  229. tween().by(0.4, { eulerAngles: new Vec3(0, 0, -180) })
  230. )
  231. // 短暂停留
  232. .delay(0.15)
  233. // 播放音效并开始第二阶段:飞向目标(带缩小和旋转)
  234. .call(() => {
  235. Audio.playUISound('data/弹球音效/get money');
  236. })
  237. .parallel(
  238. tween().to(0.6, { worldPosition: targetWorldPos }, { easing: 'quadIn' }),
  239. tween().to(0.6, { scale: new Vec3(0.4, 0.4, 0.4) }, { easing: 'quadIn' }),
  240. tween().by(0.6, { eulerAngles: new Vec3(0, 0, -360) })
  241. )
  242. .call(() => {
  243. // 动画完成,销毁节点
  244. diamondNode.destroy();
  245. if (onComplete) onComplete();
  246. })
  247. .start();
  248. }
  249. /**
  250. * 获取奖励生成起始位置
  251. */
  252. private getRewardStartPosition(): Vec3 {
  253. if (this.rewardStartNode) {
  254. const worldPos = new Vec3();
  255. this.rewardStartNode.getWorldPosition(worldPos);
  256. return worldPos;
  257. }
  258. // 默认位置(屏幕中央偏下)
  259. return new Vec3(0, -200, 0);
  260. }
  261. /**
  262. * 获取散开位置
  263. */
  264. private getScatterPosition(startPos: Vec3): Vec3 {
  265. const scatterRadius = 150; // 散开半径
  266. const angle = math.randomRange(0, Math.PI * 2); // 随机角度
  267. const distance = math.randomRange(50, scatterRadius); // 随机距离
  268. return new Vec3(
  269. startPos.x + Math.cos(angle) * distance,
  270. startPos.y + Math.sin(angle) * distance,
  271. startPos.z
  272. );
  273. }
  274. /**
  275. * 静态方法:播放奖励动画(需要传入实例)
  276. * @param moneyAniInstance MoneyAni组件实例
  277. * @param coinAmount 钞票数量
  278. * @param diamondAmount 钻石数量
  279. * @param onComplete 完成回调
  280. */
  281. public static playRewardWithInstance(moneyAniInstance: MoneyAni, coinAmount: number, diamondAmount: number, onComplete?: () => void) {
  282. if (!moneyAniInstance) {
  283. console.error('[MoneyAni] MoneyAni实例为空,请传入有效的MoneyAni组件实例');
  284. if (onComplete) onComplete();
  285. return;
  286. }
  287. moneyAniInstance.playRewardAnimation(coinAmount, diamondAmount, onComplete);
  288. }
  289. /**
  290. * 静态方法:播放奖励动画(简化版本)
  291. * 自动查找场景中的MoneyAni组件
  292. * @param coinAmount 钞票数量
  293. * @param diamondAmount 钻石数量
  294. * @param onComplete 完成回调
  295. */
  296. public static playReward(coinAmount: number, diamondAmount: number, onComplete?: () => void) {
  297. // 尝试从director获取场景中的MoneyAni组件
  298. const scene = director.getScene();
  299. if (!scene) {
  300. console.error('[MoneyAni] 无法获取当前场景');
  301. if (onComplete) onComplete();
  302. return;
  303. }
  304. // 递归查找MoneyAni组件
  305. const findMoneyAni = (node: Node): MoneyAni | null => {
  306. const moneyAni = node.getComponent(MoneyAni);
  307. if (moneyAni) return moneyAni;
  308. for (const child of node.children) {
  309. const result = findMoneyAni(child);
  310. if (result) return result;
  311. }
  312. return null;
  313. };
  314. const moneyAni = findMoneyAni(scene);
  315. if (!moneyAni) {
  316. console.error('[MoneyAni] 场景中未找到MoneyAni组件,请确保场景中存在MoneyAni组件');
  317. if (onComplete) onComplete();
  318. return;
  319. }
  320. moneyAni.playRewardAnimation(coinAmount, diamondAmount, onComplete);
  321. }
  322. /**
  323. * 根据关卡配置播放奖励动画
  324. */
  325. public async playLevelReward(level: number, onComplete?: () => void) {
  326. try {
  327. const rewards = await this.saveDataManager.getLevelRewardsFromConfig(level);
  328. if (rewards) {
  329. this.playRewardAnimation(rewards.money, rewards.diamonds, onComplete);
  330. } else {
  331. console.warn(`[MoneyAni] 无法获取关卡${level}的奖励配置,使用默认奖励`);
  332. // 使用默认奖励
  333. this.playRewardAnimation(50, 5, onComplete);
  334. }
  335. } catch (error) {
  336. console.error('[MoneyAni] 获取关卡奖励配置时出错:', error);
  337. // 使用默认奖励
  338. this.playRewardAnimation(50, 5, onComplete);
  339. }
  340. }
  341. }