GameEnd.ts 28 KB

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