MenuController.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. import { _decorator, Component, Node, Button, find } from 'cc';
  2. import { PopUPAni } from '../../Animations/PopUPAni';
  3. import EventBus, { GameEvents } from '../../Core/EventBus';
  4. import { GameManager, AppState } from '../../LevelSystem/GameManager';
  5. import { GameStartMove } from '../../Animations/GameStartMove';
  6. import { SoundController } from './SoundController';
  7. import { Audio } from '../../AudioManager/AudioManager';
  8. const { ccclass, property } = _decorator;
  9. /**
  10. * 菜单系统控制器
  11. * 负责管理菜单UI的显示和隐藏
  12. */
  13. @ccclass('MenuController')
  14. export class MenuController extends Component {
  15. // UI节点引用
  16. @property(Node) menuUI: Node = null; // Canvas/MenuUI
  17. @property(Button) menuButton: Button = null; // Canvas/MenuButton
  18. @property(Button) closeButton: Button = null; // Canvas/MenuUI中的关闭按钮
  19. @property(Button) backButton: Button = null; // Canvas/MenuUI/Buttons/BackButton 退出游戏按钮
  20. @property(Button) continueButton: Button = null; // Canvas/MenuUI/Buttons/ContinueButton 继续游戏按钮
  21. // 动画控制器
  22. @property(PopUPAni) popupAni: PopUPAni = null; // Canvas/MenuUI上的PopUPAni组件
  23. // 音频控制器
  24. @property(SoundController) soundController: SoundController = null; // Canvas/MenuUI上的SoundController组件
  25. // 游戏管理器引用
  26. @property(GameManager) gameManager: GameManager = null; // GameManager组件引用
  27. // 菜单状态
  28. private isMenuOpen: boolean = false;
  29. // GameStartMove组件引用,用于重置镜头位置
  30. private gameStartMoveComponent: GameStartMove = null;
  31. onLoad() {
  32. this.bindEvents();
  33. // 初始化时隐藏菜单 - 使用PopUPAni的场景外位置隐藏方法
  34. if (this.popupAni) {
  35. this.popupAni.hidePanelImmediate();
  36. }
  37. }
  38. /**
  39. * 绑定事件
  40. */
  41. private bindEvents() {
  42. // 绑定菜单按钮点击事件
  43. if (this.menuButton) {
  44. this.menuButton.node.on(Button.EventType.CLICK, this.onMenuButtonClick, this);
  45. }
  46. // 绑定关闭按钮点击事件
  47. if (this.closeButton) {
  48. this.closeButton.node.on(Button.EventType.CLICK, this.onCloseButtonClick, this);
  49. }
  50. // 绑定退出游戏按钮点击事件
  51. if (this.backButton) {
  52. this.backButton.node.on(Button.EventType.CLICK, this.onBackButtonClick, this);
  53. }
  54. // 绑定继续游戏按钮点击事件
  55. if (this.continueButton) {
  56. this.continueButton.node.on(Button.EventType.CLICK, this.onContinueButtonClick, this);
  57. }
  58. }
  59. /**
  60. * 菜单按钮点击事件
  61. */
  62. private async onMenuButtonClick() {
  63. // 播放UI点击音效
  64. Audio.playUISound('data/弹球音效/ui play');
  65. if (this.isMenuOpen) {
  66. await this.closeMenu();
  67. } else {
  68. await this.openMenu();
  69. }
  70. }
  71. /**
  72. * 关闭按钮点击事件
  73. */
  74. private async onCloseButtonClick() {
  75. // 播放UI点击音效
  76. Audio.playUISound('data/弹球音效/ui play');
  77. await this.closeMenu();
  78. }
  79. /**
  80. * 继续游戏按钮点击事件
  81. */
  82. private async onContinueButtonClick() {
  83. // 播放UI点击音效
  84. Audio.playUISound('data/弹球音效/ui play');
  85. console.log('[MenuController] 继续游戏按钮被点击');
  86. // 检查GameManager组件引用
  87. if (!this.gameManager) {
  88. console.error('[MenuController] GameManager组件未通过装饰器挂载,请在Inspector中拖拽GameManager组件');
  89. await this.closeMenu();
  90. return;
  91. }
  92. const currentAppState = this.gameManager.getCurrentAppState();
  93. console.log(`[MenuController] 当前应用状态: ${currentAppState}`);
  94. if (currentAppState === AppState.IN_GAME) {
  95. // 在游戏中点击继续,关闭菜单并恢复游戏
  96. console.log('[MenuController] 游戏中点击继续,关闭菜单并恢复游戏');
  97. await this.closeMenu();
  98. } else {
  99. // 游戏外点击继续,直接关闭菜单
  100. console.log('[MenuController] 游戏外点击继续,直接关闭菜单');
  101. await this.closeMenu();
  102. }
  103. }
  104. /**
  105. * 退出游戏按钮点击事件
  106. */
  107. private async onBackButtonClick() {
  108. // 播放UI点击音效
  109. Audio.playUISound('data/弹球音效/ui play');
  110. console.log('[MenuController] 退出游戏按钮被点击');
  111. // 检查GameManager组件引用
  112. if (!this.gameManager) {
  113. console.error('[MenuController] GameManager组件未通过装饰器挂载,请在Inspector中拖拽GameManager组件');
  114. return;
  115. }
  116. const currentAppState = this.gameManager.getCurrentAppState();
  117. console.log(`[MenuController] 当前应用状态: ${currentAppState}`);
  118. if (currentAppState === AppState.IN_GAME) {
  119. // 在游戏中退出,先重置镜头位置,然后视为游戏失败
  120. console.log('[MenuController] 游戏中退出,先重置镜头位置');
  121. this.resetCameraPosition();
  122. console.log('[MenuController] 触发游戏失败事件');
  123. const eventBus = EventBus.getInstance();
  124. eventBus.emit(GameEvents.GAME_DEFEAT);
  125. // 等待游戏失败处理完成后关闭菜单
  126. setTimeout(async () => {
  127. await this.closeMenu();
  128. console.log('[MenuController] 游戏失败处理完成,菜单已关闭');
  129. }, 100);
  130. } else {
  131. // 游戏外退出,关闭菜单并返回主界面(不需要重置镜头)
  132. console.log('[MenuController] 游戏外退出,关闭菜单并返回主界面');
  133. await this.closeMenu();
  134. // 触发返回主界面事件
  135. const eventBus = EventBus.getInstance();
  136. eventBus.emit(GameEvents.RETURN_TO_MAIN_MENU);
  137. console.log('[MenuController] 已触发返回主界面事件');
  138. }
  139. }
  140. /**
  141. * 重置镜头位置到原始位置
  142. * 确保从游戏中退出时镜头位置正常
  143. */
  144. private resetCameraPosition() {
  145. // 如果还没有获取GameStartMove组件,尝试获取
  146. if (!this.gameStartMoveComponent) {
  147. const cameraNode = find('Canvas/Main Camera');
  148. if (cameraNode) {
  149. this.gameStartMoveComponent = cameraNode.getComponent(GameStartMove);
  150. }
  151. }
  152. // 如果成功获取到组件,重置镜头位置
  153. if (this.gameStartMoveComponent) {
  154. console.log('[MenuController] 重置镜头位置到原始位置');
  155. this.gameStartMoveComponent.resetCameraToOriginalPosition(0.3);
  156. } else {
  157. console.warn('[MenuController] 未找到GameStartMove组件,无法重置镜头位置');
  158. }
  159. }
  160. /**
  161. * 打开菜单
  162. */
  163. public async openMenu(): Promise<void> {
  164. if (this.isMenuOpen) return;
  165. this.isMenuOpen = true;
  166. // 检查是否在游戏中,如果是则通过事件系统触发游戏暂停
  167. if (this.gameManager && this.gameManager.getCurrentAppState() === AppState.IN_GAME) {
  168. console.log('[MenuController] 游戏中打开菜单,通过事件系统触发游戏暂停');
  169. const eventBus = EventBus.getInstance();
  170. eventBus.emit(GameEvents.GAME_PAUSE);
  171. }
  172. // 使用动画显示菜单面板
  173. if (this.popupAni) {
  174. await this.popupAni.showPanel();
  175. }
  176. // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
  177. console.log('菜单已打开');
  178. }
  179. /**
  180. * 关闭菜单
  181. */
  182. public async closeMenu(): Promise<void> {
  183. if (!this.isMenuOpen) return;
  184. this.isMenuOpen = false;
  185. // 使用动画隐藏菜单面板
  186. if (this.popupAni) {
  187. await this.popupAni.hidePanel();
  188. }
  189. // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
  190. // 检查是否在游戏中,如果是则通过事件系统触发游戏恢复
  191. if (this.gameManager && this.gameManager.getCurrentAppState() === AppState.IN_GAME) {
  192. console.log('[MenuController] 游戏中关闭菜单,通过事件系统触发游戏恢复');
  193. const eventBus = EventBus.getInstance();
  194. eventBus.emit(GameEvents.GAME_RESUME);
  195. }
  196. console.log('菜单已关闭');
  197. }
  198. /**
  199. * 切换菜单状态
  200. */
  201. public async toggleMenu(): Promise<void> {
  202. if (this.isMenuOpen) {
  203. await this.closeMenu();
  204. } else {
  205. await this.openMenu();
  206. }
  207. }
  208. /**
  209. * 获取菜单状态
  210. */
  211. public getMenuState(): boolean {
  212. return this.isMenuOpen;
  213. }
  214. /**
  215. * 立即关闭菜单(无动画)
  216. */
  217. public closeMenuImmediate(): void {
  218. this.isMenuOpen = false;
  219. if (this.popupAni) {
  220. this.popupAni.hidePanelImmediate();
  221. }
  222. // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
  223. console.log('菜单已立即关闭');
  224. }
  225. /**
  226. * 立即打开菜单(无动画)
  227. */
  228. public openMenuImmediate(): void {
  229. this.isMenuOpen = true;
  230. if (this.popupAni) {
  231. this.popupAni.showPanelImmediate();
  232. }
  233. // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
  234. console.log('菜单已立即打开');
  235. }
  236. onDestroy() {
  237. // 解绑事件
  238. if (this.menuButton) {
  239. this.menuButton.node.off(Button.EventType.CLICK, this.onMenuButtonClick, this);
  240. }
  241. if (this.closeButton) {
  242. this.closeButton.node.off(Button.EventType.CLICK, this.onCloseButtonClick, this);
  243. }
  244. if (this.backButton) {
  245. this.backButton.node.off(Button.EventType.CLICK, this.onBackButtonClick, this);
  246. }
  247. if (this.continueButton) {
  248. this.continueButton.node.off(Button.EventType.CLICK, this.onContinueButtonClick, this);
  249. }
  250. }
  251. }