GameBlockSelection.ts 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. import { _decorator, Component, Node, Button, Label, find, EventTouch, Vec2, Vec3, UITransform, Rect, Collider2D, Graphics, Color } 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. // 移除toastNode属性,改用事件机制
  32. // @property({
  33. // type: Node,
  34. // tooltip: '拖拽Canvas/Toast节点到这里'
  35. // })
  36. // public toastNode: Node = 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/GameLevelUI/GameArea/GridContainer节点到这里'
  50. })
  51. public gridContainer: Node = null;
  52. @property({
  53. type: Node,
  54. tooltip: '拖拽confirm按钮节点到这里'
  55. })
  56. public confirmButton: Node = null;
  57. @property({
  58. type: Node,
  59. tooltip: '拖拽Canvas/GameLevelUI/InGameManager节点到这里'
  60. })
  61. public inGameManagerNode: Node = null;
  62. @property({
  63. type: Node,
  64. tooltip: '拖拽Canvas/Camera节点到这里'
  65. })
  66. public cameraNode: Node = null;
  67. // 按钮费用配置
  68. private readonly ADD_BALL_COST = 80;
  69. private readonly ADD_COIN_AMOUNT = 80;
  70. private readonly REFRESH_COST = 5;
  71. private session: LevelSessionManager = null;
  72. private ballController: BallController = null;
  73. private blockManager: BlockManager = null;
  74. // 回调函数,用于通知GameManager
  75. public onConfirmCallback: () => void = null;
  76. // 标记是否已初始化
  77. private isInitialized: boolean = false;
  78. // 标记是否应该生成方块(只有在onBattle触发后才为true)
  79. private shouldGenerateBlocks: boolean = false;
  80. // 用户操作方块相关属性
  81. private currentDragBlock: Node | null = null;
  82. private startPos = new Vec2();
  83. private blockStartPos: Vec3 = new Vec3();
  84. // 调试绘制相关属性
  85. @property({
  86. tooltip: '是否启用吸附检测范围调试绘制'
  87. })
  88. public debugDrawSnapRange: boolean = false;
  89. private debugDrawNode: Node = null;
  90. private debugGraphics: Graphics = null;
  91. onEnable() {
  92. console.log('[GameBlockSelection] onEnable() 节点被激活');
  93. // 如果还未初始化,则进行初始化
  94. if (!this.isInitialized) {
  95. this.initializeComponent();
  96. }
  97. this.initDebugDraw();
  98. }
  99. start() {
  100. console.log('[GameBlockSelection] start() 被调用');
  101. // 如果还未初始化,则进行初始化
  102. if (!this.isInitialized) {
  103. this.initializeComponent();
  104. }
  105. }
  106. private initializeComponent() {
  107. console.log('[GameBlockSelection] initializeComponent() 开始初始化');
  108. console.log('[GameBlockSelection] 组件节点名称:', this.node.name);
  109. console.log('[GameBlockSelection] 组件节点激活状态:', this.node.active);
  110. // 获取管理器实例
  111. this.session = LevelSessionManager.inst;
  112. // 获取BallController
  113. if (this.ballControllerNode) {
  114. this.ballController = this.ballControllerNode.getComponent(BallController);
  115. } else {
  116. console.warn('BallController节点未绑定,请在Inspector中拖拽Canvas/GameLevelUI/BallController节点');
  117. }
  118. // 获取BlockManager
  119. if (this.blockManagerNode) {
  120. this.blockManager = this.blockManagerNode.getComponent(BlockManager);
  121. } else {
  122. console.warn('BlockManager节点未绑定,请在Inspector中拖拽Canvas/GameLevelUI/BlockController节点');
  123. }
  124. // 如果没有指定coinLabelNode,尝试找到它
  125. if (!this.coinLabelNode) {
  126. this.coinLabelNode = find('Canvas-001/TopArea/CoinNode/CoinLabel');
  127. }
  128. // 初始化金币显示
  129. this.updateCoinDisplay();
  130. // 绑定按钮事件
  131. this.bindButtonEvents();
  132. // 设置事件监听器
  133. this.setupEventListeners();
  134. // 标记为已初始化
  135. this.isInitialized = true;
  136. console.log('GameBlockSelection.initializeComponent() 初始化完成');
  137. }
  138. // 设置事件监听器
  139. private setupEventListeners() {
  140. console.log('[GameBlockSelection] 开始设置事件监听器');
  141. const eventBus = EventBus.getInstance();
  142. console.log('[GameBlockSelection] EventBus实例获取结果:', !!eventBus);
  143. // 监听重置方块选择事件
  144. eventBus.on(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this);
  145. console.log('[GameBlockSelection] RESET_BLOCK_SELECTION事件监听器已设置');
  146. // 监听显示方块选择事件
  147. // 监听游戏开始事件,用于标记可以开始生成方块
  148. eventBus.on(GameEvents.GAME_START, this.onGameStartEvent, this);
  149. console.log('[GameBlockSelection] GAME_START事件监听器已设置');
  150. // 监听方块拖拽事件设置请求
  151. eventBus.on(GameEvents.SETUP_BLOCK_DRAG_EVENTS, this.onSetupBlockDragEventsEvent, this);
  152. console.log('[GameBlockSelection] SETUP_BLOCK_DRAG_EVENTS事件监听器已设置');
  153. console.log('[GameBlockSelection] 事件监听器设置完成,组件节点:', this.node.name);
  154. console.log('[GameBlockSelection] 当前节点状态:', this.node.active);
  155. }
  156. // 处理重置方块选择事件
  157. private onResetBlockSelectionEvent() {
  158. console.log('[GameBlockSelection] 接收到重置方块选择事件');
  159. this.resetSelection();
  160. }
  161. // 处理游戏开始事件
  162. private onGameStartEvent() {
  163. console.log('[GameBlockSelection] 接收到游戏开始事件,允许生成方块');
  164. this.shouldGenerateBlocks = true;
  165. }
  166. // 处理方块拖拽事件设置请求
  167. private onSetupBlockDragEventsEvent(blocks: Node[]) {
  168. console.log('[GameBlockSelection] 接收到方块拖拽事件设置请求,方块数量:', blocks.length);
  169. for (const block of blocks) {
  170. this.setupBlockDragEvents(block);
  171. console.log(`[GameBlockSelection] 为方块 ${block.name} 设置拖拽事件`);
  172. }
  173. }
  174. // 绑定按钮事件
  175. private bindButtonEvents() {
  176. // 绑定新增小球按钮
  177. if (this.addBallButton) {
  178. const btn = this.addBallButton.getComponent(Button);
  179. if (btn) {
  180. this.addBallButton.on(Button.EventType.CLICK, this.onAddBallClicked, this);
  181. } else {
  182. this.addBallButton.on(Node.EventType.TOUCH_END, this.onAddBallClicked, this);
  183. }
  184. }
  185. // 绑定增加金币按钮
  186. if (this.addCoinButton) {
  187. const btn = this.addCoinButton.getComponent(Button);
  188. if (btn) {
  189. this.addCoinButton.on(Button.EventType.CLICK, this.onAddCoinClicked, this);
  190. } else {
  191. this.addCoinButton.on(Node.EventType.TOUCH_END, this.onAddCoinClicked, this);
  192. }
  193. }
  194. // 绑定刷新方块按钮
  195. if (this.refreshButton) {
  196. const btn = this.refreshButton.getComponent(Button);
  197. if (btn) {
  198. this.refreshButton.on(Button.EventType.CLICK, this.onRefreshClicked, this);
  199. } else {
  200. this.refreshButton.on(Node.EventType.TOUCH_END, this.onRefreshClicked, this);
  201. }
  202. }
  203. // 绑定确认按钮
  204. if (this.confirmButton) {
  205. const btn = this.confirmButton.getComponent(Button);
  206. if (btn) {
  207. this.confirmButton.on(Button.EventType.CLICK, this.onConfirmButtonClicked, this);
  208. } else {
  209. this.confirmButton.on(Node.EventType.TOUCH_END, this.onConfirmButtonClicked, this);
  210. }
  211. }
  212. }
  213. // 新增小球按钮点击
  214. private onAddBallClicked() {
  215. // 应用便宜技能效果计算实际费用
  216. const actualCost = this.getActualCost(this.ADD_BALL_COST);
  217. if (!this.canSpendCoins(actualCost)) {
  218. this.showInsufficientCoinsUI();
  219. return;
  220. }
  221. // 扣除金币
  222. if (this.session.spendCoins(actualCost)) {
  223. this.updateCoinDisplay();
  224. // 通过事件系统创建新的小球
  225. const eventBus = EventBus.getInstance();
  226. eventBus.emit(GameEvents.BALL_CREATE_ADDITIONAL);
  227. console.log(`新增小球成功,扣除${actualCost}金币`);
  228. }
  229. }
  230. // 增加金币按钮点击
  231. private onAddCoinClicked() {
  232. // 免费增加金币(模拟看广告获得奖励)
  233. const coinsToAdd = 80; // 免费获得的金币数量
  234. this.session.addCoins(coinsToAdd);
  235. console.log(`通过观看广告免费获得${coinsToAdd}金币`);
  236. // 更新显示
  237. this.updateCoinDisplay();
  238. // 可以在这里添加播放奖励动画的逻辑
  239. // MoneyAni.playReward(coinsToAdd, 0);
  240. }
  241. // 刷新方块按钮点击
  242. private onRefreshClicked() {
  243. // 应用便宜技能效果计算实际费用
  244. const actualCost = this.getActualCost(this.REFRESH_COST);
  245. if (!this.canSpendCoins(actualCost)) {
  246. this.showInsufficientCoinsUI();
  247. return;
  248. }
  249. // 扣除金币
  250. if (this.session.spendCoins(actualCost)) {
  251. this.updateCoinDisplay();
  252. // 刷新方块
  253. if (this.blockManager) {
  254. // 找到PlacedBlocks容器
  255. const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
  256. if (placedBlocksContainer) {
  257. // 移除已放置方块的标签
  258. BlockTag.removeTagsInContainer(placedBlocksContainer);
  259. }
  260. // 刷新方块
  261. this.blockManager.refreshBlocks();
  262. console.log(`刷新方块成功,扣除${actualCost}金币`);
  263. } else {
  264. console.error('找不到BlockManager,无法刷新方块');
  265. }
  266. }
  267. }
  268. // 确认按钮点击
  269. public onConfirmButtonClicked() {
  270. // 检查是否有上阵方块
  271. const hasBlocks = this.hasPlacedBlocks();
  272. console.log(`[GameBlockSelection] 检查上阵方块: ${hasBlocks ? '有' : '无'}`);
  273. if (!hasBlocks) {
  274. this.showNoPlacedBlocksToast();
  275. return;
  276. }
  277. // 保存已放置的方块
  278. this.preservePlacedBlocks();
  279. // 清理kuang区域的方块(用户期望的行为)
  280. if (this.blockManager) {
  281. this.blockManager.clearBlocks();
  282. console.log('[GameBlockSelection] 已清理kuang区域的方块');
  283. }
  284. // 先回调通知GameManager,让它处理波次逻辑
  285. if (this.onConfirmCallback) {
  286. this.onConfirmCallback();
  287. }
  288. // 播放下滑diban动画,结束备战进入playing状态
  289. this.playDibanSlideDownAnimation();
  290. }
  291. // 播放diban下滑动画
  292. private playDibanSlideDownAnimation() {
  293. console.log('[GameBlockSelection] 开始播放diban下滑动画');
  294. // 使用装饰器属性获取Camera节点上的GameStartMove组件
  295. if (!this.cameraNode) {
  296. console.warn('[GameBlockSelection] Camera节点未设置,请在Inspector中拖拽Canvas/Camera节点');
  297. return;
  298. }
  299. const gameStartMove = this.cameraNode.getComponent('GameStartMove');
  300. if (!gameStartMove) {
  301. console.warn('[GameBlockSelection] GameStartMove组件未找到');
  302. return;
  303. }
  304. // 调用GameStartMove的下滑动画方法
  305. (gameStartMove as any).slideDibanDownAndHide(300, 0.3);
  306. console.log('[GameBlockSelection] 已调用GameStartMove的diban下滑动画');
  307. }
  308. // 保存已放置的方块(从GameManager迁移)
  309. private preservePlacedBlocks() {
  310. if (this.blockManager) {
  311. this.blockManager.onGameStart();
  312. }
  313. }
  314. // 检查是否有足够金币
  315. private canSpendCoins(amount: number): boolean {
  316. return this.session.getCoins() >= amount;
  317. }
  318. // 计算应用便宜技能效果后的实际费用
  319. private getActualCost(baseCost: number): number {
  320. const skillManager = SkillManager.getInstance();
  321. if (skillManager) {
  322. const cheaperSkillLevel = skillManager.getSkillLevel('cheaper_units');
  323. return Math.ceil(SkillManager.calculateCheaperUnitsPrice(baseCost, cheaperSkillLevel));
  324. }
  325. return baseCost;
  326. }
  327. // 更新金币显示
  328. private updateCoinDisplay() {
  329. if (this.coinLabelNode) {
  330. const label = this.coinLabelNode.getComponent(Label);
  331. if (label) {
  332. const coins = this.session.getCoins();
  333. label.string = coins.toString();
  334. console.log(`[GameBlockSelection] 更新金币显示: ${coins}`);
  335. } else {
  336. console.warn('[GameBlockSelection] coinLabelNode缺少Label组件');
  337. }
  338. } else {
  339. console.warn('[GameBlockSelection] coinLabelNode未找到');
  340. }
  341. }
  342. // 显示金币不足UI
  343. private showInsufficientCoinsUI() {
  344. // 使用事件机制显示Toast
  345. EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
  346. message: '金币不足!',
  347. duration: 3.0
  348. });
  349. console.log('金币不足!');
  350. }
  351. // === 公共方法:供GameManager调用 ===
  352. // 生成方块选择(不再控制UI显示,只负责生成方块)
  353. public generateBlockSelection() {
  354. console.log('[GameBlockSelection] generateBlockSelection开始执行');
  355. // 直接生成方块,不再依赖shouldGenerateBlocks标志
  356. if (this.blockManager) {
  357. this.blockManager.refreshBlocks();
  358. console.log('[GameBlockSelection] 生成新的随机方块');
  359. } else {
  360. console.warn('[GameBlockSelection] BlockManager未找到,无法生成随机方块');
  361. }
  362. // 触发进入备战状态事件
  363. EventBus.getInstance().emit(GameEvents.ENTER_BATTLE_PREPARATION);
  364. console.log('[GameBlockSelection] 方块生成完成');
  365. }
  366. // 设置确认回调
  367. public setConfirmCallback(callback: () => void) {
  368. this.onConfirmCallback = callback;
  369. }
  370. // 统一的方块占用情况刷新方法
  371. public refreshGridOccupation() {
  372. console.log('[GameBlockSelection] 刷新方块占用情况');
  373. if (this.blockManager) {
  374. // 不重置网格占用状态,只重新计算已放置方块的占用情况
  375. // 这样可以保留refreshBlocks中恢复的占用信息
  376. // 重新计算所有已放置方块的占用情况
  377. const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
  378. if (placedBlocksContainer) {
  379. for (let i = 0; i < placedBlocksContainer.children.length; i++) {
  380. const block = placedBlocksContainer.children[i];
  381. // 重新标记方块占用的网格位置
  382. const gridNode = this.blockManager.findNearestGridNode(block.worldPosition);
  383. if (gridNode) {
  384. this.blockManager.markOccupiedPositions(block, gridNode);
  385. }
  386. }
  387. }
  388. console.log('[GameBlockSelection] 方块占用情况刷新完成');
  389. } else {
  390. console.warn('[GameBlockSelection] BlockManager未找到,无法刷新占用情况');
  391. }
  392. }
  393. // 重置方块选择状态
  394. public resetSelection() {
  395. console.log('[GameBlockSelection] 重置方块选择状态');
  396. // 重置方块生成标志,等待下次onBattle触发
  397. this.shouldGenerateBlocks = false;
  398. console.log('[GameBlockSelection] 重置方块生成标志');
  399. // 注意:不再直接调用blockManager.onGameReset(),因为StartGame.clearGameStates()
  400. // 已经通过RESET_BLOCK_MANAGER事件触发了BlockManager的重置,避免重复生成方块
  401. console.log('[GameBlockSelection] BlockManager将通过事件系统自动重置');
  402. // 清理所有方块标签
  403. BlockTag.clearAllTags();
  404. console.log('[GameBlockSelection] 方块标签已清理');
  405. // 刷新方块占用情况
  406. this.refreshGridOccupation();
  407. // 更新金币显示
  408. this.updateCoinDisplay();
  409. console.log('[GameBlockSelection] 金币显示已更新');
  410. }
  411. // 检查是否有上阵方块
  412. private hasPlacedBlocks(): boolean {
  413. // 优先使用BlockManager的方法检查
  414. if (this.blockManager) {
  415. return this.blockManager.hasPlacedBlocks();
  416. }
  417. // 备用方案:直接检查PlacedBlocks容器
  418. const placedBlocksContainer = find('Canvas/GameLevelUI/PlacedBlocks');
  419. if (!placedBlocksContainer) {
  420. console.warn('找不到PlacedBlocks容器');
  421. return false;
  422. }
  423. // 检查容器中是否有子节点(方块)
  424. return placedBlocksContainer.children.length > 0;
  425. }
  426. // 显示没有上阵方块的Toast提示
  427. private showNoPlacedBlocksToast() {
  428. console.log('[GameBlockSelection] 显示未上阵植物提示');
  429. // 使用事件机制显示Toast
  430. EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
  431. message: '请至少上阵一个植物!',
  432. duration: 3.0
  433. });
  434. console.log('[GameBlockSelection] 请至少上阵一个植物!');
  435. }
  436. // 设置方块拖拽事件
  437. public setupBlockDragEvents(block: Node) {
  438. block.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  439. if (this.blockManager.gameStarted && this.blockManager.blockLocations.get(block) === 'grid') {
  440. if (!this.canMoveBlock(block)) {
  441. return;
  442. }
  443. }
  444. this.currentDragBlock = block;
  445. this.startPos = event.getUILocation();
  446. this.blockStartPos.set(block.position);
  447. this.currentDragBlock['startLocation'] = this.blockManager.blockLocations.get(block);
  448. block.setSiblingIndex(block.parent.children.length - 1);
  449. this.blockManager.tempStoreBlockOccupiedGrids(block);
  450. // 拖拽开始时禁用碰撞体
  451. const collider = block.getComponent(Collider2D);
  452. if (collider) collider.enabled = false;
  453. }, this);
  454. block.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
  455. if (this.blockManager.gameStarted && this.blockManager.blockLocations.get(block) === 'grid') {
  456. if (!this.canMoveBlock(block)) {
  457. return;
  458. }
  459. }
  460. if (!this.currentDragBlock) return;
  461. const location = event.getUILocation();
  462. const deltaX = location.x - this.startPos.x;
  463. const deltaY = location.y - this.startPos.y;
  464. this.currentDragBlock.position = new Vec3(
  465. this.blockStartPos.x + deltaX,
  466. this.blockStartPos.y + deltaY,
  467. this.blockStartPos.z
  468. );
  469. // 更新调试绘制
  470. this.updateDebugDraw();
  471. }, this);
  472. block.on(Node.EventType.TOUCH_END, async (event: EventTouch) => {
  473. if (this.blockManager.gameStarted && this.blockManager.blockLocations.get(block) === 'grid') {
  474. if (!this.canMoveBlock(block)) {
  475. return;
  476. }
  477. }
  478. if (this.currentDragBlock) {
  479. await this.handleBlockDrop(event);
  480. this.currentDragBlock = null;
  481. // 拖拽结束时恢复碰撞体
  482. const collider = block.getComponent(Collider2D);
  483. if (collider) collider.enabled = true;
  484. // 更新调试绘制
  485. this.updateDebugDraw();
  486. }
  487. }, this);
  488. block.on(Node.EventType.TOUCH_CANCEL, () => {
  489. if (this.currentDragBlock) {
  490. this.returnBlockToOriginalPosition();
  491. this.currentDragBlock = null;
  492. // 拖拽取消时恢复碰撞体
  493. const collider = block.getComponent(Collider2D);
  494. if (collider) collider.enabled = true;
  495. // 更新调试绘制
  496. this.updateDebugDraw();
  497. }
  498. }, this);
  499. }
  500. // 检查方块是否可以移动
  501. private canMoveBlock(block: Node): boolean {
  502. return true;
  503. }
  504. // 处理方块放下
  505. private async handleBlockDrop(event: EventTouch) {
  506. const touchPos = event.getLocation();
  507. const startLocation = this.currentDragBlock['startLocation'];
  508. if (this.isInKuangArea(touchPos)) {
  509. // 检查是否有标签,只有有标签的方块才能放回kuang
  510. if (BlockTag.hasTag(this.currentDragBlock)) {
  511. this.returnBlockToKuang(startLocation);
  512. // 返回kuang后输出格子占用情况
  513. this.blockManager.printGridOccupationMatrix();
  514. // 刷新方块占用情况
  515. this.refreshGridOccupation();
  516. } else {
  517. // 没有标签的方块不能放回kuang,返回原位置
  518. console.log(`[GameBlockSelection] 方块 ${this.currentDragBlock.name} 没有标签,不能放回kuang区域`);
  519. this.returnBlockToOriginalPosition();
  520. // 返回原位置后输出格子占用情况
  521. this.blockManager.printGridOccupationMatrix();
  522. // 刷新方块占用情况
  523. this.refreshGridOccupation();
  524. }
  525. } else if (this.blockManager.tryPlaceBlockToGrid(this.currentDragBlock)) {
  526. await this.handleSuccessfulPlacement(startLocation);
  527. // 放置成功后输出格子占用情况
  528. this.blockManager.printGridOccupationMatrix();
  529. // 刷新方块占用情况
  530. this.refreshGridOccupation();
  531. } else {
  532. console.log(`[GameBlockSelection] 方块 ${this.currentDragBlock.name} 放置到网格失败`);
  533. console.log(`[GameBlockSelection] 方块标签状态:`, BlockTag.hasTag(this.currentDragBlock));
  534. console.log(`[GameBlockSelection] 方块UUID:`, this.currentDragBlock.uuid);
  535. // 放置失败,尝试直接与重叠方块合成
  536. if (this.blockManager.tryMergeOnOverlap(this.currentDragBlock)) {
  537. // 合成成功时,若来自 kuang 则扣费
  538. if (startLocation !== 'grid') {
  539. const price = this.blockManager.getBlockPrice(this.currentDragBlock);
  540. this.blockManager.deductPlayerCoins(price);
  541. }
  542. // 当前拖拽块已被销毁(合并时),无需复位
  543. // 合成成功后输出格子占用情况
  544. this.blockManager.printGridOccupationMatrix();
  545. // 刷新方块占用情况
  546. this.refreshGridOccupation();
  547. } else {
  548. console.log(`[GameBlockSelection] 方块 ${this.currentDragBlock.name} 合成也失败,返回原位置`);
  549. this.returnBlockToOriginalPosition();
  550. // 放置失败后输出格子占用情况
  551. this.blockManager.printGridOccupationMatrix();
  552. // 刷新方块占用情况
  553. this.refreshGridOccupation();
  554. }
  555. }
  556. }
  557. // 检查是否在kuang区域内
  558. private isInKuangArea(touchPos: Vec2): boolean {
  559. if (!this.blockManager.kuangContainer) return false;
  560. const kuangTransform = this.blockManager.kuangContainer.getComponent(UITransform);
  561. if (!kuangTransform) return false;
  562. const kuangBoundingBox = new Rect(
  563. this.blockManager.kuangContainer.worldPosition.x - kuangTransform.width * kuangTransform.anchorX,
  564. this.blockManager.kuangContainer.worldPosition.y - kuangTransform.height * kuangTransform.anchorY,
  565. kuangTransform.width,
  566. kuangTransform.height
  567. );
  568. return kuangBoundingBox.contains(new Vec2(touchPos.x, touchPos.y));
  569. }
  570. // 返回方块到kuang区域
  571. private returnBlockToKuang(startLocation: string) {
  572. const originalPos = this.blockManager.originalPositions.get(this.currentDragBlock);
  573. if (originalPos) {
  574. const kuangNode = this.blockManager.kuangContainer;
  575. if (kuangNode && this.currentDragBlock.parent !== kuangNode) {
  576. this.currentDragBlock.removeFromParent();
  577. kuangNode.addChild(this.currentDragBlock);
  578. }
  579. this.currentDragBlock.position = originalPos.clone();
  580. }
  581. this.blockManager.restoreBlockOccupiedGrids(this.currentDragBlock);
  582. this.blockManager.blockLocations.set(this.currentDragBlock, 'kuang');
  583. this.blockManager.showPriceLabel(this.currentDragBlock);
  584. if (startLocation === 'grid') {
  585. const price = this.blockManager.getBlockPrice(this.currentDragBlock);
  586. this.blockManager.refundPlayerCoins(price);
  587. this.currentDragBlock['placedBefore'] = false;
  588. }
  589. const dbNode = this.currentDragBlock['dbNode'];
  590. if (dbNode) {
  591. dbNode.active = true;
  592. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  593. }
  594. }
  595. // 处理成功放置
  596. private async handleSuccessfulPlacement(startLocation: string) {
  597. const price = this.blockManager.getBlockPrice(this.currentDragBlock);
  598. if (startLocation === 'grid') {
  599. this.blockManager.clearTempStoredOccupiedGrids(this.currentDragBlock);
  600. this.blockManager.blockLocations.set(this.currentDragBlock, 'grid');
  601. this.blockManager.hidePriceLabel(this.currentDragBlock);
  602. const dbNode = this.currentDragBlock['dbNode'];
  603. if (dbNode) {
  604. dbNode.active = false;
  605. }
  606. // 立即将方块移动到PlacedBlocks节点下,不等游戏开始
  607. this.blockManager.moveBlockToPlacedBlocks(this.currentDragBlock);
  608. // 如果游戏已开始,添加锁定视觉提示
  609. if (this.blockManager.gameStarted) {
  610. this.blockManager.addLockedVisualHint(this.currentDragBlock);
  611. // 游戏开始后放置的方块移除标签,不能再放回kuang
  612. BlockTag.removeTag(this.currentDragBlock);
  613. }
  614. // 检查并执行合成
  615. await this.blockManager.tryMergeBlock(this.currentDragBlock);
  616. } else {
  617. if (this.blockManager.deductPlayerCoins(price)) {
  618. this.blockManager.clearTempStoredOccupiedGrids(this.currentDragBlock);
  619. this.blockManager.blockLocations.set(this.currentDragBlock, 'grid');
  620. this.blockManager.hidePriceLabel(this.currentDragBlock);
  621. const dbNode = this.currentDragBlock['dbNode'];
  622. if (dbNode) {
  623. dbNode.active = false;
  624. }
  625. this.currentDragBlock['placedBefore'] = true;
  626. // 立即将方块移动到PlacedBlocks节点下,不等游戏开始
  627. this.blockManager.moveBlockToPlacedBlocks(this.currentDragBlock);
  628. // 如果游戏已开始,添加锁定视觉提示
  629. if (this.blockManager.gameStarted) {
  630. this.blockManager.addLockedVisualHint(this.currentDragBlock);
  631. // 游戏开始后放置的方块移除标签,不能再放回kuang
  632. BlockTag.removeTag(this.currentDragBlock);
  633. }
  634. // 检查并执行合成
  635. await this.blockManager.tryMergeBlock(this.currentDragBlock);
  636. } else {
  637. this.returnBlockToOriginalPosition();
  638. }
  639. }
  640. }
  641. // 返回方块到原位置
  642. private returnBlockToOriginalPosition() {
  643. const currentLocation = this.blockManager.blockLocations.get(this.currentDragBlock);
  644. if (currentLocation === 'kuang') {
  645. const originalPos = this.blockManager.originalPositions.get(this.currentDragBlock);
  646. if (originalPos) {
  647. this.currentDragBlock.position = originalPos.clone();
  648. }
  649. } else {
  650. this.currentDragBlock.position = this.blockStartPos.clone();
  651. }
  652. this.blockManager.restoreBlockOccupiedGrids(this.currentDragBlock);
  653. this.blockManager.showPriceLabel(this.currentDragBlock);
  654. const dbNode = this.currentDragBlock['dbNode'];
  655. if (dbNode) {
  656. dbNode.active = true;
  657. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  658. }
  659. // 刷新方块占用情况
  660. this.refreshGridOccupation();
  661. }
  662. // === 调试绘制相关方法 ===
  663. // 初始化调试绘制
  664. private initDebugDraw() {
  665. if (!this.debugDrawSnapRange) return;
  666. // 创建调试绘制节点
  667. this.debugDrawNode = new Node('DebugDraw');
  668. this.debugGraphics = this.debugDrawNode.addComponent(Graphics);
  669. // 将调试绘制节点添加到场景中
  670. if (this.gridContainer && this.gridContainer.parent) {
  671. this.gridContainer.parent.addChild(this.debugDrawNode);
  672. }
  673. console.log('[GameBlockSelection] 调试绘制初始化完成');
  674. }
  675. // 绘制网格吸附范围
  676. private drawGridSnapRanges() {
  677. if (!this.debugDrawSnapRange || !this.debugGraphics || !this.blockManager) return;
  678. this.debugGraphics.strokeColor = Color.GREEN;
  679. this.debugGraphics.lineWidth = 2;
  680. // 通过BlockManager获取网格信息来绘制吸附范围
  681. const gridSpacing = 54; // 网格间距
  682. const snapRange = gridSpacing * 0.8; // 吸附范围
  683. // 遍历所有网格位置,绘制吸附范围
  684. for (let row = 0; row < 6; row++) {
  685. for (let col = 0; col < 11; col++) {
  686. // 计算网格世界坐标
  687. const gridWorldPos = this.blockManager.getGridWorldPosition(row, col);
  688. if (!gridWorldPos) continue;
  689. // 转换为调试绘制节点的本地坐标
  690. const localPos = new Vec3();
  691. this.debugDrawNode.getComponent(UITransform).convertToNodeSpaceAR(gridWorldPos, localPos);
  692. // 绘制网格吸附范围圆圈
  693. this.debugGraphics.circle(localPos.x, localPos.y, snapRange);
  694. this.debugGraphics.stroke();
  695. }
  696. }
  697. }
  698. // 绘制方块吸附范围
  699. private drawBlockSnapRange(block: Node) {
  700. if (!this.debugDrawSnapRange || !this.debugGraphics || !block || !this.blockManager) return;
  701. // 绘制方块吸附点
  702. this.debugGraphics.strokeColor = Color.RED;
  703. this.debugGraphics.fillColor = Color.RED;
  704. this.debugGraphics.lineWidth = 3;
  705. const blockParts = this.blockManager.getBlockParts(block);
  706. const gridSpacing = 54; // 网格间距
  707. const snapRange = gridSpacing * 0.6; // 吸附范围
  708. blockParts.forEach(part => {
  709. const worldPos = part.node.getWorldPosition();
  710. const localPos = new Vec3();
  711. this.debugDrawNode.getComponent(UITransform).convertToNodeSpaceAR(worldPos, localPos);
  712. // 绘制红色十字标记吸附点
  713. const crossSize = 10;
  714. this.debugGraphics.moveTo(localPos.x - crossSize, localPos.y);
  715. this.debugGraphics.lineTo(localPos.x + crossSize, localPos.y);
  716. this.debugGraphics.moveTo(localPos.x, localPos.y - crossSize);
  717. this.debugGraphics.lineTo(localPos.x, localPos.y + crossSize);
  718. this.debugGraphics.stroke();
  719. // 绘制吸附范围圆圈
  720. this.debugGraphics.circle(localPos.x, localPos.y, snapRange);
  721. this.debugGraphics.stroke();
  722. });
  723. }
  724. // 更新调试绘制
  725. private updateDebugDraw() {
  726. if (!this.debugDrawSnapRange || !this.debugGraphics) return;
  727. this.debugGraphics.clear();
  728. // 绘制网格吸附范围
  729. this.drawGridSnapRanges();
  730. // 如果有正在拖拽的方块,绘制其吸附范围
  731. if (this.currentDragBlock) {
  732. this.drawBlockSnapRange(this.currentDragBlock);
  733. }
  734. }
  735. // 清理调试绘制
  736. private cleanupDebugDraw() {
  737. if (this.debugDrawNode && this.debugDrawNode.isValid) {
  738. this.debugDrawNode.destroy();
  739. this.debugDrawNode = null;
  740. this.debugGraphics = null;
  741. }
  742. }
  743. // 设置调试绘制开关
  744. public setDebugDrawSnapRange(enabled: boolean) {
  745. this.debugDrawSnapRange = enabled;
  746. if (enabled) {
  747. this.initDebugDraw();
  748. } else {
  749. this.cleanupDebugDraw();
  750. }
  751. console.log(`[GameBlockSelection] 调试绘制已${enabled ? '开启' : '关闭'}`);
  752. }
  753. onDisable() {
  754. this.removeEventListeners();
  755. this.cleanupDebugDraw();
  756. }
  757. onDestroy() {
  758. this.removeEventListeners();
  759. this.cleanupDebugDraw();
  760. }
  761. // 移除事件监听器
  762. private removeEventListeners() {
  763. const eventBus = EventBus.getInstance();
  764. eventBus.off(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this);
  765. eventBus.off(GameEvents.GAME_START, this.onGameStartEvent, this);
  766. }
  767. }