GameEnd.ts 36 KB

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