BlockManager.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. import { _decorator, Component, Node, Prefab, instantiate, Vec3, EventTouch, Vec2, UITransform, find, Rect, Label, Color, Size } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. @ccclass('BlockManager')
  4. export class BlockManager extends Component {
  5. // 预制体数组,存储5个预制体
  6. @property([Prefab])
  7. public blockPrefabs: Prefab[] = [];
  8. // 网格容器节点
  9. @property({
  10. type: Node,
  11. tooltip: '拖拽GridContainer节点到这里'
  12. })
  13. public gridContainer: Node = null;
  14. // 方块容器节点(kuang)
  15. @property({
  16. type: Node,
  17. tooltip: '拖拽kuang节点到这里'
  18. })
  19. public kuangContainer: Node = null;
  20. // 金币标签节点
  21. @property({
  22. type: Node,
  23. tooltip: '拖拽CoinLabel节点到这里'
  24. })
  25. public coinLabelNode: Node = null;
  26. // 游戏是否已开始
  27. public gameStarted: boolean = false;
  28. // 方块移动冷却时间(秒)
  29. @property({
  30. tooltip: '游戏开始后方块移动的冷却时间(秒)'
  31. })
  32. public blockMoveCooldown: number = 10;
  33. // 玩家金币数量
  34. private playerCoins: number = 699;
  35. // 方块价格标签映射
  36. private blockPriceMap: Map<Node, Node> = new Map();
  37. // 已经生成的块
  38. private blocks: Node[] = [];
  39. // 当前拖拽的块
  40. private currentDragBlock: Node | null = null;
  41. // 拖拽起始位置
  42. private startPos = new Vec2();
  43. // 块的起始位置
  44. private blockStartPos: Vec3 = new Vec3();
  45. // 网格占用情况,用于控制台输出
  46. private gridOccupationMap: number[][] = [];
  47. // 网格行数和列数
  48. private readonly GRID_ROWS = 6;
  49. private readonly GRID_COLS = 11;
  50. // 是否已初始化网格信息
  51. private gridInitialized = false;
  52. // 存储网格节点信息
  53. private gridNodes: Node[][] = [];
  54. // 网格间距
  55. private gridSpacing = 54;
  56. // 不参与占用的节点名称列表
  57. private readonly NON_BLOCK_NODES: string[] = ['Weapon', 'Price'];
  58. // 临时保存方块的原始占用格子
  59. private tempRemovedOccupiedGrids: { block: Node, occupiedGrids: { row: number, col: number }[] }[] = [];
  60. // 方块原始位置(在kuang中的位置)
  61. private originalPositions: Map<Node, Vec3> = new Map();
  62. // 方块当前所在的区域
  63. private blockLocations: Map<Node, string> = new Map();
  64. // 方块移动冷却状态管理
  65. private blockCooldowns: Map<Node, number> = new Map(); // 存储每个方块的冷却结束时间
  66. private globalCooldownEndTime: number = 0; // 全局冷却结束时间
  67. // 检查方块是否可以移动(冷却检查)
  68. private canMoveBlock(block: Node): boolean {
  69. if (!this.gameStarted) {
  70. // 游戏未开始(备战阶段),可以自由移动
  71. return true;
  72. }
  73. const currentTime = Date.now() / 1000; // 转换为秒
  74. // 检查全局冷却
  75. if (currentTime < this.globalCooldownEndTime) {
  76. const remainingTime = Math.ceil(this.globalCooldownEndTime - currentTime);
  77. return false;
  78. }
  79. return true;
  80. }
  81. // 设置方块移动冷却
  82. private setBlockCooldown(block: Node) {
  83. if (!this.gameStarted) {
  84. // 游戏未开始,不设置冷却
  85. return;
  86. }
  87. const currentTime = Date.now() / 1000; // 转换为秒
  88. const cooldownEndTime = currentTime + this.blockMoveCooldown;
  89. // 设置全局冷却
  90. this.globalCooldownEndTime = cooldownEndTime;
  91. }
  92. // 清除所有冷却(游戏重置时调用)
  93. public clearAllCooldowns() {
  94. this.blockCooldowns.clear();
  95. this.globalCooldownEndTime = 0;
  96. }
  97. start() {
  98. // 如果没有指定GridContainer,尝试找到它
  99. if (!this.gridContainer) {
  100. this.gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer');
  101. if (!this.gridContainer) {
  102. console.error('找不到GridContainer节点');
  103. return;
  104. }
  105. }
  106. // 如果没有指定kuangContainer,尝试找到它
  107. if (!this.kuangContainer) {
  108. this.kuangContainer = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
  109. if (!this.kuangContainer) {
  110. console.error('找不到kuang节点');
  111. return;
  112. }
  113. }
  114. // 如果没有指定coinLabelNode,尝试找到它
  115. if (!this.coinLabelNode) {
  116. this.coinLabelNode = find('Canvas/GameLevelUI/CoinNode/CoinLabel');
  117. if (!this.coinLabelNode) {
  118. console.error('找不到CoinLabel节点');
  119. return;
  120. }
  121. }
  122. // 确保有PlacedBlocks节点用于存放已放置的方块
  123. this.ensurePlacedBlocksNode();
  124. // 初始化玩家金币显示
  125. this.updateCoinDisplay();
  126. // 初始化网格信息
  127. this.initGridInfo();
  128. // 初始化网格占用情况
  129. this.initGridOccupationMap();
  130. // 在kuang下随机生成三个方块
  131. this.generateRandomBlocksInKuang();
  132. }
  133. // 确保有PlacedBlocks节点
  134. ensurePlacedBlocksNode() {
  135. const canvas = find('Canvas');
  136. if (!canvas) {
  137. console.error('找不到Canvas节点');
  138. return;
  139. }
  140. let placedBlocksNode = find('Canvas/PlacedBlocks');
  141. if (!placedBlocksNode) {
  142. placedBlocksNode = new Node('PlacedBlocks');
  143. canvas.addChild(placedBlocksNode);
  144. if (!placedBlocksNode.getComponent(UITransform)) {
  145. placedBlocksNode.addComponent(UITransform);
  146. }
  147. console.log('已创建PlacedBlocks节点');
  148. }
  149. }
  150. // 初始化网格信息
  151. initGridInfo() {
  152. if (!this.gridContainer || this.gridInitialized) return;
  153. this.gridNodes = [];
  154. for (let row = 0; row < this.GRID_ROWS; row++) {
  155. this.gridNodes[row] = [];
  156. }
  157. for (let i = 0; i < this.gridContainer.children.length; i++) {
  158. const grid = this.gridContainer.children[i];
  159. if (grid.name.startsWith('Grid_')) {
  160. const parts = grid.name.split('_');
  161. if (parts.length === 3) {
  162. const row = parseInt(parts[1]);
  163. const col = parseInt(parts[2]);
  164. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  165. this.gridNodes[row][col] = grid;
  166. }
  167. }
  168. }
  169. }
  170. if (this.GRID_ROWS > 1 && this.GRID_COLS > 0) {
  171. if (this.gridNodes[0][0] && this.gridNodes[1][0]) {
  172. const pos1 = this.gridNodes[0][0].position;
  173. const pos2 = this.gridNodes[1][0].position;
  174. this.gridSpacing = Math.abs(pos2.y - pos1.y);
  175. }
  176. }
  177. this.gridInitialized = true;
  178. }
  179. // 初始化网格占用情况
  180. initGridOccupationMap() {
  181. this.gridOccupationMap = [];
  182. for (let row = 0; row < this.GRID_ROWS; row++) {
  183. const rowArray: number[] = [];
  184. for (let col = 0; col < this.GRID_COLS; col++) {
  185. rowArray.push(0);
  186. }
  187. this.gridOccupationMap.push(rowArray);
  188. }
  189. }
  190. // 在kuang下随机生成三个方块
  191. private generateRandomBlocksInKuang() {
  192. this.clearBlocks();
  193. if (this.blockPrefabs.length === 0) {
  194. console.error('没有可用的预制体');
  195. return;
  196. }
  197. const kuangNode = this.kuangContainer;
  198. if (!kuangNode) {
  199. console.error('找不到kuang节点');
  200. return;
  201. }
  202. const offsets = [
  203. new Vec3(-200, 0, 0),
  204. new Vec3(0, 0, 0),
  205. new Vec3(200, 0, 0)
  206. ];
  207. const dbNodes = [
  208. kuangNode.getChildByName('db01'),
  209. kuangNode.getChildByName('db02'),
  210. kuangNode.getChildByName('db03')
  211. ];
  212. console.log('开始在kuang容器中生成随机方块');
  213. for (let i = 0; i < 3; i++) {
  214. const randomIndex = Math.floor(Math.random() * this.blockPrefabs.length);
  215. const prefab = this.blockPrefabs[randomIndex];
  216. if (!prefab) {
  217. console.error(`方块预制体索引 ${randomIndex} 无效`);
  218. continue;
  219. }
  220. const block = instantiate(prefab);
  221. kuangNode.addChild(block);
  222. block.position = offsets[i];
  223. this.originalPositions.set(block, offsets[i].clone());
  224. this.blockLocations.set(block, 'kuang');
  225. this.blocks.push(block);
  226. if (dbNodes[i]) {
  227. const priceNode = dbNodes[i].getChildByName('Price');
  228. if (priceNode) {
  229. this.blockPriceMap.set(block, priceNode);
  230. priceNode.active = true;
  231. }
  232. this.associateDbNodeWithBlock(block, dbNodes[i]);
  233. }
  234. this.setupDragEvents(block);
  235. console.log(`生成方块 ${i + 1}/3: ${prefab.name} 在位置 (${offsets[i].x.toFixed(2)}, ${offsets[i].y.toFixed(2)})`);
  236. }
  237. console.log(`成功在kuang容器中生成了 ${this.blocks.length} 个方块`);
  238. this.updateCoinDisplay();
  239. }
  240. // 将db节点与方块关联
  241. associateDbNodeWithBlock(block: Node, dbNode: Node) {
  242. block['dbNode'] = dbNode;
  243. block.on(Node.EventType.TRANSFORM_CHANGED, () => {
  244. if (dbNode && block.parent) {
  245. const location = this.blockLocations.get(block);
  246. if (location === 'grid') {
  247. dbNode.active = false;
  248. return;
  249. }
  250. dbNode.active = true;
  251. const worldPos = block.parent.getComponent(UITransform).convertToWorldSpaceAR(block.position);
  252. const localPos = dbNode.parent.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
  253. dbNode.position = new Vec3(localPos.x, localPos.y - 80, localPos.z);
  254. }
  255. });
  256. }
  257. // 更新金币显示
  258. updateCoinDisplay() {
  259. if (this.coinLabelNode) {
  260. const label = this.coinLabelNode.getComponent(Label);
  261. if (label) {
  262. label.string = this.playerCoins.toString();
  263. }
  264. }
  265. }
  266. // 获取方块价格
  267. getBlockPrice(block: Node): number {
  268. const priceNode = this.blockPriceMap.get(block);
  269. if (priceNode) {
  270. const label = priceNode.getComponent(Label);
  271. if (label) {
  272. const price = parseInt(label.string);
  273. if (!isNaN(price)) {
  274. return price;
  275. }
  276. }
  277. }
  278. return 50;
  279. }
  280. // 隐藏价格标签
  281. hidePriceLabel(block: Node) {
  282. const priceNode = this.blockPriceMap.get(block);
  283. if (priceNode) {
  284. priceNode.active = false;
  285. }
  286. }
  287. // 显示价格标签
  288. showPriceLabel(block: Node) {
  289. const priceNode = this.blockPriceMap.get(block);
  290. if (priceNode) {
  291. priceNode.active = true;
  292. }
  293. }
  294. // 扣除玩家金币
  295. deductPlayerCoins(amount: number): boolean {
  296. if (this.playerCoins >= amount) {
  297. this.playerCoins -= amount;
  298. this.updateCoinDisplay();
  299. return true;
  300. }
  301. return false;
  302. }
  303. // 归还玩家金币
  304. refundPlayerCoins(amount: number) {
  305. this.playerCoins += amount;
  306. this.updateCoinDisplay();
  307. }
  308. // 设置拖拽事件
  309. setupDragEvents(block: Node) {
  310. block.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  311. if (this.gameStarted && this.blockLocations.get(block) === 'grid') {
  312. if (!this.canMoveBlock(block)) {
  313. return;
  314. }
  315. }
  316. this.currentDragBlock = block;
  317. this.startPos = event.getUILocation();
  318. this.blockStartPos.set(block.position);
  319. this.currentDragBlock['startLocation'] = this.blockLocations.get(block);
  320. block.setSiblingIndex(block.parent.children.length - 1);
  321. this.tempStoreBlockOccupiedGrids(block);
  322. }, this);
  323. block.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
  324. if (this.gameStarted && this.blockLocations.get(block) === 'grid') {
  325. if (!this.canMoveBlock(block)) {
  326. return;
  327. }
  328. }
  329. if (!this.currentDragBlock) return;
  330. const location = event.getUILocation();
  331. const deltaX = location.x - this.startPos.x;
  332. const deltaY = location.y - this.startPos.y;
  333. this.currentDragBlock.position = new Vec3(
  334. this.blockStartPos.x + deltaX,
  335. this.blockStartPos.y + deltaY,
  336. this.blockStartPos.z
  337. );
  338. }, this);
  339. block.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
  340. if (this.gameStarted && this.blockLocations.get(block) === 'grid') {
  341. if (!this.canMoveBlock(block)) {
  342. return;
  343. }
  344. }
  345. if (this.currentDragBlock) {
  346. this.handleBlockDrop(event);
  347. // 如果成功移动且游戏已开始,设置冷却
  348. if (this.gameStarted && this.blockLocations.get(this.currentDragBlock) === 'grid') {
  349. this.setBlockCooldown(this.currentDragBlock);
  350. }
  351. this.currentDragBlock = null;
  352. }
  353. }, this);
  354. block.on(Node.EventType.TOUCH_CANCEL, () => {
  355. if (this.currentDragBlock) {
  356. this.returnBlockToOriginalPosition();
  357. this.currentDragBlock = null;
  358. }
  359. }, this);
  360. }
  361. // 处理方块放下
  362. handleBlockDrop(event: EventTouch) {
  363. const touchPos = event.getUILocation();
  364. const startLocation = this.currentDragBlock['startLocation'];
  365. if (this.isInKuangArea(touchPos)) {
  366. this.returnBlockToKuang(startLocation);
  367. } else if (this.tryPlaceBlockToGrid(this.currentDragBlock)) {
  368. this.handleSuccessfulPlacement(startLocation);
  369. console.log('成功放置,移动到网格');
  370. } else {
  371. this.returnBlockToOriginalPosition();
  372. }
  373. }
  374. // 返回方块到kuang区域
  375. returnBlockToKuang(startLocation: string) {
  376. const originalPos = this.originalPositions.get(this.currentDragBlock);
  377. if (originalPos) {
  378. const kuangNode = this.kuangContainer;
  379. if (kuangNode && this.currentDragBlock.parent !== kuangNode) {
  380. this.currentDragBlock.removeFromParent();
  381. kuangNode.addChild(this.currentDragBlock);
  382. }
  383. this.currentDragBlock.position = originalPos.clone();
  384. }
  385. this.restoreBlockOccupiedGrids(this.currentDragBlock);
  386. this.blockLocations.set(this.currentDragBlock, 'kuang');
  387. this.showPriceLabel(this.currentDragBlock);
  388. if (startLocation === 'grid') {
  389. const price = this.getBlockPrice(this.currentDragBlock);
  390. this.refundPlayerCoins(price);
  391. this.currentDragBlock['placedBefore'] = false;
  392. }
  393. const dbNode = this.currentDragBlock['dbNode'];
  394. if (dbNode) {
  395. dbNode.active = true;
  396. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  397. }
  398. }
  399. // 处理成功放置
  400. handleSuccessfulPlacement(startLocation: string) {
  401. const price = this.getBlockPrice(this.currentDragBlock);
  402. if (startLocation === 'grid') {
  403. this.clearTempStoredOccupiedGrids(this.currentDragBlock);
  404. this.blockLocations.set(this.currentDragBlock, 'grid');
  405. this.hidePriceLabel(this.currentDragBlock);
  406. const dbNode = this.currentDragBlock['dbNode'];
  407. if (dbNode) {
  408. dbNode.active = false;
  409. }
  410. // 立即将方块移动到PlacedBlocks节点下,不等游戏开始
  411. this.moveBlockToPlacedBlocks(this.currentDragBlock);
  412. // 如果游戏已开始,添加锁定视觉提示
  413. if (this.gameStarted) {
  414. this.addLockedVisualHint(this.currentDragBlock);
  415. }
  416. } else {
  417. if (this.deductPlayerCoins(price)) {
  418. this.clearTempStoredOccupiedGrids(this.currentDragBlock);
  419. this.blockLocations.set(this.currentDragBlock, 'grid');
  420. this.hidePriceLabel(this.currentDragBlock);
  421. const dbNode = this.currentDragBlock['dbNode'];
  422. if (dbNode) {
  423. dbNode.active = false;
  424. }
  425. this.currentDragBlock['placedBefore'] = true;
  426. // 立即将方块移动到PlacedBlocks节点下,不等游戏开始
  427. this.moveBlockToPlacedBlocks(this.currentDragBlock);
  428. // 如果游戏已开始,添加锁定视觉提示
  429. if (this.gameStarted) {
  430. this.addLockedVisualHint(this.currentDragBlock);
  431. }
  432. } else {
  433. this.returnBlockToOriginalPosition();
  434. }
  435. }
  436. }
  437. // 返回方块到原位置
  438. returnBlockToOriginalPosition() {
  439. const currentLocation = this.blockLocations.get(this.currentDragBlock);
  440. if (currentLocation === 'kuang') {
  441. const originalPos = this.originalPositions.get(this.currentDragBlock);
  442. if (originalPos) {
  443. this.currentDragBlock.position = originalPos.clone();
  444. }
  445. } else {
  446. this.currentDragBlock.position = this.blockStartPos.clone();
  447. }
  448. this.restoreBlockOccupiedGrids(this.currentDragBlock);
  449. this.showPriceLabel(this.currentDragBlock);
  450. const dbNode = this.currentDragBlock['dbNode'];
  451. if (dbNode) {
  452. dbNode.active = true;
  453. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  454. }
  455. }
  456. // 检查是否在kuang区域内
  457. isInKuangArea(touchPos: Vec2): boolean {
  458. if (!this.kuangContainer) return false;
  459. const kuangTransform = this.kuangContainer.getComponent(UITransform);
  460. if (!kuangTransform) return false;
  461. const kuangBoundingBox = new Rect(
  462. this.kuangContainer.worldPosition.x - kuangTransform.width * kuangTransform.anchorX,
  463. this.kuangContainer.worldPosition.y - kuangTransform.height * kuangTransform.anchorY,
  464. kuangTransform.width,
  465. kuangTransform.height
  466. );
  467. return kuangBoundingBox.contains(new Vec2(touchPos.x, touchPos.y));
  468. }
  469. // 临时保存方块占用的网格
  470. tempStoreBlockOccupiedGrids(block: Node) {
  471. const occupiedGrids = block['occupiedGrids'];
  472. if (!occupiedGrids || occupiedGrids.length === 0) return;
  473. this.tempRemovedOccupiedGrids.push({
  474. block: block,
  475. occupiedGrids: [...occupiedGrids]
  476. });
  477. for (const grid of occupiedGrids) {
  478. if (grid.row >= 0 && grid.row < this.GRID_ROWS &&
  479. grid.col >= 0 && grid.col < this.GRID_COLS) {
  480. this.gridOccupationMap[grid.row][grid.col] = 0;
  481. }
  482. }
  483. block['occupiedGrids'] = [];
  484. }
  485. // 恢复方块原来的占用状态
  486. restoreBlockOccupiedGrids(block: Node) {
  487. const index = this.tempRemovedOccupiedGrids.findIndex(item => item.block === block);
  488. if (index === -1) return;
  489. const savedItem = this.tempRemovedOccupiedGrids[index];
  490. for (const grid of savedItem.occupiedGrids) {
  491. if (grid.row >= 0 && grid.row < this.GRID_ROWS &&
  492. grid.col >= 0 && grid.col < this.GRID_COLS) {
  493. this.gridOccupationMap[grid.row][grid.col] = 1;
  494. }
  495. }
  496. block['occupiedGrids'] = [...savedItem.occupiedGrids];
  497. this.tempRemovedOccupiedGrids.splice(index, 1);
  498. }
  499. // 清除临时保存的占用状态
  500. clearTempStoredOccupiedGrids(block: Node) {
  501. const index = this.tempRemovedOccupiedGrids.findIndex(item => item.block === block);
  502. if (index === -1) return;
  503. this.tempRemovedOccupiedGrids.splice(index, 1);
  504. }
  505. // 尝试将方块放置到网格中
  506. tryPlaceBlockToGrid(block: Node): boolean {
  507. if (!this.gridContainer || !this.gridInitialized) return false;
  508. let b1Node = block;
  509. if (block.name !== 'B1') {
  510. b1Node = block.getChildByName('B1');
  511. if (!b1Node) {
  512. return false;
  513. }
  514. }
  515. const b1WorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position);
  516. const gridPos = this.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(b1WorldPos);
  517. const gridSize = this.gridContainer.getComponent(UITransform).contentSize;
  518. const halfWidth = gridSize.width / 2;
  519. const halfHeight = gridSize.height / 2;
  520. const tolerance = this.gridSpacing * 0.5;
  521. if (gridPos.x < -halfWidth - tolerance || gridPos.x > halfWidth + tolerance ||
  522. gridPos.y < -halfHeight - tolerance || gridPos.y > halfHeight + tolerance) {
  523. return false;
  524. }
  525. const nearestGrid = this.findNearestGridNode(gridPos);
  526. if (!nearestGrid) {
  527. return false;
  528. }
  529. return this.tryPlaceBlockToSpecificGrid(block, nearestGrid);
  530. }
  531. // 找到最近的网格节点
  532. findNearestGridNode(position: Vec3): Node {
  533. if (!this.gridContainer || !this.gridInitialized) return null;
  534. let nearestNode: Node = null;
  535. let minDistance = Number.MAX_VALUE;
  536. for (let row = 0; row < this.GRID_ROWS; row++) {
  537. for (let col = 0; col < this.GRID_COLS; col++) {
  538. const grid = this.gridNodes[row][col];
  539. if (grid) {
  540. const distance = Vec3.distance(position, grid.position);
  541. if (distance < minDistance) {
  542. minDistance = distance;
  543. nearestNode = grid;
  544. }
  545. }
  546. }
  547. }
  548. if (minDistance > this.gridSpacing * 2) {
  549. return null;
  550. }
  551. return nearestNode;
  552. }
  553. // 尝试将方块放置到指定的网格节点
  554. tryPlaceBlockToSpecificGrid(block: Node, targetGrid: Node): boolean {
  555. let b1Node = block;
  556. if (block.name !== 'B1') {
  557. b1Node = block.getChildByName('B1');
  558. if (!b1Node) {
  559. return false;
  560. }
  561. }
  562. if (!this.canPlaceBlockAt(block, targetGrid)) {
  563. return false;
  564. }
  565. const gridCenterWorldPos = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(targetGrid.position);
  566. const targetWorldPos = gridCenterWorldPos.clone();
  567. const b1LocalPos = b1Node.position.clone();
  568. let rootTargetWorldPos;
  569. if (b1Node === block) {
  570. rootTargetWorldPos = targetWorldPos.clone();
  571. } else {
  572. rootTargetWorldPos = new Vec3(
  573. targetWorldPos.x - b1LocalPos.x,
  574. targetWorldPos.y - b1LocalPos.y,
  575. targetWorldPos.z
  576. );
  577. }
  578. const rootTargetLocalPos = block.parent.getComponent(UITransform).convertToNodeSpaceAR(rootTargetWorldPos);
  579. block.position = rootTargetLocalPos;
  580. this.markOccupiedPositions(block, targetGrid);
  581. return true;
  582. }
  583. // 检查方块是否可以放置在指定位置
  584. canPlaceBlockAt(block: Node, targetGrid: Node): boolean {
  585. if (!this.gridInitialized) return false;
  586. const targetRowCol = this.getGridRowCol(targetGrid);
  587. if (!targetRowCol) return false;
  588. const parts = this.getBlockParts(block);
  589. for (const part of parts) {
  590. const row = targetRowCol.row - part.y;
  591. const col = targetRowCol.col + part.x;
  592. if (row < 0 || row >= this.GRID_ROWS || col < 0 || col >= this.GRID_COLS) {
  593. return false;
  594. }
  595. if (this.gridOccupationMap[row][col] === 1) {
  596. return false;
  597. }
  598. }
  599. return true;
  600. }
  601. // 获取网格行列索引
  602. getGridRowCol(gridNode: Node): { row: number, col: number } | null {
  603. if (!gridNode || !gridNode.name.startsWith('Grid_')) return null;
  604. const parts = gridNode.name.split('_');
  605. if (parts.length === 3) {
  606. const row = parseInt(parts[1]);
  607. const col = parseInt(parts[2]);
  608. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  609. return { row, col };
  610. }
  611. }
  612. return null;
  613. }
  614. // 获取方块的所有部分节点及其相对坐标
  615. getBlockParts(block: Node): { node: Node, x: number, y: number }[] {
  616. const parts: { node: Node, x: number, y: number }[] = [];
  617. parts.push({ node: block, x: 0, y: 0 });
  618. this.findBlockParts(block, parts, 0, 0);
  619. return parts;
  620. }
  621. // 递归查找方块的所有部分
  622. findBlockParts(node: Node, result: { node: Node, x: number, y: number }[], parentX: number, parentY: number) {
  623. for (let i = 0; i < node.children.length; i++) {
  624. const child = node.children[i];
  625. if (this.NON_BLOCK_NODES.indexOf(child.name) !== -1) {
  626. continue;
  627. }
  628. let x = parentX;
  629. let y = parentY;
  630. const match = child.name.match(/^\((-?\d+),(-?\d+)\)$/);
  631. if (match) {
  632. x = parseInt(match[1]);
  633. y = parseInt(match[2]);
  634. result.push({ node: child, x, y });
  635. } else if (child.name.startsWith('B')) {
  636. const relativeX = Math.round(child.position.x / this.gridSpacing);
  637. const relativeY = -Math.round(child.position.y / this.gridSpacing);
  638. x = parentX + relativeX;
  639. y = parentY + relativeY;
  640. result.push({ node: child, x, y });
  641. }
  642. this.findBlockParts(child, result, x, y);
  643. }
  644. }
  645. // 标记方块占用的格子
  646. markOccupiedPositions(block: Node, targetGrid: Node) {
  647. if (!this.gridInitialized) return;
  648. const targetRowCol = this.getGridRowCol(targetGrid);
  649. if (!targetRowCol) return;
  650. const parts = this.getBlockParts(block);
  651. block['occupiedGrids'] = [];
  652. for (const part of parts) {
  653. const row = targetRowCol.row - part.y;
  654. const col = targetRowCol.col + part.x;
  655. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  656. this.gridOccupationMap[row][col] = 1;
  657. block['occupiedGrids'] = block['occupiedGrids'] || [];
  658. block['occupiedGrids'].push({ row, col });
  659. }
  660. }
  661. }
  662. // 清除方块
  663. clearBlocks() {
  664. const blocksToRemove = [];
  665. for (const block of this.blocks) {
  666. if (block.isValid) {
  667. const location = this.blockLocations.get(block);
  668. if (location === 'kuang') {
  669. blocksToRemove.push(block);
  670. }
  671. }
  672. }
  673. for (const block of blocksToRemove) {
  674. const dbNode = block['dbNode'];
  675. if (dbNode && dbNode.isValid) {
  676. block.off(Node.EventType.TRANSFORM_CHANGED);
  677. const kuangNode = this.kuangContainer;
  678. if (kuangNode) {
  679. const dbName = dbNode.name;
  680. if (!kuangNode.getChildByName(dbName)) {
  681. dbNode.parent = kuangNode;
  682. }
  683. }
  684. }
  685. const index = this.blocks.indexOf(block);
  686. if (index !== -1) {
  687. this.blocks.splice(index, 1);
  688. }
  689. this.originalPositions.delete(block);
  690. this.blockLocations.delete(block);
  691. this.blockPriceMap.delete(block);
  692. block.destroy();
  693. }
  694. }
  695. // 游戏开始时调用
  696. onGameStart() {
  697. this.gameStarted = true;
  698. console.log('游戏已开始,已放置的方块将有移动冷却时间');
  699. for (const block of this.blocks) {
  700. if (block.isValid) {
  701. const location = this.blockLocations.get(block);
  702. if (location === 'grid') {
  703. this.hidePriceLabel(block);
  704. const dbNode = block['dbNode'];
  705. if (dbNode) {
  706. dbNode.active = false;
  707. }
  708. this.moveBlockToPlacedBlocks(block);
  709. this.addLockedVisualHint(block);
  710. }
  711. }
  712. }
  713. }
  714. // 游戏重置时调用
  715. onGameReset() {
  716. this.gameStarted = false;
  717. this.clearAllCooldowns();
  718. console.log('游戏已重置,方块可以自由移动');
  719. }
  720. // 添加视觉提示,表明方块已锁定
  721. addLockedVisualHint(block: Node) {
  722. const children = block.children;
  723. for (let i = 0; i < children.length; i++) {
  724. const child = children[i];
  725. if (this.NON_BLOCK_NODES.indexOf(child.name) !== -1) {
  726. continue;
  727. }
  728. child.setScale(new Vec3(0.95, 0.95, 1));
  729. }
  730. }
  731. // 将方块移动到PlacedBlocks节点下
  732. moveBlockToPlacedBlocks(block: Node) {
  733. const placedBlocksNode = find('Canvas/PlacedBlocks');
  734. if (!placedBlocksNode) {
  735. console.error('找不到PlacedBlocks节点');
  736. return;
  737. }
  738. const worldPosition = new Vec3();
  739. block.getWorldPosition(worldPosition);
  740. block.removeFromParent();
  741. placedBlocksNode.addChild(block);
  742. block.setWorldPosition(worldPosition);
  743. }
  744. }