GameEnd.ts 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  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. }
  518. /**
  519. * 继续按钮点击事件
  520. */
  521. private onContinueButtonClick() {
  522. // 播放UI点击音效
  523. Audio.playUISound('data/弹球音效/ui play');
  524. console.log('[GameEnd] 点击继续按钮');
  525. // 派发事件给MoneyAni播放奖励动画
  526. const rewards = this.saveDataManager.getLastRewards();
  527. console.log('[GameEnd] 派发奖励动画事件,奖励数据:', rewards);
  528. EventBus.getInstance().emit('PLAY_REWARD_ANIMATION', {
  529. money: rewards.money,
  530. diamonds: rewards.diamonds
  531. });
  532. // 触发返回主菜单事件
  533. EventBus.getInstance().emit('CONTINUE_CLICK');
  534. // 隐藏结算面板(通过动画)
  535. this.hideEndPanelWithAnimation();
  536. }
  537. /**
  538. * 隐藏结算面板(通过动画)
  539. */
  540. private async hideEndPanelWithAnimation() {
  541. // 播放隐藏动画
  542. await this.fadeOutWithScale();
  543. console.log('[GameEnd] 结算面板已通过动画隐藏');
  544. }
  545. /**
  546. * 重置UI状态
  547. */
  548. private onResetUI() {
  549. console.log('[GameEnd] 重置UI状态');
  550. // 停止所有动画
  551. this.stopAllAnimations();
  552. // 直接重置到隐藏状态,不使用动画
  553. this.initializeHiddenState();
  554. // 重置所有状态,为下一局游戏做准备
  555. this.hasDoubledReward = false;
  556. // 注意:不重置currentRewards,保持奖励显示直到下次游戏开始
  557. // this.currentRewards = {money: -1, diamonds: -1}; // 移除这行,避免重置奖励显示
  558. this.isGameSuccess = false;
  559. this.hasProcessedGameEnd = false; // 重置处理状态标志
  560. // 重置按钮状态
  561. if (this.doubleButton) {
  562. this.doubleButton.interactable = true;
  563. }
  564. console.log('[GameEnd] UI状态重置完成');
  565. }
  566. /**
  567. * 设置EndLabel文本
  568. * @param text 要显示的文本内容
  569. */
  570. private setEndLabelText(text: string) {
  571. if (this.endLabel) {
  572. this.endLabel.string = text;
  573. console.log(`[GameEnd] 已设置EndLabel文本为: ${text}`);
  574. } else {
  575. // 如果endLabel未绑定,尝试通过路径查找
  576. const endLabelNode = find('Canvas/GameEnd/Sprite/EndLabel');
  577. if (endLabelNode) {
  578. const labelComponent = endLabelNode.getComponent(Label);
  579. if (labelComponent) {
  580. labelComponent.string = text;
  581. console.log(`[GameEnd] 通过路径查找设置EndLabel文本为: ${text}`);
  582. } else {
  583. console.warn('[GameEnd] 找到EndLabel节点但无Label组件');
  584. }
  585. } else {
  586. console.warn('[GameEnd] 未找到EndLabel节点,请检查路径: Canvas/GameEnd/Sprite/EndLabel');
  587. }
  588. }
  589. }
  590. /**
  591. * 获取当前奖励信息(用于外部查询)
  592. */
  593. public getCurrentRewards(): {money: number, diamonds: number} {
  594. return {...this.currentRewards};
  595. }
  596. /**
  597. * 检查是否已获得双倍奖励
  598. */
  599. public hasGotDoubleReward(): boolean {
  600. return this.hasDoubledReward;
  601. }
  602. // === 动画方法 ===
  603. /**
  604. * 初始化为隐藏状态
  605. */
  606. private initializeHiddenState() {
  607. // 设置初始透明度为0
  608. if (this.node) {
  609. let uiOpacity = this.node.getComponent(UIOpacity);
  610. if (!uiOpacity) {
  611. uiOpacity = this.node.addComponent(UIOpacity);
  612. console.log('[GameEnd] 添加UIOpacity组件到节点');
  613. }
  614. uiOpacity.opacity = 0;
  615. }
  616. // 设置初始缩放为0(完全隐藏,恢复到历史版本实现)
  617. if (this.node) {
  618. this.node.setScale(0, 0, 1);
  619. }
  620. console.log('[GameEnd] 初始化为隐藏状态 - 缩放: 0, 透明度: 0');
  621. }
  622. /**
  623. * 初始化为隐藏状态但保持节点激活(用于游戏开始时)
  624. */
  625. private initializeHiddenStateKeepActive() {
  626. // 设置初始透明度为0
  627. if (this.node) {
  628. let uiOpacity = this.node.getComponent(UIOpacity);
  629. if (!uiOpacity) {
  630. uiOpacity = this.node.addComponent(UIOpacity);
  631. console.log('[GameEnd] 添加UIOpacity组件到节点');
  632. }
  633. uiOpacity.opacity = 0;
  634. }
  635. // 设置初始缩放为0(完全隐藏,恢复到历史版本实现)
  636. if (this.node) {
  637. this.node.setScale(0, 0, 1);
  638. }
  639. console.log('[GameEnd] 初始化为隐藏状态(保持激活) - 缩放: 0, 透明度: 0');
  640. }
  641. /**
  642. * 淡入动画
  643. */
  644. public fadeIn(target?: Node, duration?: number): Promise<void> {
  645. const animTarget = target || this.node;
  646. const animDuration = duration !== undefined ? duration : this.animationDuration;
  647. if (!animTarget) {
  648. console.warn('[GameEnd] fadeIn: 未指定目标节点');
  649. return Promise.resolve();
  650. }
  651. return new Promise<void>((resolve) => {
  652. let uiOpacity = animTarget.getComponent(UIOpacity);
  653. if (!uiOpacity) {
  654. uiOpacity = animTarget.addComponent(UIOpacity);
  655. }
  656. Tween.stopAllByTarget(uiOpacity);
  657. uiOpacity.opacity = 0;
  658. tween(uiOpacity)
  659. .to(animDuration, { opacity: 255 }, { easing: 'quadOut' })
  660. .call(() => {
  661. resolve();
  662. })
  663. .start();
  664. });
  665. }
  666. /**
  667. * 淡出动画
  668. */
  669. public fadeOut(target?: Node, duration?: number): Promise<void> {
  670. const animTarget = target || this.node;
  671. const animDuration = duration !== undefined ? duration : this.animationDuration;
  672. if (!animTarget) {
  673. console.warn('[GameEnd] fadeOut: 未指定目标节点');
  674. return Promise.resolve();
  675. }
  676. return new Promise<void>((resolve) => {
  677. let uiOpacity = animTarget.getComponent(UIOpacity);
  678. if (!uiOpacity) {
  679. uiOpacity = animTarget.addComponent(UIOpacity);
  680. }
  681. Tween.stopAllByTarget(uiOpacity);
  682. tween(uiOpacity)
  683. .to(animDuration, { opacity: 0 }, { easing: 'quadIn' })
  684. .call(() => {
  685. resolve();
  686. })
  687. .start();
  688. });
  689. }
  690. /**
  691. * 缩放弹出动画
  692. */
  693. public scalePopIn(target?: Node, duration?: number): Promise<void> {
  694. const animTarget = target || this.node;
  695. const animDuration = duration !== undefined ? duration : this.animationDuration;
  696. if (!animTarget) {
  697. console.warn('[GameEnd] scalePopIn: 未指定目标节点');
  698. return Promise.resolve();
  699. }
  700. return new Promise<void>((resolve) => {
  701. Tween.stopAllByTarget(animTarget);
  702. animTarget.setScale(0, 0, 1);
  703. tween(animTarget)
  704. .to(animDuration, { scale: this._originalScale }, { easing: 'backOut' })
  705. .call(() => {
  706. resolve();
  707. })
  708. .start();
  709. });
  710. }
  711. /**
  712. * 缩放收缩动画
  713. */
  714. public scalePopOut(target?: Node, duration?: number): Promise<void> {
  715. const animTarget = target || this.node;
  716. const animDuration = duration !== undefined ? duration : this.animationDuration;
  717. if (!animTarget) {
  718. console.warn('[GameEnd] scalePopOut: 未指定目标节点');
  719. return Promise.resolve();
  720. }
  721. return new Promise<void>((resolve) => {
  722. Tween.stopAllByTarget(animTarget);
  723. tween(animTarget)
  724. .to(animDuration, { scale: new Vec3(0, 0, 1) }, { easing: 'backIn' })
  725. .call(() => {
  726. resolve();
  727. })
  728. .start();
  729. });
  730. }
  731. /**
  732. * 组合动画:淡入 + 缩放弹出
  733. */
  734. public async fadeInWithScale(fadeTarget?: Node, scaleTarget?: Node, duration?: number): Promise<void> {
  735. const promises: Promise<void>[] = [];
  736. if (fadeTarget || this.node) {
  737. promises.push(this.fadeIn(fadeTarget, duration));
  738. }
  739. if (scaleTarget || this.node) {
  740. promises.push(this.scalePopIn(scaleTarget, duration));
  741. }
  742. await Promise.all(promises);
  743. }
  744. /**
  745. * 组合动画:淡出 + 缩放收缩
  746. */
  747. public async fadeOutWithScale(fadeTarget?: Node, scaleTarget?: Node, duration?: number): Promise<void> {
  748. const promises: Promise<void>[] = [];
  749. if (fadeTarget || this.node) {
  750. promises.push(this.fadeOut(fadeTarget, duration));
  751. }
  752. if (scaleTarget || this.node) {
  753. promises.push(this.scalePopOut(scaleTarget, duration));
  754. }
  755. await Promise.all(promises);
  756. }
  757. /**
  758. * 停止所有动画
  759. */
  760. public stopAllAnimations() {
  761. if (this.node) {
  762. // 停止所有Tween动画
  763. Tween.stopAllByTarget(this.node);
  764. // 停止UIOpacity组件的动画
  765. const uiOpacity = this.node.getComponent(UIOpacity);
  766. if (uiOpacity) {
  767. Tween.stopAllByTarget(uiOpacity);
  768. }
  769. console.log('[GameEnd] 已停止所有动画');
  770. } else {
  771. console.warn('[GameEnd] stopAllAnimations: 节点不存在');
  772. }
  773. }
  774. /**
  775. * 清理所有敌人
  776. * 在游戏结束时调用,符合阶段一UI弹出阶段的职责
  777. */
  778. /**
  779. * 通过事件系统清理所有敌人
  780. * 使用CLEAR_ALL_ENEMIES事件,避免直接引用EnemyController
  781. */
  782. private clearAllEnemies() {
  783. console.log('[GameEnd] 游戏结束,派发CLEAR_ALL_ENEMIES事件清理所有敌人');
  784. try {
  785. // 通过事件总线派发清理敌人事件
  786. EventBus.getInstance().emit(GameEvents.CLEAR_ALL_ENEMIES);
  787. console.log('[GameEnd] 已派发CLEAR_ALL_ENEMIES事件,统一清理敌人');
  788. } catch (error) {
  789. console.error('[GameEnd] 派发清理敌人事件时发生错误:', error);
  790. }
  791. }
  792. /**
  793. * 重置所有目标节点到初始状态
  794. */
  795. public resetToInitialState() {
  796. this.stopAllAnimations();
  797. if (this.node) {
  798. let uiOpacity = this.node.getComponent(UIOpacity);
  799. if (uiOpacity) {
  800. uiOpacity.opacity = 255;
  801. }
  802. this.node.setScale(this._originalScale);
  803. }
  804. }
  805. onDisable() {
  806. console.log('[GameEnd] onDisable方法被调用,节点已禁用');
  807. // 停止所有动画
  808. this.stopAllAnimations();
  809. // 清理事件监听
  810. const eventBus = EventBus.getInstance();
  811. eventBus.off(GameEvents.GAME_SUCCESS, this.onGameSuccess, this);
  812. eventBus.off(GameEvents.GAME_DEFEAT, this.onGameDefeat, this);
  813. eventBus.off(GameEvents.GAME_START, this.onGameStart, this);
  814. eventBus.off(GameEvents.RESET_UI_STATES, this.onResetUI, this);
  815. console.log('[GameEnd] 事件监听器已清理');
  816. }
  817. protected onDestroy() {
  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. // 清理按钮事件
  827. if (this.doubleButton) {
  828. this.doubleButton.node.off(Button.EventType.CLICK, this.onDoubleButtonClick, this);
  829. }
  830. if (this.continueButton) {
  831. this.continueButton.node.off(Button.EventType.CLICK, this.onContinueButtonClick, this);
  832. }
  833. }
  834. }