GameBlockSelection.ts 19 KB

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