MenuController.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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. // 需要检测的UI节点引用
  22. @property(Node) skillUpUI: Node = null; // Canvas/SkillUpUI
  23. @property(Node) upgradeUI: Node = null; // Canvas/UpgradeUI
  24. @property(Node) shopUI: Node = null; // Canvas/ShopUI
  25. // 动画控制器
  26. @property(PopUPAni) popupAni: PopUPAni = null; // Canvas/MenuUI上的PopUPAni组件
  27. // 音频控制器
  28. @property(SoundController) soundController: SoundController = null; // Canvas/MenuUI上的SoundController组件
  29. // 游戏管理器引用
  30. @property(GameManager) gameManager: GameManager = null; // GameManager组件引用
  31. // 菜单状态
  32. private isMenuOpen: boolean = false;
  33. // GameStartMove组件引用,用于重置镜头位置
  34. private gameStartMoveComponent: GameStartMove = null;
  35. onLoad() {
  36. this.bindEvents();
  37. // 初始化时隐藏菜单 - 使用PopUPAni的场景外位置隐藏方法
  38. if (this.popupAni) {
  39. this.popupAni.hidePanelImmediate();
  40. }
  41. // 设置界面状态监听
  42. this.setupUIStateListeners();
  43. // 初始化菜单按钮显示状态
  44. this.updateMenuButtonVisibility();
  45. }
  46. start() {
  47. // 初始化菜单按钮状态
  48. this.updateMenuButtonVisibility();
  49. }
  50. /**
  51. * 绑定事件
  52. */
  53. private bindEvents() {
  54. // 绑定菜单按钮点击事件
  55. if (this.menuButton) {
  56. this.menuButton.node.on(Button.EventType.CLICK, this.onMenuButtonClick, this);
  57. }
  58. // 绑定关闭按钮点击事件
  59. if (this.closeButton) {
  60. this.closeButton.node.on(Button.EventType.CLICK, this.onCloseButtonClick, this);
  61. }
  62. // 绑定退出游戏按钮点击事件
  63. if (this.backButton) {
  64. this.backButton.node.on(Button.EventType.CLICK, this.onBackButtonClick, this);
  65. }
  66. // 绑定继续游戏按钮点击事件
  67. if (this.continueButton) {
  68. this.continueButton.node.on(Button.EventType.CLICK, this.onContinueButtonClick, this);
  69. }
  70. }
  71. /**
  72. * 设置UI状态监听器
  73. */
  74. private setupUIStateListeners() {
  75. const eventBus = EventBus.getInstance();
  76. // 监听界面切换事件(如果有的话)
  77. // 这里可以根据实际的界面切换事件来更新菜单按钮显示状态
  78. eventBus.on(GameEvents.RETURN_TO_MAIN_MENU, this.updateMenuButtonVisibility, this);
  79. eventBus.on(GameEvents.UI_PANEL_SWITCHED, this.updateMenuButtonVisibility, this);
  80. }
  81. /**
  82. * 检测当前是否在需要隐藏菜单按钮的界面
  83. */
  84. private isInHiddenMenuUI(): boolean {
  85. // 检查SkillUpUI、UpgradeUI、ShopUI是否激活
  86. const isSkillUpActive = this.skillUpUI && this.skillUpUI.active;
  87. const isUpgradeActive = this.upgradeUI && this.upgradeUI.active;
  88. const isShopActive = this.shopUI && this.shopUI.active;
  89. return isSkillUpActive || isUpgradeActive || isShopActive;
  90. }
  91. /**
  92. * 更新菜单按钮的显示状态
  93. */
  94. private updateMenuButtonVisibility() {
  95. if (!this.menuButton) return;
  96. // 检查当前应用状态
  97. const currentAppState = this.gameManager ? this.gameManager.getCurrentAppState() : null;
  98. // 在游戏外界面中,检查是否在需要隐藏菜单按钮的界面
  99. if (currentAppState !== AppState.IN_GAME && this.isInHiddenMenuUI()) {
  100. // 在SkillUpUI、UpgradeUI、ShopUI界面中隐藏菜单按钮
  101. this.menuButton.node.active = false;
  102. console.log('[MenuController] 在游戏外界面中隐藏菜单按钮');
  103. } else {
  104. // 在其他界面中显示菜单按钮
  105. this.menuButton.node.active = true;
  106. console.log('[MenuController] 显示菜单按钮');
  107. }
  108. }
  109. /**
  110. * 菜单按钮点击事件
  111. */
  112. private async onMenuButtonClick() {
  113. // 播放UI点击音效
  114. Audio.playUISound('data/弹球音效/ui play');
  115. if (this.isMenuOpen) {
  116. await this.closeMenu();
  117. } else {
  118. await this.openMenu();
  119. }
  120. }
  121. /**
  122. * 关闭按钮点击事件
  123. */
  124. private async onCloseButtonClick() {
  125. // 播放UI点击音效
  126. Audio.playUISound('data/弹球音效/ui play');
  127. await this.closeMenu();
  128. }
  129. /**
  130. * 继续游戏按钮点击事件
  131. */
  132. private async onContinueButtonClick() {
  133. // 播放UI点击音效
  134. Audio.playUISound('data/弹球音效/ui play');
  135. console.log('[MenuController] 继续游戏按钮被点击');
  136. // 检查GameManager组件引用
  137. if (!this.gameManager) {
  138. console.error('[MenuController] GameManager组件未通过装饰器挂载,请在Inspector中拖拽GameManager组件');
  139. await this.closeMenu();
  140. return;
  141. }
  142. const currentAppState = this.gameManager.getCurrentAppState();
  143. console.log(`[MenuController] 当前应用状态: ${currentAppState}`);
  144. if (currentAppState === AppState.IN_GAME) {
  145. // 在游戏中点击继续,关闭菜单并恢复游戏
  146. console.log('[MenuController] 游戏中点击继续,关闭菜单并恢复游戏');
  147. await this.closeMenu();
  148. } else {
  149. // 游戏外点击继续,直接关闭菜单
  150. console.log('[MenuController] 游戏外点击继续,直接关闭菜单');
  151. await this.closeMenu();
  152. }
  153. }
  154. /**
  155. * 退出游戏按钮点击事件
  156. * 只在游戏中有效,游戏外界面不显示菜单按钮
  157. */
  158. private async onBackButtonClick() {
  159. // 播放UI点击音效
  160. Audio.playUISound('data/弹球音效/ui play');
  161. console.log('[MenuController] 退出游戏按钮被点击');
  162. // 检查GameManager组件引用
  163. if (!this.gameManager) {
  164. console.error('[MenuController] GameManager组件未通过装饰器挂载,请在Inspector中拖拽GameManager组件');
  165. return;
  166. }
  167. const currentAppState = this.gameManager.getCurrentAppState();
  168. console.log(`[MenuController] 当前应用状态: ${currentAppState}`);
  169. // 只处理游戏中的退出逻辑,游戏外界面不显示菜单按钮
  170. if (currentAppState === AppState.IN_GAME) {
  171. // 在游戏中退出,应该触发游戏失败事件显示GameEnd面板
  172. console.log('[MenuController] 游戏中退出,触发GAME_DEFEAT事件显示游戏失败UI');
  173. // 先触发游戏失败事件,让GameEnd面板显示
  174. const eventBus = EventBus.getInstance();
  175. eventBus.emit(GameEvents.GAME_DEFEAT);
  176. // 关闭菜单(不触发GAME_RESUME事件,避免事件冲突)
  177. await this.closeMenuWithoutResume();
  178. console.log('[MenuController] 菜单退出处理完成,已触发GAME_DEFEAT事件');
  179. } else {
  180. // 游戏外不应该显示菜单按钮,如果意外触发则只关闭菜单
  181. console.log('[MenuController] 游戏外意外触发退出按钮,仅关闭菜单');
  182. await this.closeMenu();
  183. }
  184. }
  185. /**
  186. * 重置镜头位置到原始位置
  187. * 确保从游戏中退出时镜头位置正常
  188. */
  189. private resetCameraPosition() {
  190. // 如果还没有获取GameStartMove组件,尝试获取
  191. if (!this.gameStartMoveComponent) {
  192. const cameraNode = find('Canvas/Main Camera');
  193. if (cameraNode) {
  194. this.gameStartMoveComponent = cameraNode.getComponent(GameStartMove);
  195. }
  196. }
  197. // 如果成功获取到组件,重置镜头位置
  198. if (this.gameStartMoveComponent) {
  199. console.log('[MenuController] 重置镜头位置到原始位置');
  200. this.gameStartMoveComponent.resetCameraToOriginalPosition(0.3);
  201. } else {
  202. console.warn('[MenuController] 未找到GameStartMove组件,无法重置镜头位置');
  203. }
  204. }
  205. /**
  206. * 打开菜单
  207. */
  208. public async openMenu(): Promise<void> {
  209. if (this.isMenuOpen) return;
  210. this.isMenuOpen = true;
  211. // 检查是否在游戏中,如果是则通过事件系统触发游戏暂停
  212. if (this.gameManager && this.gameManager.getCurrentAppState() === AppState.IN_GAME) {
  213. console.log('[MenuController] 游戏中打开菜单,通过事件系统触发游戏暂停');
  214. const eventBus = EventBus.getInstance();
  215. eventBus.emit(GameEvents.GAME_PAUSE);
  216. }
  217. // 使用动画显示菜单面板
  218. if (this.popupAni) {
  219. await this.popupAni.showPanel();
  220. }
  221. // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
  222. console.log('菜单已打开');
  223. }
  224. /**
  225. * 关闭菜单
  226. */
  227. public async closeMenu(): Promise<void> {
  228. if (!this.isMenuOpen) return;
  229. this.isMenuOpen = false;
  230. // 使用动画隐藏菜单面板
  231. if (this.popupAni) {
  232. await this.popupAni.hidePanel();
  233. }
  234. // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
  235. // 检查是否在游戏中,如果是则通过事件系统触发游戏恢复
  236. if (this.gameManager && this.gameManager.getCurrentAppState() === AppState.IN_GAME) {
  237. console.log('[MenuController] 游戏中关闭菜单,通过事件系统触发游戏恢复');
  238. const eventBus = EventBus.getInstance();
  239. eventBus.emit(GameEvents.GAME_RESUME);
  240. }
  241. console.log('菜单已关闭');
  242. }
  243. /**
  244. * 关闭菜单但不触发游戏恢复事件(用于游戏失败退出)
  245. */
  246. private async closeMenuWithoutResume(): Promise<void> {
  247. if (!this.isMenuOpen) return;
  248. this.isMenuOpen = false;
  249. // 使用动画隐藏菜单面板
  250. if (this.popupAni) {
  251. await this.popupAni.hidePanel();
  252. }
  253. // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
  254. // 不触发GAME_RESUME事件,避免与GAME_DEFEAT事件冲突
  255. console.log('菜单已关闭(未触发游戏恢复)');
  256. }
  257. /**
  258. * 切换菜单状态
  259. */
  260. public async toggleMenu(): Promise<void> {
  261. if (this.isMenuOpen) {
  262. await this.closeMenu();
  263. } else {
  264. await this.openMenu();
  265. }
  266. }
  267. /**
  268. * 获取菜单状态
  269. */
  270. public getMenuState(): boolean {
  271. return this.isMenuOpen;
  272. }
  273. /**
  274. * 立即关闭菜单(无动画)
  275. */
  276. public closeMenuImmediate(): void {
  277. this.isMenuOpen = false;
  278. if (this.popupAni) {
  279. this.popupAni.hidePanelImmediate();
  280. }
  281. // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
  282. console.log('菜单已立即关闭');
  283. }
  284. /**
  285. * 立即打开菜单(无动画)
  286. */
  287. public openMenuImmediate(): void {
  288. this.isMenuOpen = true;
  289. if (this.popupAni) {
  290. this.popupAni.showPanelImmediate();
  291. }
  292. // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
  293. console.log('菜单已立即打开');
  294. }
  295. /**
  296. * 公共方法:更新菜单按钮显示状态
  297. * 供外部组件调用,例如在界面切换时
  298. */
  299. public updateMenuButtonState(): void {
  300. this.updateMenuButtonVisibility();
  301. }
  302. onDestroy() {
  303. // 解绑按钮事件
  304. if (this.menuButton) {
  305. this.menuButton.node.off(Button.EventType.CLICK, this.onMenuButtonClick, this);
  306. }
  307. if (this.closeButton) {
  308. this.closeButton.node.off(Button.EventType.CLICK, this.onCloseButtonClick, this);
  309. }
  310. if (this.backButton) {
  311. this.backButton.node.off(Button.EventType.CLICK, this.onBackButtonClick, this);
  312. }
  313. if (this.continueButton) {
  314. this.continueButton.node.off(Button.EventType.CLICK, this.onContinueButtonClick, this);
  315. }
  316. // 解绑事件监听器
  317. const eventBus = EventBus.getInstance();
  318. eventBus.off(GameEvents.RETURN_TO_MAIN_MENU, this.updateMenuButtonVisibility, this);
  319. eventBus.off(GameEvents.UI_PANEL_SWITCHED, this.updateMenuButtonVisibility, this);
  320. }
  321. }