GameEnd.ts 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  1. import { _decorator, Component, Node, Label, Button, tween, Tween, Vec3, UIOpacity, find } from 'cc';
  2. import { SaveDataManager } from '../LevelSystem/SaveDataManager';
  3. import { InGameManager, GameState } from '../LevelSystem/IN_game';
  4. import EventBus, { GameEvents } from '../Core/EventBus';
  5. import { Audio } from '../AudioManager/AudioManager';
  6. const { ccclass, property } = _decorator;
  7. /**
  8. * 游戏结算界面管理器
  9. * 负责处理游戏结束后的奖励显示和双倍奖励功能
  10. */
  11. @ccclass('GameEnd')
  12. export class GameEnd extends Component {
  13. // === UI节点引用 ===
  14. @property({
  15. type: Button,
  16. tooltip: '双倍奖励按钮 (Canvas/GameEnd/double)'
  17. })
  18. public doubleButton: Button = null;
  19. @property({
  20. type: Label,
  21. tooltip: '钞票数量显示 '
  22. })
  23. public moneyLabel: Label = null;
  24. @property({
  25. type: Label,
  26. tooltip: '钻石数量显示 '
  27. })
  28. public diamondLabel: Label = null;
  29. @property({
  30. type: Button,
  31. tooltip: '继续按钮 (Canvas/GameEnd/Continue)'
  32. })
  33. public continueButton: Button = null;
  34. @property({
  35. type: InGameManager,
  36. tooltip: '游戏管理器组件'
  37. })
  38. public inGameManager: InGameManager = null;
  39. @property({
  40. type: Label,
  41. tooltip: 'EndLabel文本显示 (Canvas/GameEnd/Sprite/EndLabel)'
  42. })
  43. public endLabel: Label = null;
  44. // === 动画相关属性 ===
  45. @property({ tooltip: '动画持续时间(秒)' })
  46. public animationDuration: number = 0.3;
  47. // 淡入淡出和缩放动画都直接作用于当前节点(Canvas/GameEnd)
  48. // 不需要额外的目标节点属性
  49. private _originalScale: Vec3 = new Vec3();
  50. // === 私有属性 ===
  51. private saveDataManager: SaveDataManager = null;
  52. public currentRewards: {money: number, diamonds: number} = {money: -1, diamonds: -1};
  53. private hasDoubledReward: boolean = false;
  54. private isGameSuccess: boolean = false;
  55. private hasProcessedGameEnd: boolean = false; // 添加处理状态标志
  56. onLoad() {
  57. console.log('[GameEnd] onLoad方法被调用');
  58. this.setupEventListeners();
  59. }
  60. start() {
  61. console.log('[GameEnd] start方法被调用');
  62. // 保存原始缩放值
  63. if (this.node) {
  64. this._originalScale.set(this.node.scale);
  65. }
  66. // 设置事件监听器
  67. this.setupEventListeners();
  68. // 初始化为隐藏状态(但保持节点激活以便接收事件)
  69. this.initializeHiddenStateKeepActive();
  70. console.log('[GameEnd] start方法完成,面板已初始化为隐藏状态');
  71. }
  72. onEnable() {
  73. console.log('[GameEnd] onEnable方法被调用,节点已激活');
  74. // 初始化管理器
  75. this.initializeManagers();
  76. // UI节点已通过装饰器挂载,无需自动查找
  77. // 绑定按钮事件
  78. this.bindButtonEvents();
  79. // 初始化UI状态
  80. this.initializeUI();
  81. // 检查当前游戏状态并处理奖励(解决时序问题)
  82. this.checkAndHandleGameState();
  83. }
  84. /**
  85. * 初始化管理器
  86. */
  87. private initializeManagers() {
  88. this.saveDataManager = SaveDataManager.getInstance();
  89. // InGameManager已通过装饰器挂载,无需查找
  90. // 如果UI组件未在编辑器中绑定,尝试自动查找
  91. this.autoFindUIComponents();
  92. }
  93. /**
  94. * 自动查找UI组件(如果编辑器中未绑定)
  95. */
  96. private autoFindUIComponents() {
  97. // 自动查找moneyLabel
  98. if (!this.moneyLabel) {
  99. // 尝试多个可能的路径
  100. const moneyLabelPaths = [
  101. 'ResourceNode/MoneyResourceNode/MoneyLabel',
  102. 'Sprite/ResourceNode/MoneyResourceNode/MoneyLabel',
  103. 'ResourceNode/MoneyLabel',
  104. 'MoneyResourceNode/MoneyLabel'
  105. ];
  106. for (const path of moneyLabelPaths) {
  107. const moneyLabelNode = this.node.getChildByPath(path);
  108. if (moneyLabelNode) {
  109. this.moneyLabel = moneyLabelNode.getComponent(Label);
  110. if (this.moneyLabel) {
  111. console.log(`[GameEnd] 自动找到moneyLabel,路径: ${path}`);
  112. break;
  113. }
  114. }
  115. }
  116. // 如果还是没找到,尝试递归查找
  117. if (!this.moneyLabel) {
  118. this.moneyLabel = this.findLabelByName(this.node, 'MoneyLabel');
  119. if (this.moneyLabel) {
  120. console.log('[GameEnd] 通过递归查找找到moneyLabel');
  121. }
  122. }
  123. }
  124. // 自动查找diamondLabel
  125. if (!this.diamondLabel) {
  126. // 尝试多个可能的路径
  127. const diamondLabelPaths = [
  128. 'ResourceNode/DiamondResourceNode/DiamondLabel',
  129. 'Sprite/ResourceNode/DiamondResourceNode/DiamondLabel',
  130. 'ResourceNode/DiamondLabel',
  131. 'DiamondResourceNode/DiamondLabel'
  132. ];
  133. for (const path of diamondLabelPaths) {
  134. const diamondLabelNode = this.node.getChildByPath(path);
  135. if (diamondLabelNode) {
  136. this.diamondLabel = diamondLabelNode.getComponent(Label);
  137. if (this.diamondLabel) {
  138. console.log(`[GameEnd] 自动找到diamondLabel,路径: ${path}`);
  139. break;
  140. }
  141. }
  142. }
  143. // 如果还是没找到,尝试递归查找
  144. if (!this.diamondLabel) {
  145. this.diamondLabel = this.findLabelByName(this.node, 'DiamondLabel');
  146. if (this.diamondLabel) {
  147. console.log('[GameEnd] 通过递归查找找到diamondLabel');
  148. }
  149. }
  150. }
  151. console.log('[GameEnd] UI组件自动查找完成 - moneyLabel:', !!this.moneyLabel, 'diamondLabel:', !!this.diamondLabel);
  152. // 如果仍然没有找到组件,打印节点结构用于调试
  153. if (!this.moneyLabel || !this.diamondLabel) {
  154. console.warn('[GameEnd] 部分UI组件未找到,打印节点结构用于调试:');
  155. this.printNodeStructure(this.node, 0, 3); // 最多打印3层
  156. }
  157. }
  158. /**
  159. * 递归查找指定名称的Label组件
  160. */
  161. private findLabelByName(node: Node, targetName: string): Label | null {
  162. // 检查当前节点
  163. if (node.name === targetName) {
  164. const label = node.getComponent(Label);
  165. if (label) return label;
  166. }
  167. // 递归检查子节点
  168. for (const child of node.children) {
  169. const result = this.findLabelByName(child, targetName);
  170. if (result) return result;
  171. }
  172. return null;
  173. }
  174. /**
  175. * 打印节点结构用于调试
  176. */
  177. private printNodeStructure(node: Node, depth: number, maxDepth: number) {
  178. if (depth > maxDepth) return;
  179. const indent = ' '.repeat(depth);
  180. const components = node.getComponents(Component).map(c => c.constructor.name).join(', ');
  181. console.log(`${indent}${node.name} [${components || 'No Components'}]`);
  182. for (const child of node.children) {
  183. this.printNodeStructure(child, depth + 1, maxDepth);
  184. }
  185. }
  186. /**
  187. * 绑定按钮事件
  188. */
  189. private bindButtonEvents() {
  190. if (this.doubleButton) {
  191. this.doubleButton.node.on(Button.EventType.CLICK, this.onDoubleButtonClick, this);
  192. }
  193. if (this.continueButton) {
  194. this.continueButton.node.on(Button.EventType.CLICK, this.onContinueButtonClick, this);
  195. }
  196. }
  197. /**
  198. * 设置事件监听器
  199. */
  200. private setupEventListeners() {
  201. console.log('[GameEnd] 开始设置事件监听器');
  202. const eventBus = EventBus.getInstance();
  203. // 监听游戏成功事件
  204. eventBus.on(GameEvents.GAME_SUCCESS, this.onGameSuccess, this);
  205. console.log('[GameEnd] 已注册GAME_SUCCESS事件监听器');
  206. // 监听游戏失败事件
  207. eventBus.on(GameEvents.GAME_DEFEAT, this.onGameDefeat, this);
  208. console.log('[GameEnd] 已注册GAME_DEFEAT事件监听器');
  209. // 监听游戏开始事件
  210. eventBus.on(GameEvents.GAME_START, this.onGameStart, this);
  211. console.log('[GameEnd] 已注册GAME_START事件监听器');
  212. // 监听UI重置事件
  213. eventBus.on(GameEvents.RESET_UI_STATES, this.onResetUI, this);
  214. console.log('[GameEnd] 事件监听器设置完成');
  215. }
  216. /**
  217. * 初始化UI状态
  218. */
  219. private initializeUI() {
  220. // 重置状态
  221. this.hasDoubledReward = false;
  222. this.currentRewards = {money: 0, diamonds: 0};
  223. console.log('[GameEnd] UI状态初始化完成');
  224. }
  225. /**
  226. * 检查当前游戏状态并处理奖励(解决时序问题)
  227. */
  228. private checkAndHandleGameState() {
  229. console.log('[GameEnd] 检查当前游戏状态');
  230. if (!this.inGameManager) {
  231. console.log('[GameEnd] InGameManager未初始化,无法检查游戏状态');
  232. return;
  233. }
  234. const currentState = this.inGameManager.getCurrentState();
  235. console.log('[GameEnd] 当前游戏状态:', currentState);
  236. // 如果游戏已经结束,主动处理奖励
  237. if (currentState === GameState.SUCCESS) {
  238. console.log('[GameEnd] 检测到游戏成功状态,主动处理奖励');
  239. this.isGameSuccess = true;
  240. this.calculateAndShowRewards();
  241. } else if (currentState === GameState.DEFEAT) {
  242. console.log('[GameEnd] 检测到游戏失败状态,主动处理奖励');
  243. this.isGameSuccess = false;
  244. this.calculateAndShowRewards();
  245. }
  246. }
  247. /**
  248. * 处理游戏成功事件
  249. * 统一处理游戏成功逻辑:状态切换 + UI显示 + 奖励计算
  250. */
  251. private onGameSuccess() {
  252. console.log('[GameEnd] 接收到GAME_SUCCESS事件');
  253. console.log('[GameEnd] 游戏成功事件处理,开始统一处理流程');
  254. // 1. 清理敌人(阶段一:UI弹出阶段的职责)
  255. this.clearAllEnemies();
  256. // 2. 设置游戏状态为成功(如果还未设置)
  257. if (this.inGameManager && this.inGameManager.getCurrentState() !== GameState.SUCCESS) {
  258. this.inGameManager.setCurrentState(GameState.SUCCESS);
  259. console.log('[GameEnd] 已将游戏状态切换为SUCCESS');
  260. }
  261. // 3. 播放游戏成功音效
  262. Audio.playUISound('data/弹球音效/win');
  263. console.log('[GameEnd] 已播放游戏成功音效');
  264. // 4. 设置EndLabel文本
  265. this.setEndLabelText('SUCCESS');
  266. // 5. 设置成功标志
  267. this.isGameSuccess = true;
  268. // 6. 计算和显示奖励(包含面板动画显示)
  269. this.calculateAndShowRewards();
  270. }
  271. /**
  272. * 处理游戏失败事件
  273. * 统一处理游戏失败逻辑:状态切换 + UI显示 + 奖励计算
  274. */
  275. private onGameDefeat() {
  276. console.log('[GameEnd] 接收到GAME_DEFEAT事件');
  277. console.log('[GameEnd] 游戏失败事件处理,开始统一处理流程');
  278. // 1. 清理敌人(阶段一:UI弹出阶段的职责)
  279. this.clearAllEnemies();
  280. // 2. 设置游戏状态为失败(如果还未设置)
  281. if (this.inGameManager && this.inGameManager.getCurrentState() !== GameState.DEFEAT) {
  282. this.inGameManager.setCurrentState(GameState.DEFEAT);
  283. console.log('[GameEnd] 已将游戏状态切换为DEFEAT');
  284. }
  285. // 3. 播放游戏失败音效
  286. Audio.playUISound('data/弹球音效/lose');
  287. console.log('[GameEnd] 已播放游戏失败音效');
  288. // 4. 设置EndLabel文本
  289. this.setEndLabelText('DEFEAT');
  290. // 5. 设置失败标志
  291. this.isGameSuccess = false;
  292. // 6. 计算和显示奖励(包含面板动画显示)
  293. this.calculateAndShowRewards();
  294. }
  295. /**
  296. * 游戏开始处理
  297. */
  298. private onGameStart() {
  299. console.log('[GameEnd] 收到游戏开始事件,重置奖励显示');
  300. // 重置奖励显示,为新游戏做准备
  301. this.currentRewards = {money: 0, diamonds: 0};
  302. this.hasProcessedGameEnd = false;
  303. this.hasDoubledReward = false;
  304. this.isGameSuccess = false;
  305. }
  306. /**
  307. * 计算并显示奖励
  308. */
  309. private async calculateAndShowRewards() {
  310. console.log('[GameEnd] 开始计算并显示奖励');
  311. // 防止重复计算奖励(使用专用标志判断)
  312. if (this.hasProcessedGameEnd) {
  313. console.log('[GameEnd] 游戏结束已处理过,跳过重复计算,直接显示面板');
  314. this.updateRewardDisplay();
  315. // 确保面板显示
  316. this.showEndPanelWithAnimation();
  317. return;
  318. }
  319. // 标记为已处理,防止重复执行
  320. this.hasProcessedGameEnd = true;
  321. if (!this.saveDataManager) {
  322. console.error('[GameEnd] SaveDataManager未初始化');
  323. return;
  324. }
  325. const currentLevel = this.saveDataManager.getCurrentLevel();
  326. console.log(`[GameEnd] 当前关卡: ${currentLevel}, 游戏成功: ${this.isGameSuccess}`);
  327. try {
  328. if (this.isGameSuccess) {
  329. // 游戏成功,给予完整奖励
  330. console.log('[GameEnd] 准备给予成功奖励');
  331. await this.saveDataManager.giveCompletionRewards(currentLevel);
  332. console.log('[GameEnd] 已给予成功奖励');
  333. } else {
  334. // 游戏失败,给予按比例奖励
  335. const totalWaves = this.inGameManager?.levelWaves?.length || 1;
  336. const completedWaves = this.inGameManager ? Math.max(0, this.inGameManager.getCurrentWave() - 1) : 0;
  337. console.log(`[GameEnd] 准备给予失败奖励,完成波数: ${completedWaves}/${totalWaves}`);
  338. await this.saveDataManager.giveFailureRewards(currentLevel, completedWaves, totalWaves);
  339. console.log(`[GameEnd] 已给予失败奖励,完成波数: ${completedWaves}/${totalWaves}`);
  340. }
  341. // 获取奖励数据并显示
  342. this.currentRewards = this.saveDataManager.getLastRewards();
  343. console.log('[GameEnd] 获取到的奖励数据:', this.currentRewards);
  344. console.log('[GameEnd] 奖励计算完成');
  345. this.updateRewardDisplay();
  346. // 显示结算面板(通过动画)
  347. this.showEndPanelWithAnimation();
  348. } catch (error) {
  349. console.error('[GameEnd] 计算奖励时出错:', error);
  350. }
  351. }
  352. /**
  353. * 更新奖励显示
  354. */
  355. private updateRewardDisplay() {
  356. console.log(`[GameEnd] 开始更新奖励显示 - 钞票: ${this.currentRewards.money}, 钻石: ${this.currentRewards.diamonds}`);
  357. // 检查moneyLabel绑定状态
  358. if (this.moneyLabel) {
  359. this.moneyLabel.string = this.currentRewards.money.toString();
  360. console.log(`[GameEnd] 钞票标签已更新: ${this.currentRewards.money}`);
  361. } else {
  362. console.error('[GameEnd] moneyLabel未绑定!请在编辑器中将Canvas/GameEnd/ResourceNode/MoneyResourceNode/MoneyLabel拖拽到GameEnd组件的moneyLabel属性');
  363. }
  364. // 检查diamondLabel绑定状态
  365. if (this.diamondLabel) {
  366. this.diamondLabel.string = this.currentRewards.diamonds.toString();
  367. console.log(`[GameEnd] 钻石标签已更新: ${this.currentRewards.diamonds}`);
  368. } else {
  369. console.error('[GameEnd] diamondLabel未绑定!请在编辑器中将Canvas/GameEnd/ResourceNode/DiamondResourceNode/DiamondLabel拖拽到GameEnd组件的diamondLabel属性');
  370. }
  371. console.log(`[GameEnd] 奖励显示更新完成`);
  372. }
  373. /**
  374. * 显示结算面板(通过动画)
  375. */
  376. private showEndPanelWithAnimation() {
  377. console.log('[GameEnd] 开始显示结算面板动画');
  378. // 确保节点处于激活状态
  379. if (this.node && !this.node.active) {
  380. this.node.active = true;
  381. console.log('[GameEnd] 激活GameEnd节点');
  382. }
  383. // 重置双倍奖励状态
  384. this.hasDoubledReward = false;
  385. if (this.doubleButton) {
  386. this.doubleButton.interactable = true;
  387. }
  388. // 确保有UIOpacity组件
  389. if (this.node) {
  390. let uiOpacity = this.node.getComponent(UIOpacity);
  391. if (!uiOpacity) {
  392. uiOpacity = this.node.addComponent(UIOpacity);
  393. console.log('[GameEnd] 在showEndPanelWithAnimation中添加UIOpacity组件');
  394. }
  395. }
  396. // 播放显示动画(如果有的话)
  397. if (this.animationDuration > 0) {
  398. console.log(`[GameEnd] 动画持续时间: ${this.animationDuration}秒,开始播放GameEnd面板弹出动画`);
  399. this.playShowAnimation();
  400. } else {
  401. // 如果没有动画,直接显示
  402. if (this.node) {
  403. this.node.setScale(1, 1, 1);
  404. const uiOpacity = this.node.getComponent(UIOpacity);
  405. if (uiOpacity) {
  406. uiOpacity.opacity = 255;
  407. }
  408. }
  409. console.log('[GameEnd] 无动画配置,直接显示面板');
  410. }
  411. console.log('[GameEnd] GameEnd面板显示流程完成');
  412. }
  413. /**
  414. * 播放显示动画
  415. */
  416. private playShowAnimation() {
  417. if (!this.node) {
  418. console.error('[GameEnd] playShowAnimation: 节点不存在');
  419. return;
  420. }
  421. console.log('[GameEnd] 开始播放GameEnd面板弹出动画');
  422. // 停止所有正在进行的动画
  423. this.stopAllAnimations();
  424. // 设置节点位置到屏幕中心
  425. this.centerNodeOnScreen();
  426. // 确保有UIOpacity组件
  427. let uiOpacity = this.node.getComponent(UIOpacity);
  428. if (!uiOpacity) {
  429. uiOpacity = this.node.addComponent(UIOpacity);
  430. console.log('[GameEnd] 添加UIOpacity组件');
  431. }
  432. // 设置初始状态
  433. this.node.setScale(0, 0, 1);
  434. uiOpacity.opacity = 0;
  435. console.log('[GameEnd] 设置动画初始状态 - 缩放: 0, 透明度: 0');
  436. // 播放缩放和淡入动画
  437. tween(this.node)
  438. .to(this.animationDuration, {
  439. scale: new Vec3(1, 1, 1)
  440. }, {
  441. easing: 'backOut'
  442. })
  443. .call(() => {
  444. console.log('[GameEnd] GameEnd面板缩放动画完成');
  445. })
  446. .start();
  447. tween(uiOpacity)
  448. .to(this.animationDuration, {
  449. opacity: 255
  450. })
  451. .call(() => {
  452. console.log('[GameEnd] GameEnd面板淡入动画完成');
  453. })
  454. .start();
  455. console.log('[GameEnd] GameEnd面板弹出动画开始执行');
  456. }
  457. /**
  458. * 将节点居中到屏幕中央
  459. */
  460. private centerNodeOnScreen() {
  461. if (!this.node) return;
  462. // 获取Canvas节点
  463. const canvas = find('Canvas');
  464. if (!canvas) {
  465. console.warn('[GameEnd] 未找到Canvas节点');
  466. return;
  467. }
  468. // 设置位置为(0, 0),这在Canvas坐标系中是屏幕中心
  469. this.node.setPosition(0, 0, 0);
  470. console.log('[GameEnd] 已将面板居中到屏幕中央');
  471. }
  472. /**
  473. * 双倍按钮点击事件
  474. */
  475. private onDoubleButtonClick() {
  476. // 播放UI点击音效
  477. Audio.playUISound('data/弹球音效/ui play');
  478. if (this.hasDoubledReward) {
  479. console.log('[GameEnd] 已经获得过双倍奖励');
  480. return;
  481. }
  482. console.log('[GameEnd] 点击双倍奖励按钮');
  483. // 这里可以添加观看广告的逻辑
  484. // 暂时直接给予双倍奖励
  485. this.giveDoubleReward();
  486. }
  487. /**
  488. * 给予双倍奖励
  489. */
  490. private giveDoubleReward() {
  491. if (!this.saveDataManager || this.hasDoubledReward) {
  492. return;
  493. }
  494. // 计算双倍奖励
  495. const doubleMoney = this.currentRewards.money;
  496. const doubleDiamonds = this.currentRewards.diamonds;
  497. // 添加额外奖励到玩家账户
  498. if (doubleMoney > 0) {
  499. this.saveDataManager.addMoney(doubleMoney, 'double_reward');
  500. }
  501. if (doubleDiamonds > 0) {
  502. this.saveDataManager.addDiamonds(doubleDiamonds, 'double_reward');
  503. }
  504. // 更新当前奖励显示(显示双倍后的数值)
  505. this.currentRewards.money += doubleMoney;
  506. this.currentRewards.diamonds += doubleDiamonds;
  507. this.updateRewardDisplay();
  508. // 标记已获得双倍奖励
  509. this.hasDoubledReward = true;
  510. // 禁用双倍按钮
  511. if (this.doubleButton) {
  512. this.doubleButton.interactable = false;
  513. }
  514. console.log(`[GameEnd] 双倍奖励已给予 - 额外钞票: ${doubleMoney}, 额外钻石: ${doubleDiamonds}`);
  515. // 触发货币变化事件
  516. EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
  517. // 派发事件给MoneyAni播放奖励动画
  518. const rewards = this.saveDataManager.getLastRewards();
  519. console.log('[GameEnd] 派发奖励动画事件,奖励数据:', rewards);
  520. EventBus.getInstance().emit('PLAY_REWARD_ANIMATION', {
  521. money: rewards.money,
  522. diamonds: rewards.diamonds
  523. });
  524. // 触发返回主菜单事件
  525. EventBus.getInstance().emit('CONTINUE_CLICK');
  526. // 隐藏结算面板(通过动画)
  527. this.hideEndPanelWithAnimation();
  528. }
  529. /**
  530. * 继续按钮点击事件
  531. */
  532. private onContinueButtonClick() {
  533. // 播放UI点击音效
  534. Audio.playUISound('data/弹球音效/ui play');
  535. console.log('[GameEnd] 点击继续按钮');
  536. // 派发事件给MoneyAni播放奖励动画
  537. const rewards = this.saveDataManager.getLastRewards();
  538. console.log('[GameEnd] 派发奖励动画事件,奖励数据:', rewards);
  539. EventBus.getInstance().emit('PLAY_REWARD_ANIMATION', {
  540. money: rewards.money,
  541. diamonds: rewards.diamonds
  542. });
  543. // 触发返回主菜单事件
  544. EventBus.getInstance().emit('CONTINUE_CLICK');
  545. // 隐藏结算面板(通过动画)
  546. this.hideEndPanelWithAnimation();
  547. }
  548. /**
  549. * 隐藏结算面板(通过动画)
  550. */
  551. private async hideEndPanelWithAnimation() {
  552. // 播放隐藏动画
  553. await this.fadeOutWithScale();
  554. console.log('[GameEnd] 结算面板已通过动画隐藏');
  555. }
  556. /**
  557. * 重置UI状态
  558. */
  559. private onResetUI() {
  560. console.log('[GameEnd] 重置UI状态');
  561. // 停止所有动画
  562. this.stopAllAnimations();
  563. // 直接重置到隐藏状态,不使用动画
  564. this.initializeHiddenState();
  565. // 重置所有状态,为下一局游戏做准备
  566. this.hasDoubledReward = false;
  567. // 注意:不重置currentRewards,保持奖励显示直到下次游戏开始
  568. // this.currentRewards = {money: -1, diamonds: -1}; // 移除这行,避免重置奖励显示
  569. this.isGameSuccess = false;
  570. this.hasProcessedGameEnd = false; // 重置处理状态标志
  571. // 重置按钮状态
  572. if (this.doubleButton) {
  573. this.doubleButton.interactable = true;
  574. }
  575. console.log('[GameEnd] UI状态重置完成');
  576. }
  577. /**
  578. * 设置EndLabel文本
  579. * @param text 要显示的文本内容
  580. */
  581. private setEndLabelText(text: string) {
  582. if (this.endLabel) {
  583. this.endLabel.string = text;
  584. console.log(`[GameEnd] 已设置EndLabel文本为: ${text}`);
  585. } else {
  586. // 如果endLabel未绑定,尝试通过路径查找
  587. const endLabelNode = find('Canvas/GameEnd/Sprite/EndLabel');
  588. if (endLabelNode) {
  589. const labelComponent = endLabelNode.getComponent(Label);
  590. if (labelComponent) {
  591. labelComponent.string = text;
  592. console.log(`[GameEnd] 通过路径查找设置EndLabel文本为: ${text}`);
  593. } else {
  594. console.warn('[GameEnd] 找到EndLabel节点但无Label组件');
  595. }
  596. } else {
  597. console.warn('[GameEnd] 未找到EndLabel节点,请检查路径: Canvas/GameEnd/Sprite/EndLabel');
  598. }
  599. }
  600. }
  601. /**
  602. * 获取当前奖励信息(用于外部查询)
  603. */
  604. public getCurrentRewards(): {money: number, diamonds: number} {
  605. return {...this.currentRewards};
  606. }
  607. /**
  608. * 检查是否已获得双倍奖励
  609. */
  610. public hasGotDoubleReward(): boolean {
  611. return this.hasDoubledReward;
  612. }
  613. // === 动画方法 ===
  614. /**
  615. * 初始化为隐藏状态
  616. */
  617. private initializeHiddenState() {
  618. // 设置初始透明度为0
  619. if (this.node) {
  620. let uiOpacity = this.node.getComponent(UIOpacity);
  621. if (!uiOpacity) {
  622. uiOpacity = this.node.addComponent(UIOpacity);
  623. console.log('[GameEnd] 添加UIOpacity组件到节点');
  624. }
  625. uiOpacity.opacity = 0;
  626. }
  627. // 设置初始缩放为0(完全隐藏,恢复到历史版本实现)
  628. if (this.node) {
  629. this.node.setScale(0, 0, 1);
  630. }
  631. console.log('[GameEnd] 初始化为隐藏状态 - 缩放: 0, 透明度: 0');
  632. }
  633. /**
  634. * 初始化为隐藏状态但保持节点激活(用于游戏开始时)
  635. */
  636. private initializeHiddenStateKeepActive() {
  637. // 设置初始透明度为0
  638. if (this.node) {
  639. let uiOpacity = this.node.getComponent(UIOpacity);
  640. if (!uiOpacity) {
  641. uiOpacity = this.node.addComponent(UIOpacity);
  642. console.log('[GameEnd] 添加UIOpacity组件到节点');
  643. }
  644. uiOpacity.opacity = 0;
  645. }
  646. // 设置初始缩放为0(完全隐藏,恢复到历史版本实现)
  647. if (this.node) {
  648. this.node.setScale(0, 0, 1);
  649. }
  650. console.log('[GameEnd] 初始化为隐藏状态(保持激活) - 缩放: 0, 透明度: 0');
  651. }
  652. /**
  653. * 淡入动画
  654. */
  655. public fadeIn(target?: Node, duration?: number): Promise<void> {
  656. const animTarget = target || this.node;
  657. const animDuration = duration !== undefined ? duration : this.animationDuration;
  658. if (!animTarget) {
  659. console.warn('[GameEnd] fadeIn: 未指定目标节点');
  660. return Promise.resolve();
  661. }
  662. return new Promise<void>((resolve) => {
  663. let uiOpacity = animTarget.getComponent(UIOpacity);
  664. if (!uiOpacity) {
  665. uiOpacity = animTarget.addComponent(UIOpacity);
  666. }
  667. Tween.stopAllByTarget(uiOpacity);
  668. uiOpacity.opacity = 0;
  669. tween(uiOpacity)
  670. .to(animDuration, { opacity: 255 }, { easing: 'quadOut' })
  671. .call(() => {
  672. resolve();
  673. })
  674. .start();
  675. });
  676. }
  677. /**
  678. * 淡出动画
  679. */
  680. public fadeOut(target?: Node, duration?: number): Promise<void> {
  681. const animTarget = target || this.node;
  682. const animDuration = duration !== undefined ? duration : this.animationDuration;
  683. if (!animTarget) {
  684. console.warn('[GameEnd] fadeOut: 未指定目标节点');
  685. return Promise.resolve();
  686. }
  687. return new Promise<void>((resolve) => {
  688. let uiOpacity = animTarget.getComponent(UIOpacity);
  689. if (!uiOpacity) {
  690. uiOpacity = animTarget.addComponent(UIOpacity);
  691. }
  692. Tween.stopAllByTarget(uiOpacity);
  693. tween(uiOpacity)
  694. .to(animDuration, { opacity: 0 }, { easing: 'quadIn' })
  695. .call(() => {
  696. resolve();
  697. })
  698. .start();
  699. });
  700. }
  701. /**
  702. * 缩放弹出动画
  703. */
  704. public scalePopIn(target?: Node, duration?: number): Promise<void> {
  705. const animTarget = target || this.node;
  706. const animDuration = duration !== undefined ? duration : this.animationDuration;
  707. if (!animTarget) {
  708. console.warn('[GameEnd] scalePopIn: 未指定目标节点');
  709. return Promise.resolve();
  710. }
  711. return new Promise<void>((resolve) => {
  712. Tween.stopAllByTarget(animTarget);
  713. animTarget.setScale(0, 0, 1);
  714. tween(animTarget)
  715. .to(animDuration, { scale: this._originalScale }, { easing: 'backOut' })
  716. .call(() => {
  717. resolve();
  718. })
  719. .start();
  720. });
  721. }
  722. /**
  723. * 缩放收缩动画
  724. */
  725. public scalePopOut(target?: Node, duration?: number): Promise<void> {
  726. const animTarget = target || this.node;
  727. const animDuration = duration !== undefined ? duration : this.animationDuration;
  728. if (!animTarget) {
  729. console.warn('[GameEnd] scalePopOut: 未指定目标节点');
  730. return Promise.resolve();
  731. }
  732. return new Promise<void>((resolve) => {
  733. Tween.stopAllByTarget(animTarget);
  734. tween(animTarget)
  735. .to(animDuration, { scale: new Vec3(0, 0, 1) }, { easing: 'backIn' })
  736. .call(() => {
  737. resolve();
  738. })
  739. .start();
  740. });
  741. }
  742. /**
  743. * 组合动画:淡入 + 缩放弹出
  744. */
  745. public async fadeInWithScale(fadeTarget?: Node, scaleTarget?: Node, duration?: number): Promise<void> {
  746. const promises: Promise<void>[] = [];
  747. if (fadeTarget || this.node) {
  748. promises.push(this.fadeIn(fadeTarget, duration));
  749. }
  750. if (scaleTarget || this.node) {
  751. promises.push(this.scalePopIn(scaleTarget, duration));
  752. }
  753. await Promise.all(promises);
  754. }
  755. /**
  756. * 组合动画:淡出 + 缩放收缩
  757. */
  758. public async fadeOutWithScale(fadeTarget?: Node, scaleTarget?: Node, duration?: number): Promise<void> {
  759. const promises: Promise<void>[] = [];
  760. if (fadeTarget || this.node) {
  761. promises.push(this.fadeOut(fadeTarget, duration));
  762. }
  763. if (scaleTarget || this.node) {
  764. promises.push(this.scalePopOut(scaleTarget, duration));
  765. }
  766. await Promise.all(promises);
  767. }
  768. /**
  769. * 停止所有动画
  770. */
  771. public stopAllAnimations() {
  772. if (this.node) {
  773. // 停止所有Tween动画
  774. Tween.stopAllByTarget(this.node);
  775. // 停止UIOpacity组件的动画
  776. const uiOpacity = this.node.getComponent(UIOpacity);
  777. if (uiOpacity) {
  778. Tween.stopAllByTarget(uiOpacity);
  779. }
  780. console.log('[GameEnd] 已停止所有动画');
  781. } else {
  782. console.warn('[GameEnd] stopAllAnimations: 节点不存在');
  783. }
  784. }
  785. /**
  786. * 清理所有敌人
  787. * 在游戏结束时调用,符合阶段一UI弹出阶段的职责
  788. */
  789. /**
  790. * 通过事件系统清理所有敌人
  791. * 使用CLEAR_ALL_ENEMIES事件,避免直接引用EnemyController
  792. */
  793. private clearAllEnemies() {
  794. console.log('[GameEnd] 游戏结束,派发CLEAR_ALL_ENEMIES事件清理所有敌人');
  795. try {
  796. // 通过事件总线派发清理敌人事件
  797. EventBus.getInstance().emit(GameEvents.CLEAR_ALL_ENEMIES);
  798. console.log('[GameEnd] 已派发CLEAR_ALL_ENEMIES事件,统一清理敌人');
  799. } catch (error) {
  800. console.error('[GameEnd] 派发清理敌人事件时发生错误:', error);
  801. }
  802. }
  803. /**
  804. * 重置所有目标节点到初始状态
  805. */
  806. public resetToInitialState() {
  807. this.stopAllAnimations();
  808. if (this.node) {
  809. let uiOpacity = this.node.getComponent(UIOpacity);
  810. if (uiOpacity) {
  811. uiOpacity.opacity = 255;
  812. }
  813. this.node.setScale(this._originalScale);
  814. }
  815. }
  816. onDisable() {
  817. console.log('[GameEnd] onDisable方法被调用,节点已禁用');
  818. // 停止所有动画
  819. this.stopAllAnimations();
  820. // 清理事件监听
  821. const eventBus = EventBus.getInstance();
  822. eventBus.off(GameEvents.GAME_SUCCESS, this.onGameSuccess, this);
  823. eventBus.off(GameEvents.GAME_DEFEAT, this.onGameDefeat, this);
  824. eventBus.off(GameEvents.GAME_START, this.onGameStart, this);
  825. eventBus.off(GameEvents.RESET_UI_STATES, this.onResetUI, this);
  826. console.log('[GameEnd] 事件监听器已清理');
  827. }
  828. protected onDestroy() {
  829. // 停止所有动画
  830. this.stopAllAnimations();
  831. // 清理事件监听
  832. const eventBus = EventBus.getInstance();
  833. eventBus.off(GameEvents.GAME_SUCCESS, this.onGameSuccess, this);
  834. eventBus.off(GameEvents.GAME_DEFEAT, this.onGameDefeat, this);
  835. eventBus.off(GameEvents.GAME_START, this.onGameStart, this);
  836. eventBus.off(GameEvents.RESET_UI_STATES, this.onResetUI, this);
  837. // 清理按钮事件
  838. if (this.doubleButton) {
  839. this.doubleButton.node.off(Button.EventType.CLICK, this.onDoubleButtonClick, this);
  840. }
  841. if (this.continueButton) {
  842. this.continueButton.node.off(Button.EventType.CLICK, this.onContinueButtonClick, this);
  843. }
  844. }
  845. }