GameBlockSelection.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. import { _decorator, Component, Node, Button, Label, find, UITransform, Sprite, Color, tween, Tween, Prefab, instantiate } from 'cc';
  2. import { LevelSessionManager } from '../../Core/LevelSessionManager';
  3. import { BallController } from '../BallController';
  4. import { BlockManager } from '../BlockManager';
  5. import { GameStartMove } from '../../Animations/GameStartMove';
  6. import { BlockTag } from './BlockTag';
  7. import { SkillManager } from '../SkillSelection/SkillManager';
  8. import EventBus, { GameEvents } from '../../Core/EventBus';
  9. const { ccclass, property } = _decorator;
  10. @ccclass('GameBlockSelection')
  11. export class GameBlockSelection extends Component {
  12. @property({
  13. type: Node,
  14. tooltip: '拖拽BlockSelectionUI/diban/ann001按钮节点到这里'
  15. })
  16. public addBallButton: Node = null;
  17. @property({
  18. type: Node,
  19. tooltip: '拖拽BlockSelectionUI/diban/ann002按钮节点到这里'
  20. })
  21. public addCoinButton: Node = null;
  22. @property({
  23. type: Node,
  24. tooltip: '拖拽BlockSelectionUI/diban/ann003按钮节点到这里'
  25. })
  26. public refreshButton: Node = null;
  27. @property({
  28. type: Node,
  29. tooltip: '拖拽Canvas-001/TopArea/CoinNode/CoinLabel节点到这里'
  30. })
  31. public coinLabelNode: Node = null;
  32. @property({
  33. type: Prefab,
  34. tooltip: '拖拽Toast预制体到这里'
  35. })
  36. public toastPrefab: Prefab = null;
  37. @property({
  38. type: Node,
  39. tooltip: '拖拽Canvas/GameLevelUI/BallController节点到这里'
  40. })
  41. public ballControllerNode: Node = null;
  42. @property({
  43. type: Node,
  44. tooltip: '拖拽Canvas/GameLevelUI/BlockController节点到这里'
  45. })
  46. public blockManagerNode: Node = null;
  47. @property({
  48. type: Node,
  49. tooltip: '拖拽Canvas/Camera节点到这里'
  50. })
  51. public cameraNode: Node = null;
  52. @property({
  53. type: Node,
  54. tooltip: '拖拽Canvas/GameLevelUI/GameArea/GridContainer节点到这里'
  55. })
  56. public gridContainer: Node = null;
  57. @property({
  58. type: Node,
  59. tooltip: '拖拽confirm按钮节点到这里'
  60. })
  61. public confirmButton: Node = null;
  62. @property({
  63. type: Node,
  64. tooltip: '拖拽BlockSelectionUI根节点到这里'
  65. })
  66. public blockSelectionUINode: Node = null;
  67. @property({
  68. type: Node,
  69. tooltip: '拖拽BlockSelectionUI/diban节点到这里'
  70. })
  71. public dibanNode: Node = null;
  72. // 按钮费用配置
  73. private readonly ADD_BALL_COST = 80;
  74. private readonly ADD_COIN_AMOUNT = 80;
  75. private readonly REFRESH_COST = 5;
  76. private session: LevelSessionManager = null;
  77. private ballController: BallController = null;
  78. private blockManager: BlockManager = null;
  79. private gameStartMove: GameStartMove = null;
  80. // 回调函数,用于通知GameManager
  81. public onConfirmCallback: () => void = null;
  82. start() {
  83. console.log('GameBlockSelection.start() 开始初始化');
  84. // 获取管理器实例
  85. this.session = LevelSessionManager.inst;
  86. // 获取BallController
  87. if (this.ballControllerNode) {
  88. this.ballController = this.ballControllerNode.getComponent(BallController);
  89. } else {
  90. console.warn('BallController节点未绑定,请在Inspector中拖拽Canvas/GameLevelUI/BallController节点');
  91. }
  92. // 获取BlockManager
  93. if (this.blockManagerNode) {
  94. this.blockManager = this.blockManagerNode.getComponent(BlockManager);
  95. } else {
  96. console.warn('BlockManager节点未绑定,请在Inspector中拖拽Canvas/GameLevelUI/BlockController节点');
  97. }
  98. // 获取GameStartMove组件
  99. if (this.cameraNode) {
  100. this.gameStartMove = this.cameraNode.getComponent(GameStartMove);
  101. console.log('GameStartMove组件获取结果:', !!this.gameStartMove);
  102. // 如果GameStartMove存在,设置BlockSelectionUI和diban引用,并更新原始位置
  103. if (this.gameStartMove && this.blockSelectionUINode && this.dibanNode) {
  104. this.gameStartMove.blockSelectionUI = this.blockSelectionUINode;
  105. this.gameStartMove.dibanNode = this.dibanNode;
  106. this.gameStartMove.updateDibanOriginalPosition();
  107. console.log('GameStartMove引用设置完成:', {
  108. blockSelectionUISet: !!this.gameStartMove.blockSelectionUI,
  109. dibanNodeSet: !!this.gameStartMove.dibanNode,
  110. blockSelectionUINodeName: this.blockSelectionUINode.name,
  111. dibanNodeName: this.dibanNode.name
  112. });
  113. } else {
  114. console.warn('GameStartMove引用设置失败:', {
  115. gameStartMove: !!this.gameStartMove,
  116. blockSelectionUINode: !!this.blockSelectionUINode,
  117. dibanNode: !!this.dibanNode
  118. });
  119. }
  120. } else {
  121. console.warn('Camera节点未绑定,请在Inspector中拖拽Canvas/Camera节点');
  122. }
  123. // 如果没有指定coinLabelNode,尝试找到它
  124. if (!this.coinLabelNode) {
  125. this.coinLabelNode = find('Canvas-001/TopArea/CoinNode/CoinLabel');
  126. }
  127. // 绑定按钮事件
  128. this.bindButtonEvents();
  129. // 设置事件监听器
  130. this.setupEventListeners();
  131. console.log('GameBlockSelection.start() 初始化完成');
  132. }
  133. // 设置事件监听器
  134. private setupEventListeners() {
  135. const eventBus = EventBus.getInstance();
  136. // 监听重置方块选择事件
  137. eventBus.on(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this);
  138. }
  139. // 处理重置方块选择事件
  140. private onResetBlockSelectionEvent() {
  141. console.log('[GameBlockSelection] 接收到重置方块选择事件');
  142. this.resetSelection();
  143. }
  144. // 绑定按钮事件
  145. private bindButtonEvents() {
  146. // 绑定新增小球按钮
  147. if (this.addBallButton) {
  148. const btn = this.addBallButton.getComponent(Button);
  149. if (btn) {
  150. this.addBallButton.on(Button.EventType.CLICK, this.onAddBallClicked, this);
  151. } else {
  152. this.addBallButton.on(Node.EventType.TOUCH_END, this.onAddBallClicked, this);
  153. }
  154. }
  155. // 绑定增加金币按钮
  156. if (this.addCoinButton) {
  157. const btn = this.addCoinButton.getComponent(Button);
  158. if (btn) {
  159. this.addCoinButton.on(Button.EventType.CLICK, this.onAddCoinClicked, this);
  160. } else {
  161. this.addCoinButton.on(Node.EventType.TOUCH_END, this.onAddCoinClicked, this);
  162. }
  163. }
  164. // 绑定刷新方块按钮
  165. if (this.refreshButton) {
  166. const btn = this.refreshButton.getComponent(Button);
  167. if (btn) {
  168. this.refreshButton.on(Button.EventType.CLICK, this.onRefreshClicked, this);
  169. } else {
  170. this.refreshButton.on(Node.EventType.TOUCH_END, this.onRefreshClicked, this);
  171. }
  172. }
  173. // 绑定确认按钮
  174. if (this.confirmButton) {
  175. const btn = this.confirmButton.getComponent(Button);
  176. if (btn) {
  177. this.confirmButton.on(Button.EventType.CLICK, this.onConfirmButtonClicked, this);
  178. } else {
  179. this.confirmButton.on(Node.EventType.TOUCH_END, this.onConfirmButtonClicked, this);
  180. }
  181. }
  182. }
  183. // 新增小球按钮点击
  184. private onAddBallClicked() {
  185. // 应用便宜技能效果计算实际费用
  186. const actualCost = this.getActualCost(this.ADD_BALL_COST);
  187. if (!this.canSpendCoins(actualCost)) {
  188. this.showInsufficientCoinsUI();
  189. return;
  190. }
  191. // 扣除金币
  192. if (this.session.spendCoins(actualCost)) {
  193. this.updateCoinDisplay();
  194. // 通过事件系统创建新的小球
  195. const eventBus = EventBus.getInstance();
  196. eventBus.emit(GameEvents.BALL_CREATE_ADDITIONAL);
  197. console.log(`新增小球成功,扣除${actualCost}金币`);
  198. }
  199. }
  200. // 增加金币按钮点击
  201. private onAddCoinClicked() {
  202. // 应用便宜技能效果计算实际费用
  203. const actualCost = this.getActualCost(this.ADD_COIN_AMOUNT);
  204. if (!this.canSpendCoins(actualCost)) {
  205. this.showInsufficientCoinsUI();
  206. return;
  207. }
  208. // 扣除金币
  209. if (this.session.spendCoins(actualCost)) {
  210. this.updateCoinDisplay();
  211. // 添加金币(这里可以根据需要调整添加的金币数量)
  212. const coinsToAdd = 100; // 可以根据需要调整
  213. this.session.addCoins(coinsToAdd);
  214. console.log(`花费${actualCost}金币,获得${coinsToAdd}金币`);
  215. // 更新显示
  216. this.updateCoinDisplay();
  217. }
  218. }
  219. // 刷新方块按钮点击
  220. private onRefreshClicked() {
  221. // 应用便宜技能效果计算实际费用
  222. const actualCost = this.getActualCost(this.REFRESH_COST);
  223. if (!this.canSpendCoins(actualCost)) {
  224. this.showInsufficientCoinsUI();
  225. return;
  226. }
  227. // 扣除金币
  228. if (this.session.spendCoins(actualCost)) {
  229. this.updateCoinDisplay();
  230. // 刷新方块
  231. if (this.blockManager) {
  232. // 找到PlacedBlocks容器
  233. const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
  234. if (placedBlocksContainer) {
  235. // 移除已放置方块的标签
  236. BlockTag.removeTagsInContainer(placedBlocksContainer);
  237. }
  238. // 刷新方块
  239. this.blockManager.refreshBlocks();
  240. console.log(`刷新方块成功,扣除${actualCost}金币`);
  241. } else {
  242. console.error('找不到BlockManager,无法刷新方块');
  243. }
  244. }
  245. }
  246. // 确认按钮点击(从GameManager迁移的onConfirmButtonClicked)
  247. public onConfirmButtonClicked() {
  248. // 检查是否有上阵方块
  249. const hasBlocks = this.hasPlacedBlocks();
  250. console.log(`[GameBlockSelection] 检查上阵方块: ${hasBlocks ? '有' : '无'}`);
  251. if (!hasBlocks) {
  252. this.showNoPlacedBlocksToast();
  253. return;
  254. }
  255. // 保存已放置的方块
  256. this.preservePlacedBlocks();
  257. // 先回调通知GameManager,让它处理波次逻辑
  258. if (this.onConfirmCallback) {
  259. this.onConfirmCallback();
  260. }
  261. // 然后隐藏UI并恢复游戏
  262. this.hideBlockSelection();
  263. }
  264. // 保存已放置的方块(从GameManager迁移)
  265. private preservePlacedBlocks() {
  266. if (this.blockManager) {
  267. this.blockManager.onGameStart();
  268. }
  269. }
  270. // 检查是否有足够金币
  271. private canSpendCoins(amount: number): boolean {
  272. return this.session.getCoins() >= amount;
  273. }
  274. // 计算应用便宜技能效果后的实际费用
  275. private getActualCost(baseCost: number): number {
  276. const skillManager = SkillManager.getInstance();
  277. if (skillManager) {
  278. const cheaperSkillLevel = skillManager.getSkillLevel('cheaper_units');
  279. return Math.ceil(SkillManager.calculateCheaperUnitsPrice(baseCost, cheaperSkillLevel));
  280. }
  281. return baseCost;
  282. }
  283. // 更新金币显示
  284. private updateCoinDisplay() {
  285. if (this.coinLabelNode) {
  286. const label = this.coinLabelNode.getComponent(Label);
  287. if (label) {
  288. label.string = this.session.getCoins().toString();
  289. }
  290. }
  291. }
  292. // 显示金币不足UI
  293. private showInsufficientCoinsUI() {
  294. if (!this.toastPrefab) {
  295. console.error('Toast预制体未绑定,请在Inspector中拖拽Toast预制体');
  296. return;
  297. }
  298. // 实例化Toast预制体
  299. const toastNode = instantiate(this.toastPrefab);
  300. // 设置Toast的文本为"金币不足!"
  301. const labelNode = toastNode.getChildByName('label');
  302. if (labelNode) {
  303. const label = labelNode.getComponent(Label);
  304. if (label) {
  305. label.string = '金币不足!';
  306. }
  307. }
  308. // 将Toast添加到Canvas下
  309. const canvas = find('Canvas');
  310. if (canvas) {
  311. canvas.addChild(toastNode);
  312. }
  313. // 3秒后自动销毁Toast
  314. this.scheduleOnce(() => {
  315. if (toastNode && toastNode.isValid) {
  316. toastNode.destroy();
  317. }
  318. }, 3.0);
  319. console.log('金币不足!');
  320. }
  321. // === 公共方法:供GameManager调用 ===
  322. // 显示方块选择UI(用于游戏开始或下一波)
  323. public showBlockSelection(isNextWave: boolean = false) {
  324. // 通过事件系统暂停游戏
  325. const eventBus = EventBus.getInstance();
  326. eventBus.emit(GameEvents.GAME_PAUSE);
  327. console.log('[GameBlockSelection] 显示方块选择UI,确保游戏暂停');
  328. // 首先显示UI节点
  329. this.node.active = true;
  330. if (this.gridContainer) {
  331. this.gridContainer.active = true;
  332. }
  333. // 如果有BlockSelectionUI节点,确保它可见
  334. if (this.blockSelectionUINode) {
  335. this.blockSelectionUINode.active = true;
  336. }
  337. // 每次显示方块选择UI时,生成新的随机方块
  338. if (this.blockManager) {
  339. this.blockManager.refreshBlocks();
  340. console.log('[GameBlockSelection] 生成新的随机方块');
  341. } else {
  342. console.warn('[GameBlockSelection] BlockManager未找到,无法生成随机方块');
  343. }
  344. // 播放BlockSelectionUI出现动画
  345. this.playShowAnimation();
  346. // 派发事件
  347. EventBus.getInstance().emit(GameEvents.BLOCK_SELECTION_OPEN);
  348. console.log(`[GameBlockSelection] ${isNextWave ? '显示下一波方块选择UI' : '显示游戏开始方块选择UI'}`);
  349. }
  350. // 隐藏方块选择UI
  351. public hideBlockSelection() {
  352. // 播放隐藏动画,动画完成后隐藏UI
  353. this.playHideAnimation(() => {
  354. this.node.active = false;
  355. // 派发事件
  356. EventBus.getInstance().emit(GameEvents.BLOCK_SELECTION_CLOSE);
  357. // 发送游戏开始事件,确保GamePause正确设置isInGameMode状态
  358. console.log('[GameBlockSelection] 隐藏方块选择UI、发送游戏开始事件');
  359. EventBus.getInstance().emit(GameEvents.GAME_START);
  360. // 通过事件系统恢复游戏状态
  361. console.log('[GameBlockSelection] 恢复游戏状态');
  362. EventBus.getInstance().emit(GameEvents.GAME_RESUME);
  363. // 移除这里的波次提示调用,让GameManager统一控制波次提示的显示时机
  364. // 显示波次提示Toast并开始敌人生成
  365. // const enemyController = EnemyController.getInstance();
  366. // if (enemyController) {
  367. // enemyController.showStartWavePromptUI(2); // 显示2秒Toast提示
  368. // }
  369. });
  370. }
  371. // 播放显示动画
  372. private playShowAnimation() {
  373. console.log('播放显示动画playShowAnimation');
  374. if (this.gameStartMove && this.blockSelectionUINode && this.dibanNode) {
  375. // 设置GameStartMove的blockSelectionUI和dibanNode引用
  376. this.gameStartMove.blockSelectionUI = this.blockSelectionUINode;
  377. this.gameStartMove.dibanNode = this.dibanNode;
  378. // 使用GameStartMove的进入方块选择模式动画
  379. this.gameStartMove.enterBlockSelectionMode(300, 0.3);
  380. console.log('进入方块选择模式动画已启动');
  381. }
  382. }
  383. // 播放隐藏动画
  384. private playHideAnimation(onComplete?: () => void) {
  385. console.log('播放隐藏动画playHideAnimation');
  386. console.log('GameBlockSelection 引用检查:', {
  387. gameStartMove: !!this.gameStartMove,
  388. blockSelectionUINode: !!this.blockSelectionUINode,
  389. dibanNode: !!this.dibanNode,
  390. blockSelectionUINodeName: this.blockSelectionUINode?.name,
  391. dibanNodeName: this.dibanNode?.name
  392. });
  393. if (this.gameStartMove && this.blockSelectionUINode && this.dibanNode) {
  394. // 设置GameStartMove的blockSelectionUI和dibanNode引用
  395. this.gameStartMove.blockSelectionUI = this.blockSelectionUINode;
  396. this.gameStartMove.dibanNode = this.dibanNode;
  397. console.log('GameStartMove 引用设置后检查:', {
  398. gameStartMoveBlockSelectionUI: !!this.gameStartMove.blockSelectionUI,
  399. gameStartMoveDibanNode: !!this.gameStartMove.dibanNode
  400. });
  401. // 使用GameStartMove的退出方块选择模式动画
  402. this.gameStartMove.exitBlockSelectionMode(300, 0.3);
  403. console.log('退出方块选择模式动画已启动');
  404. // 由于slideDibanDownAndHide会自动隐藏blockSelectionUI和重置位置,我们需要在动画完成后执行回调
  405. if (onComplete) {
  406. this.scheduleOnce(() => {
  407. onComplete();
  408. }, 0.3); // 与动画时长一致
  409. }
  410. } else {
  411. console.log('GameBlockSelection 条件检查失败,无法播放动画');
  412. if (onComplete) {
  413. // 如果没有动画组件,直接执行回调
  414. onComplete();
  415. }
  416. }
  417. }
  418. // 设置确认回调
  419. public setConfirmCallback(callback: () => void) {
  420. this.onConfirmCallback = callback;
  421. }
  422. // 重置方块选择状态
  423. public resetSelection() {
  424. console.log('[GameBlockSelection] 重置方块选择状态');
  425. // 重置BlockManager状态
  426. if (this.blockManager) {
  427. this.blockManager.onGameReset();
  428. console.log('[GameBlockSelection] BlockManager状态已重置');
  429. }
  430. // 清理所有方块标签
  431. BlockTag.clearAllTags();
  432. console.log('[GameBlockSelection] 方块标签已清理');
  433. // 更新金币显示
  434. this.updateCoinDisplay();
  435. console.log('[GameBlockSelection] 金币显示已更新');
  436. }
  437. // 检查是否有上阵方块
  438. private hasPlacedBlocks(): boolean {
  439. // 优先使用BlockManager的方法检查
  440. if (this.blockManager) {
  441. return this.blockManager.hasPlacedBlocks();
  442. }
  443. // 备用方案:直接检查PlacedBlocks容器
  444. const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
  445. if (!placedBlocksContainer) {
  446. console.warn('找不到PlacedBlocks容器');
  447. return false;
  448. }
  449. // 检查容器中是否有子节点(方块)
  450. return placedBlocksContainer.children.length > 0;
  451. }
  452. // 显示没有上阵方块的Toast提示
  453. private showNoPlacedBlocksToast() {
  454. console.log('[GameBlockSelection] 开始显示Toast提示');
  455. if (!this.toastPrefab) {
  456. console.error('[GameBlockSelection] Toast预制体未绑定,请在Inspector中拖拽Toast预制体');
  457. return;
  458. }
  459. console.log('[GameBlockSelection] Toast预制体已绑定');
  460. // 实例化Toast预制体
  461. const toastNode = instantiate(this.toastPrefab);
  462. console.log('[GameBlockSelection] Toast节点已实例化:', toastNode.name);
  463. // 设置Toast的文本为"请至少上阵一个植物!"
  464. const labelNode = toastNode.getChildByName('label');
  465. console.log('[GameBlockSelection] 查找label子节点:', !!labelNode);
  466. if (labelNode) {
  467. const label = labelNode.getComponent(Label);
  468. console.log('[GameBlockSelection] 获取Label组件:', !!label);
  469. if (label) {
  470. label.string = '请至少上阵一个植物!';
  471. } else {
  472. console.error('[GameBlockSelection] label节点没有Label组件');
  473. }
  474. } else {
  475. console.error('[GameBlockSelection] 找不到名为"label"的子节点');
  476. }
  477. // 将Toast添加到Canvas下
  478. const canvas = find('Canvas');
  479. if (canvas) {
  480. canvas.addChild(toastNode);
  481. } else {
  482. console.error('[GameBlockSelection] 找不到Canvas节点');
  483. return;
  484. }
  485. // 3秒后自动销毁Toast
  486. this.scheduleOnce(() => {
  487. if (toastNode && toastNode.isValid) {
  488. toastNode.destroy();
  489. } else {
  490. console.warn('[GameBlockSelection] Toast节点无效,无法销毁');
  491. }
  492. }, 3.0);
  493. console.log('[GameBlockSelection] 请至少上阵一个植物!');
  494. }
  495. onDestroy() {
  496. // 清理事件监听
  497. const eventBus = EventBus.getInstance();
  498. eventBus.off(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this);
  499. }
  500. }