GameBlockSelection.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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 coinsToAdd = 80; // 免费获得的金币数量
  210. this.session.addCoins(coinsToAdd);
  211. console.log(`通过观看广告免费获得${coinsToAdd}金币`);
  212. // 更新显示
  213. this.updateCoinDisplay();
  214. // 可以在这里添加播放奖励动画的逻辑
  215. // MoneyAni.playReward(coinsToAdd, 0);
  216. }
  217. // 刷新方块按钮点击
  218. private onRefreshClicked() {
  219. // 应用便宜技能效果计算实际费用
  220. const actualCost = this.getActualCost(this.REFRESH_COST);
  221. if (!this.canSpendCoins(actualCost)) {
  222. this.showInsufficientCoinsUI();
  223. return;
  224. }
  225. // 扣除金币
  226. if (this.session.spendCoins(actualCost)) {
  227. this.updateCoinDisplay();
  228. // 刷新方块
  229. if (this.blockManager) {
  230. // 找到PlacedBlocks容器
  231. const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
  232. if (placedBlocksContainer) {
  233. // 移除已放置方块的标签
  234. BlockTag.removeTagsInContainer(placedBlocksContainer);
  235. }
  236. // 刷新方块
  237. this.blockManager.refreshBlocks();
  238. console.log(`刷新方块成功,扣除${actualCost}金币`);
  239. } else {
  240. console.error('找不到BlockManager,无法刷新方块');
  241. }
  242. }
  243. }
  244. // 确认按钮点击
  245. public onConfirmButtonClicked() {
  246. // 检查是否有上阵方块
  247. const hasBlocks = this.hasPlacedBlocks();
  248. console.log(`[GameBlockSelection] 检查上阵方块: ${hasBlocks ? '有' : '无'}`);
  249. if (!hasBlocks) {
  250. this.showNoPlacedBlocksToast();
  251. return;
  252. }
  253. // 保存已放置的方块
  254. this.preservePlacedBlocks();
  255. // 清理kuang区域的方块(用户期望的行为)
  256. if (this.blockManager) {
  257. this.blockManager.clearBlocks();
  258. console.log('[GameBlockSelection] 已清理kuang区域的方块');
  259. }
  260. // 先回调通知GameManager,让它处理波次逻辑
  261. if (this.onConfirmCallback) {
  262. this.onConfirmCallback();
  263. }
  264. // 播放下滑diban动画,结束备战进入playing状态
  265. this.playDibanSlideDownAnimation();
  266. }
  267. // 播放diban下滑动画
  268. private playDibanSlideDownAnimation() {
  269. console.log('[GameBlockSelection] 开始播放diban下滑动画');
  270. // 使用装饰器属性获取Camera节点上的GameStartMove组件
  271. if (!this.cameraNode) {
  272. console.warn('[GameBlockSelection] Camera节点未设置,请在Inspector中拖拽Canvas/Camera节点');
  273. return;
  274. }
  275. const gameStartMove = this.cameraNode.getComponent('GameStartMove');
  276. if (!gameStartMove) {
  277. console.warn('[GameBlockSelection] GameStartMove组件未找到');
  278. return;
  279. }
  280. // 调用GameStartMove的下滑动画方法
  281. (gameStartMove as any).slideDibanDownAndHide(300, 0.3);
  282. console.log('[GameBlockSelection] 已调用GameStartMove的diban下滑动画');
  283. }
  284. // 保存已放置的方块(从GameManager迁移)
  285. private preservePlacedBlocks() {
  286. if (this.blockManager) {
  287. this.blockManager.onGameStart();
  288. }
  289. }
  290. // 检查是否有足够金币
  291. private canSpendCoins(amount: number): boolean {
  292. return this.session.getCoins() >= amount;
  293. }
  294. // 计算应用便宜技能效果后的实际费用
  295. private getActualCost(baseCost: number): number {
  296. const skillManager = SkillManager.getInstance();
  297. if (skillManager) {
  298. const cheaperSkillLevel = skillManager.getSkillLevel('cheaper_units');
  299. return Math.ceil(SkillManager.calculateCheaperUnitsPrice(baseCost, cheaperSkillLevel));
  300. }
  301. return baseCost;
  302. }
  303. // 更新金币显示
  304. private updateCoinDisplay() {
  305. if (this.coinLabelNode) {
  306. const label = this.coinLabelNode.getComponent(Label);
  307. if (label) {
  308. const coins = this.session.getCoins();
  309. label.string = coins.toString();
  310. console.log(`[GameBlockSelection] 更新金币显示: ${coins}`);
  311. } else {
  312. console.warn('[GameBlockSelection] coinLabelNode缺少Label组件');
  313. }
  314. } else {
  315. console.warn('[GameBlockSelection] coinLabelNode未找到');
  316. }
  317. }
  318. // 显示金币不足UI
  319. private showInsufficientCoinsUI() {
  320. if (!this.toastPrefab) {
  321. console.error('Toast预制体未绑定,请在Inspector中拖拽Toast预制体');
  322. return;
  323. }
  324. // 实例化Toast预制体
  325. const toastNode = instantiate(this.toastPrefab);
  326. // 设置Toast的文本为"金币不足!"
  327. const labelNode = toastNode.getChildByName('label');
  328. if (labelNode) {
  329. const label = labelNode.getComponent(Label);
  330. if (label) {
  331. label.string = '金币不足!';
  332. }
  333. }
  334. // 将Toast添加到Canvas下
  335. const canvas = find('Canvas');
  336. if (canvas) {
  337. canvas.addChild(toastNode);
  338. }
  339. // 3秒后自动销毁Toast
  340. this.scheduleOnce(() => {
  341. if (toastNode && toastNode.isValid) {
  342. toastNode.destroy();
  343. }
  344. }, 3.0);
  345. console.log('金币不足!');
  346. }
  347. // === 公共方法:供GameManager调用 ===
  348. // 生成方块选择(不再控制UI显示,只负责生成方块)
  349. public generateBlockSelection() {
  350. console.log('[GameBlockSelection] generateBlockSelection开始执行');
  351. // 直接生成方块,不再依赖shouldGenerateBlocks标志
  352. if (this.blockManager) {
  353. this.blockManager.refreshBlocks();
  354. console.log('[GameBlockSelection] 生成新的随机方块');
  355. } else {
  356. console.warn('[GameBlockSelection] BlockManager未找到,无法生成随机方块');
  357. }
  358. // 触发进入备战状态事件
  359. EventBus.getInstance().emit(GameEvents.ENTER_BATTLE_PREPARATION);
  360. console.log('[GameBlockSelection] 方块生成完成');
  361. }
  362. // 设置确认回调
  363. public setConfirmCallback(callback: () => void) {
  364. this.onConfirmCallback = callback;
  365. }
  366. // 重置方块选择状态
  367. public resetSelection() {
  368. console.log('[GameBlockSelection] 重置方块选择状态');
  369. // 重置方块生成标志,等待下次onBattle触发
  370. this.shouldGenerateBlocks = false;
  371. console.log('[GameBlockSelection] 重置方块生成标志');
  372. // 注意:不再直接调用blockManager.onGameReset(),因为StartGame.clearGameStates()
  373. // 已经通过RESET_BLOCK_MANAGER事件触发了BlockManager的重置,避免重复生成方块
  374. console.log('[GameBlockSelection] BlockManager将通过事件系统自动重置');
  375. // 清理所有方块标签
  376. BlockTag.clearAllTags();
  377. console.log('[GameBlockSelection] 方块标签已清理');
  378. // 更新金币显示
  379. this.updateCoinDisplay();
  380. console.log('[GameBlockSelection] 金币显示已更新');
  381. }
  382. // 检查是否有上阵方块
  383. private hasPlacedBlocks(): boolean {
  384. // 优先使用BlockManager的方法检查
  385. if (this.blockManager) {
  386. return this.blockManager.hasPlacedBlocks();
  387. }
  388. // 备用方案:直接检查PlacedBlocks容器
  389. const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
  390. if (!placedBlocksContainer) {
  391. console.warn('找不到PlacedBlocks容器');
  392. return false;
  393. }
  394. // 检查容器中是否有子节点(方块)
  395. return placedBlocksContainer.children.length > 0;
  396. }
  397. // 显示没有上阵方块的Toast提示
  398. private showNoPlacedBlocksToast() {
  399. console.log('[GameBlockSelection] 开始显示Toast提示');
  400. if (!this.toastPrefab) {
  401. console.error('[GameBlockSelection] Toast预制体未绑定,请在Inspector中拖拽Toast预制体');
  402. return;
  403. }
  404. console.log('[GameBlockSelection] Toast预制体已绑定');
  405. // 实例化Toast预制体
  406. const toastNode = instantiate(this.toastPrefab);
  407. console.log('[GameBlockSelection] Toast节点已实例化:', toastNode.name);
  408. // 设置Toast的文本为"请至少上阵一个植物!"
  409. const labelNode = toastNode.getChildByName('label');
  410. console.log('[GameBlockSelection] 查找label子节点:', !!labelNode);
  411. if (labelNode) {
  412. const label = labelNode.getComponent(Label);
  413. console.log('[GameBlockSelection] 获取Label组件:', !!label);
  414. if (label) {
  415. label.string = '请至少上阵一个植物!';
  416. } else {
  417. console.error('[GameBlockSelection] label节点没有Label组件');
  418. }
  419. } else {
  420. console.error('[GameBlockSelection] 找不到名为"label"的子节点');
  421. }
  422. // 将Toast添加到Canvas下
  423. const canvas = find('Canvas');
  424. if (canvas) {
  425. canvas.addChild(toastNode);
  426. } else {
  427. console.error('[GameBlockSelection] 找不到Canvas节点');
  428. return;
  429. }
  430. // 3秒后自动销毁Toast
  431. this.scheduleOnce(() => {
  432. if (toastNode && toastNode.isValid) {
  433. toastNode.destroy();
  434. } else {
  435. console.warn('[GameBlockSelection] Toast节点无效,无法销毁');
  436. }
  437. }, 3.0);
  438. console.log('[GameBlockSelection] 请至少上阵一个植物!');
  439. }
  440. onDestroy() {
  441. // 清理事件监听
  442. const eventBus = EventBus.getInstance();
  443. eventBus.off(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this);
  444. eventBus.off(GameEvents.GAME_START, this.onGameStartEvent, this);
  445. }
  446. }