BlockManager.ts 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108
  1. import { _decorator, Component, Node, Prefab, instantiate, Vec3, EventTouch, Vec2, UITransform, find, Rect, Label, Color, Size, Contact2DType, Collider2D, IPhysics2DContact } 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. private playerCoins: number = 699;
  30. // 方块价格标签映射
  31. private blockPriceMap: Map<Node, Node> = new Map();
  32. // 已经生成的块
  33. private blocks: Node[] = [];
  34. // 当前拖拽的块
  35. private currentDragBlock: Node | null = null;
  36. // 拖拽起始位置
  37. private startPos = new Vec2();
  38. // 块的起始位置
  39. private blockStartPos: Vec3 = new Vec3();
  40. // 网格占用情况,用于控制台输出
  41. private gridOccupationMap: number[][] = [];
  42. // 网格行数和列数
  43. private readonly GRID_ROWS = 6;
  44. private readonly GRID_COLS = 11;
  45. // 是否已初始化网格信息
  46. private gridInitialized = false;
  47. // 存储网格节点信息
  48. private gridNodes: Node[][] = [];
  49. // 网格间距
  50. private gridSpacing = 54;
  51. // 不参与占用的节点名称列表
  52. private readonly NON_BLOCK_NODES: string[] = ['Weapon', 'Price'];
  53. // 临时保存方块的原始占用格子
  54. private tempRemovedOccupiedGrids: { block: Node, occupiedGrids: { row: number, col: number }[] }[] = [];
  55. // 方块原始位置(在kuang中的位置)
  56. private originalPositions: Map<Node, Vec3> = new Map();
  57. // 方块当前所在的区域
  58. private blockLocations: Map<Node, string> = new Map();
  59. start() {
  60. // 如果没有指定GridContainer,尝试找到它
  61. if (!this.gridContainer) {
  62. this.gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer');
  63. if (!this.gridContainer) {
  64. console.error('找不到GridContainer节点');
  65. return;
  66. }
  67. }
  68. // 如果没有指定kuangContainer,尝试找到它
  69. if (!this.kuangContainer) {
  70. this.kuangContainer = find('Canvas/GameLevelUI/kuang');
  71. if (!this.kuangContainer) {
  72. console.error('找不到kuang节点');
  73. return;
  74. }
  75. }
  76. // 如果没有指定coinLabelNode,尝试找到它
  77. if (!this.coinLabelNode) {
  78. this.coinLabelNode = find('Canvas/GameLevelUI/CoinNode/CoinLabel');
  79. if (!this.coinLabelNode) {
  80. console.error('找不到CoinLabel节点');
  81. return;
  82. }
  83. }
  84. // 确保有PlacedBlocks节点用于存放已放置的方块
  85. this.ensurePlacedBlocksNode();
  86. // 初始化玩家金币显示
  87. this.updateCoinDisplay();
  88. // 初始化网格信息
  89. this.initGridInfo();
  90. // 初始化网格占用情况
  91. this.initGridOccupationMap();
  92. // 生成随机的三个块
  93. this.generateRandomBlocks();
  94. // 注册碰撞回调
  95. const collider = this.getComponent(Collider2D);
  96. if (collider) {
  97. collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  98. console.log('方块碰撞监听器已注册');
  99. }
  100. }
  101. // 确保有PlacedBlocks节点
  102. ensurePlacedBlocksNode() {
  103. // 查找Canvas节点
  104. const canvas = find('Canvas');
  105. if (!canvas) {
  106. console.error('找不到Canvas节点');
  107. return;
  108. }
  109. // 查找或创建PlacedBlocks节点
  110. let placedBlocksNode = find('Canvas/PlacedBlocks');
  111. if (!placedBlocksNode) {
  112. placedBlocksNode = new Node('PlacedBlocks');
  113. canvas.addChild(placedBlocksNode);
  114. // 确保PlacedBlocks节点有UITransform组件
  115. if (!placedBlocksNode.getComponent(UITransform)) {
  116. placedBlocksNode.addComponent(UITransform);
  117. }
  118. console.log('已创建PlacedBlocks节点');
  119. }
  120. }
  121. // 更新金币显示
  122. updateCoinDisplay() {
  123. if (this.coinLabelNode) {
  124. const label = this.coinLabelNode.getComponent(Label);
  125. if (label) {
  126. label.string = this.playerCoins.toString();
  127. }
  128. }
  129. }
  130. // 初始化网格信息
  131. initGridInfo() {
  132. if (!this.gridContainer || this.gridInitialized) return;
  133. // 创建二维数组存储网格节点
  134. this.gridNodes = [];
  135. for (let row = 0; row < this.GRID_ROWS; row++) {
  136. this.gridNodes[row] = [];
  137. }
  138. // 遍历所有网格节点,将它们放入二维数组
  139. for (let i = 0; i < this.gridContainer.children.length; i++) {
  140. const grid = this.gridContainer.children[i];
  141. if (grid.name.startsWith('Grid_')) {
  142. // 从节点名称解析行列信息,例如Grid_2_3表示第2行第3列
  143. const parts = grid.name.split('_');
  144. if (parts.length === 3) {
  145. const row = parseInt(parts[1]);
  146. const col = parseInt(parts[2]);
  147. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  148. this.gridNodes[row][col] = grid;
  149. }
  150. }
  151. }
  152. }
  153. // 计算网格间距
  154. if (this.GRID_ROWS > 1 && this.GRID_COLS > 0) {
  155. if (this.gridNodes[0][0] && this.gridNodes[1][0]) {
  156. const pos1 = this.gridNodes[0][0].position;
  157. const pos2 = this.gridNodes[1][0].position;
  158. this.gridSpacing = Math.abs(pos2.y - pos1.y);
  159. }
  160. }
  161. this.gridInitialized = true;
  162. }
  163. // 初始化网格占用情况
  164. initGridOccupationMap() {
  165. this.gridOccupationMap = [];
  166. for (let row = 0; row < this.GRID_ROWS; row++) {
  167. const rowArray: number[] = [];
  168. for (let col = 0; col < this.GRID_COLS; col++) {
  169. rowArray.push(0);
  170. }
  171. this.gridOccupationMap.push(rowArray);
  172. }
  173. }
  174. generateRandomBlocks() {
  175. // 如果游戏已开始,只清除kuang区域的块,否则清除所有块
  176. this.clearBlocks();
  177. // 检查是否有预制体可用
  178. if (this.blockPrefabs.length === 0) {
  179. console.error('没有可用的预制体');
  180. return;
  181. }
  182. // 获取kuang节点
  183. const kuangNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
  184. if (!kuangNode) {
  185. console.error('找不到kuang节点');
  186. return;
  187. }
  188. // 位置偏移量
  189. const offsets = [
  190. new Vec3(-200, 0, 0),
  191. new Vec3(0, 0, 0),
  192. new Vec3(200, 0, 0)
  193. ];
  194. // 获取kuang节点下的db01、db02、db03节点
  195. const dbNodes = [
  196. kuangNode.getChildByName('db01'),
  197. kuangNode.getChildByName('db02'),
  198. kuangNode.getChildByName('db03')
  199. ];
  200. // 生成三个随机块
  201. for (let i = 0; i < 3; i++) {
  202. // 随机选择一个预制体
  203. const randomIndex = Math.floor(Math.random() * this.blockPrefabs.length);
  204. const prefab = this.blockPrefabs[randomIndex];
  205. // 实例化预制体
  206. const block = instantiate(prefab);
  207. kuangNode.addChild(block); // 直接添加到kuang节点下
  208. // 设置位置
  209. block.position = offsets[i];
  210. // 保存原始位置
  211. this.originalPositions.set(block, offsets[i].clone());
  212. // 标记方块位置为kuang
  213. this.blockLocations.set(block, 'kuang');
  214. // 添加到块数组
  215. this.blocks.push(block);
  216. // 将对应的db节点与方块关联
  217. if (dbNodes[i]) {
  218. // 保存价格节点引用 (Price是db节点的子节点)
  219. const priceNode = dbNodes[i].getChildByName('Price');
  220. if (priceNode) {
  221. this.blockPriceMap.set(block, priceNode);
  222. priceNode.active = true;
  223. }
  224. // 将db节点作为方块的子节点
  225. this.associateDbNodeWithBlock(block, dbNodes[i]);
  226. }
  227. // 添加触摸事件
  228. this.setupDragEvents(block);
  229. }
  230. }
  231. // 将db节点与方块关联
  232. associateDbNodeWithBlock(block: Node, dbNode: Node) {
  233. // 记录db节点与方块的关联
  234. block['dbNode'] = dbNode;
  235. // 当方块移动时,db节点也跟随移动
  236. block.on(Node.EventType.TRANSFORM_CHANGED, () => {
  237. if (dbNode && block.parent) {
  238. // 检查方块当前位置
  239. const location = this.blockLocations.get(block);
  240. // 如果方块在网格中,不需要显示db节点
  241. if (location === 'grid') {
  242. dbNode.active = false;
  243. return;
  244. }
  245. // 确保db节点可见
  246. dbNode.active = true;
  247. // 获取方块在世界坐标系中的位置
  248. const worldPos = block.parent.getComponent(UITransform).convertToWorldSpaceAR(block.position);
  249. // 将世界坐标转换为db节点父节点的本地坐标
  250. const localPos = dbNode.parent.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
  251. // 设置db节点位置,放在方块下方
  252. dbNode.position = new Vec3(localPos.x, localPos.y - 80, localPos.z);
  253. }
  254. });
  255. }
  256. // 获取方块价格
  257. getBlockPrice(block: Node): number {
  258. const priceNode = this.blockPriceMap.get(block);
  259. if (priceNode) {
  260. const label = priceNode.getComponent(Label);
  261. if (label) {
  262. // 尝试解析价格
  263. const price = parseInt(label.string);
  264. if (!isNaN(price)) {
  265. return price;
  266. }
  267. }
  268. }
  269. // 默认价格
  270. return 50;
  271. }
  272. // 隐藏价格标签
  273. hidePriceLabel(block: Node) {
  274. const priceNode = this.blockPriceMap.get(block);
  275. if (priceNode) {
  276. priceNode.active = false;
  277. }
  278. }
  279. // 显示价格标签
  280. showPriceLabel(block: Node) {
  281. const priceNode = this.blockPriceMap.get(block);
  282. if (priceNode) {
  283. priceNode.active = true;
  284. }
  285. }
  286. // 扣除玩家金币
  287. deductPlayerCoins(amount: number): boolean {
  288. if (this.playerCoins >= amount) {
  289. this.playerCoins -= amount;
  290. this.updateCoinDisplay();
  291. return true;
  292. }
  293. return false;
  294. }
  295. setupDragEvents(block: Node) {
  296. // 添加触摸开始事件
  297. block.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  298. // 记录当前拖拽的块
  299. this.currentDragBlock = block;
  300. // 记录触摸起始位置
  301. this.startPos = event.getUILocation();
  302. // 记录块的起始位置
  303. this.blockStartPos.set(block.position);
  304. // 记录块的起始位置类型
  305. this.currentDragBlock['startLocation'] = this.blockLocations.get(block);
  306. // 将块置于顶层
  307. block.setSiblingIndex(block.parent.children.length - 1);
  308. // 临时保存方块占用的网格,而不是直接移除
  309. this.tempStoreBlockOccupiedGrids(block);
  310. }, this);
  311. // 添加触摸移动事件
  312. block.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
  313. if (!this.currentDragBlock) return;
  314. // 计算触摸点位移
  315. const location = event.getUILocation();
  316. const deltaX = location.x - this.startPos.x;
  317. const deltaY = location.y - this.startPos.y;
  318. // 更新块的位置
  319. this.currentDragBlock.position = new Vec3(
  320. this.blockStartPos.x + deltaX,
  321. this.blockStartPos.y + deltaY,
  322. this.blockStartPos.z
  323. );
  324. }, this);
  325. // 添加触摸结束事件
  326. block.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
  327. if (this.currentDragBlock) {
  328. // 获取当前触摸位置
  329. const touchPos = event.getUILocation();
  330. // 获取起始位置类型
  331. const startLocation = this.currentDragBlock['startLocation'];
  332. // 检查是否拖到了kuang区域
  333. if (this.isInKuangArea(touchPos)) {
  334. // 如果拖回kuang区域,恢复到原始位置
  335. const originalPos = this.originalPositions.get(this.currentDragBlock);
  336. if (originalPos) {
  337. // 确保方块在kuang节点下
  338. const kuangNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
  339. if (kuangNode && this.currentDragBlock.parent !== kuangNode) {
  340. // 将方块从当前父节点移除
  341. this.currentDragBlock.removeFromParent();
  342. // 添加到kuang节点下
  343. kuangNode.addChild(this.currentDragBlock);
  344. }
  345. // 设置位置
  346. this.currentDragBlock.position = originalPos.clone();
  347. }
  348. // 恢复方块原来的占用状态(如果有)
  349. this.restoreBlockOccupiedGrids(this.currentDragBlock);
  350. // 更新方块位置标记
  351. this.blockLocations.set(this.currentDragBlock, 'kuang');
  352. // 确保价格标签显示
  353. this.showPriceLabel(this.currentDragBlock);
  354. // 如果方块之前在网格中,需要恢复金币
  355. if (startLocation === 'grid') {
  356. // 获取方块价格
  357. const price = this.getBlockPrice(this.currentDragBlock);
  358. // 恢复金币
  359. this.playerCoins += price;
  360. // 更新金币显示
  361. this.updateCoinDisplay();
  362. // 重置已放置标记
  363. this.currentDragBlock['placedBefore'] = false;
  364. }
  365. // 确保db节点可见并回到正确位置
  366. const dbNode = this.currentDragBlock['dbNode'];
  367. if (dbNode) {
  368. dbNode.active = true;
  369. // 触发一次位置更新,确保db节点位置正确
  370. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  371. }
  372. } else if (this.tryPlaceBlockToGrid(this.currentDragBlock)) {
  373. // 获取方块价格
  374. const price = this.getBlockPrice(this.currentDragBlock);
  375. // 如果方块之前在网格中,不需要重复扣除金币
  376. if (startLocation === 'grid') {
  377. // 清除临时保存的占用状态
  378. this.clearTempStoredOccupiedGrids(this.currentDragBlock);
  379. // 更新方块位置标记
  380. this.blockLocations.set(this.currentDragBlock, 'grid');
  381. // 隐藏价格标签
  382. this.hidePriceLabel(this.currentDragBlock);
  383. // 隐藏db节点
  384. const dbNode = this.currentDragBlock['dbNode'];
  385. if (dbNode) {
  386. dbNode.active = false;
  387. }
  388. // 如果游戏已经开始,将方块移动到PlacedBlocks节点下
  389. if (this.gameStarted) {
  390. this.moveBlockToPlacedBlocks(this.currentDragBlock);
  391. }
  392. } else {
  393. // 尝试扣除金币
  394. if (this.deductPlayerCoins(price)) {
  395. // 扣除成功,清除临时保存的占用状态
  396. this.clearTempStoredOccupiedGrids(this.currentDragBlock);
  397. // 更新方块位置标记
  398. this.blockLocations.set(this.currentDragBlock, 'grid');
  399. // 隐藏价格标签
  400. this.hidePriceLabel(this.currentDragBlock);
  401. // 隐藏db节点
  402. const dbNode = this.currentDragBlock['dbNode'];
  403. if (dbNode) {
  404. dbNode.active = false;
  405. }
  406. // 标记方块已经放置过
  407. this.currentDragBlock['placedBefore'] = true;
  408. // 如果游戏已经开始,将方块移动到PlacedBlocks节点下
  409. if (this.gameStarted) {
  410. this.moveBlockToPlacedBlocks(this.currentDragBlock);
  411. }
  412. } else {
  413. // 金币不足,放置失败,返回原位
  414. const originalPos = this.originalPositions.get(this.currentDragBlock);
  415. if (originalPos) {
  416. this.currentDragBlock.position = originalPos.clone();
  417. }
  418. // 恢复方块原来的占用状态
  419. this.restoreBlockOccupiedGrids(this.currentDragBlock);
  420. // 确保价格标签显示
  421. this.showPriceLabel(this.currentDragBlock);
  422. // 确保db节点可见并回到正确位置
  423. const dbNode = this.currentDragBlock['dbNode'];
  424. if (dbNode) {
  425. dbNode.active = true;
  426. // 触发一次位置更新,确保db节点位置正确
  427. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  428. }
  429. }
  430. }
  431. } else {
  432. // 放置失败,返回原位
  433. const currentLocation = this.blockLocations.get(this.currentDragBlock);
  434. if (currentLocation === 'kuang') {
  435. // 如果之前在kuang中,返回kuang中的位置
  436. const originalPos = this.originalPositions.get(this.currentDragBlock);
  437. if (originalPos) {
  438. this.currentDragBlock.position = originalPos.clone();
  439. }
  440. } else {
  441. // 否则返回拖拽开始的位置
  442. this.currentDragBlock.position = this.blockStartPos.clone();
  443. }
  444. // 恢复方块原来的占用状态
  445. this.restoreBlockOccupiedGrids(this.currentDragBlock);
  446. // 确保价格标签显示
  447. this.showPriceLabel(this.currentDragBlock);
  448. // 确保db节点可见并回到正确位置
  449. const dbNode = this.currentDragBlock['dbNode'];
  450. if (dbNode) {
  451. dbNode.active = true;
  452. // 触发一次位置更新,确保db节点位置正确
  453. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  454. }
  455. }
  456. this.currentDragBlock = null;
  457. }
  458. }, this);
  459. // 添加触摸取消事件
  460. block.on(Node.EventType.TOUCH_CANCEL, () => {
  461. if (this.currentDragBlock) {
  462. // 获取起始位置类型
  463. const startLocation = this.currentDragBlock['startLocation'];
  464. // 触摸取消,返回原位
  465. this.currentDragBlock.position = this.blockStartPos.clone();
  466. // 如果当前在kuang区域,确保方块在kuang节点下
  467. if (this.blockLocations.get(this.currentDragBlock) === 'kuang') {
  468. const kuangNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
  469. if (kuangNode && this.currentDragBlock.parent !== kuangNode) {
  470. // 将方块从当前父节点移除
  471. this.currentDragBlock.removeFromParent();
  472. // 添加到kuang节点下
  473. kuangNode.addChild(this.currentDragBlock);
  474. }
  475. }
  476. // 恢复方块原来的占用状态
  477. this.restoreBlockOccupiedGrids(this.currentDragBlock);
  478. // 确保价格标签显示
  479. this.showPriceLabel(this.currentDragBlock);
  480. // 如果方块之前在网格中且现在在kuang区域,需要恢复金币
  481. if (startLocation === 'grid' &&
  482. this.blockLocations.get(this.currentDragBlock) === 'kuang') {
  483. // 获取方块价格
  484. const price = this.getBlockPrice(this.currentDragBlock);
  485. // 恢复金币
  486. this.playerCoins += price;
  487. // 更新金币显示
  488. this.updateCoinDisplay();
  489. // 重置已放置标记
  490. this.currentDragBlock['placedBefore'] = false;
  491. }
  492. // 确保db节点可见并回到正确位置
  493. const dbNode = this.currentDragBlock['dbNode'];
  494. if (dbNode) {
  495. dbNode.active = true;
  496. // 触发一次位置更新,确保db节点位置正确
  497. this.currentDragBlock.emit(Node.EventType.TRANSFORM_CHANGED);
  498. }
  499. this.currentDragBlock = null;
  500. }
  501. }, this);
  502. }
  503. // 检查是否在kuang区域内
  504. isInKuangArea(touchPos: Vec2): boolean {
  505. if (!this.kuangContainer) return false;
  506. // 获取kuang的世界矩形
  507. const kuangTransform = this.kuangContainer.getComponent(UITransform);
  508. if (!kuangTransform) return false;
  509. // 计算kuang的世界边界
  510. const kuangBoundingBox = new Rect(
  511. this.kuangContainer.worldPosition.x - kuangTransform.width * kuangTransform.anchorX,
  512. this.kuangContainer.worldPosition.y - kuangTransform.height * kuangTransform.anchorY,
  513. kuangTransform.width,
  514. kuangTransform.height
  515. );
  516. // 检查触摸点是否在kuang矩形内
  517. return kuangBoundingBox.contains(new Vec2(touchPos.x, touchPos.y));
  518. }
  519. // 临时保存方块占用的网格
  520. tempStoreBlockOccupiedGrids(block: Node) {
  521. // 获取方块占用的网格
  522. const occupiedGrids = block['occupiedGrids'];
  523. if (!occupiedGrids || occupiedGrids.length === 0) return;
  524. // 保存到临时数组
  525. this.tempRemovedOccupiedGrids.push({
  526. block: block,
  527. occupiedGrids: [...occupiedGrids] // 复制一份,避免引用问题
  528. });
  529. // 移除占用标记
  530. for (const grid of occupiedGrids) {
  531. if (grid.row >= 0 && grid.row < this.GRID_ROWS &&
  532. grid.col >= 0 && grid.col < this.GRID_COLS) {
  533. this.gridOccupationMap[grid.row][grid.col] = 0;
  534. }
  535. }
  536. // 清空方块的占用记录
  537. block['occupiedGrids'] = [];
  538. }
  539. // 恢复方块原来的占用状态
  540. restoreBlockOccupiedGrids(block: Node) {
  541. // 查找临时保存的占用状态
  542. const index = this.tempRemovedOccupiedGrids.findIndex(item => item.block === block);
  543. if (index === -1) return;
  544. const savedItem = this.tempRemovedOccupiedGrids[index];
  545. // 恢复占用标记
  546. for (const grid of savedItem.occupiedGrids) {
  547. if (grid.row >= 0 && grid.row < this.GRID_ROWS &&
  548. grid.col >= 0 && grid.col < this.GRID_COLS) {
  549. this.gridOccupationMap[grid.row][grid.col] = 1;
  550. }
  551. }
  552. // 恢复方块的占用记录
  553. block['occupiedGrids'] = [...savedItem.occupiedGrids];
  554. // 从临时数组中移除
  555. this.tempRemovedOccupiedGrids.splice(index, 1);
  556. }
  557. // 清除临时保存的占用状态
  558. clearTempStoredOccupiedGrids(block: Node) {
  559. // 查找临时保存的占用状态
  560. const index = this.tempRemovedOccupiedGrids.findIndex(item => item.block === block);
  561. if (index === -1) return;
  562. // 从临时数组中移除
  563. this.tempRemovedOccupiedGrids.splice(index, 1);
  564. }
  565. // 获取网格行列索引
  566. getGridRowCol(gridNode: Node): { row: number, col: number } | null {
  567. if (!gridNode || !gridNode.name.startsWith('Grid_')) return null;
  568. const parts = gridNode.name.split('_');
  569. if (parts.length === 3) {
  570. const row = parseInt(parts[1]);
  571. const col = parseInt(parts[2]);
  572. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  573. return { row, col };
  574. }
  575. }
  576. return null;
  577. }
  578. // 找到最近的网格节点
  579. findNearestGridNode(position: Vec3): Node {
  580. if (!this.gridContainer || !this.gridInitialized) return null;
  581. let nearestNode: Node = null;
  582. let minDistance = Number.MAX_VALUE;
  583. // 遍历所有网格节点
  584. for (let row = 0; row < this.GRID_ROWS; row++) {
  585. for (let col = 0; col < this.GRID_COLS; col++) {
  586. const grid = this.gridNodes[row][col];
  587. if (grid) {
  588. const distance = Vec3.distance(position, grid.position);
  589. if (distance < minDistance) {
  590. minDistance = distance;
  591. nearestNode = grid;
  592. }
  593. }
  594. }
  595. }
  596. // 增加容错性,放宽距离限制
  597. if (minDistance > this.gridSpacing * 2) {
  598. // 如果距离超过网格间距的4倍,可能是完全偏离了网格区域
  599. if (minDistance > this.gridSpacing * 4) {
  600. return null;
  601. }
  602. }
  603. return nearestNode;
  604. }
  605. // 尝试将方块放置到网格中
  606. tryPlaceBlockToGrid(block: Node): boolean {
  607. if (!this.gridContainer || !this.gridInitialized) return false;
  608. // 记录方块之前的位置
  609. const previousLocation = this.blockLocations.get(block);
  610. // 获取B1节点
  611. let b1Node = block;
  612. if (block.name !== 'B1') {
  613. // 查找B1节点
  614. b1Node = block.getChildByName('B1');
  615. if (!b1Node) {
  616. return false;
  617. }
  618. }
  619. // 获取方块B1节点在世界坐标中的位置
  620. const b1WorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position);
  621. // 将B1节点的世界坐标转换为GridContainer的本地坐标
  622. const gridPos = this.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(b1WorldPos);
  623. // 检查是否在GridContainer范围内
  624. const gridSize = this.gridContainer.getComponent(UITransform).contentSize;
  625. const halfWidth = gridSize.width / 2;
  626. const halfHeight = gridSize.height / 2;
  627. // 增加容错性,放宽边界检查
  628. const tolerance = this.gridSpacing * 0.5;
  629. if (gridPos.x < -halfWidth - tolerance || gridPos.x > halfWidth + tolerance ||
  630. gridPos.y < -halfHeight - tolerance || gridPos.y > halfHeight + tolerance) {
  631. return false;
  632. }
  633. // 找到最近的网格节点
  634. const nearestGrid = this.findNearestGridNode(gridPos);
  635. if (!nearestGrid) {
  636. // 尝试使用网格行列直接定位
  637. const row = Math.floor((gridPos.y + halfHeight) / this.gridSpacing);
  638. const col = Math.floor((gridPos.x + halfWidth) / this.gridSpacing);
  639. // 检查计算出的行列是否在有效范围内
  640. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  641. const grid = this.gridNodes[row][col];
  642. if (grid) {
  643. const result = this.tryPlaceBlockToSpecificGrid(block, grid);
  644. // 如果放置成功,处理db节点
  645. if (result) {
  646. const dbNode = block['dbNode'];
  647. if (dbNode) {
  648. // 隐藏db节点
  649. dbNode.active = false;
  650. }
  651. // 如果方块之前在网格中,不需要重复扣除金币
  652. if (previousLocation === 'grid') {
  653. block['placedBefore'] = true;
  654. }
  655. }
  656. return result;
  657. }
  658. }
  659. return false;
  660. }
  661. const result = this.tryPlaceBlockToSpecificGrid(block, nearestGrid);
  662. // 如果放置成功,处理db节点
  663. if (result) {
  664. const dbNode = block['dbNode'];
  665. if (dbNode) {
  666. // 隐藏db节点
  667. dbNode.active = false;
  668. }
  669. // 如果方块之前在网格中,不需要重复扣除金币
  670. if (previousLocation === 'grid') {
  671. block['placedBefore'] = true;
  672. }
  673. }
  674. return result;
  675. }
  676. // 尝试将方块放置到指定的网格节点
  677. tryPlaceBlockToSpecificGrid(block: Node, targetGrid: Node): boolean {
  678. // 获取B1节点
  679. let b1Node = block;
  680. if (block.name !== 'B1') {
  681. b1Node = block.getChildByName('B1');
  682. if (!b1Node) {
  683. return false;
  684. }
  685. }
  686. // 检查方块的所有部分是否会与已占用的格子重叠
  687. if (!this.canPlaceBlockAt(block, targetGrid)) {
  688. return false;
  689. }
  690. // 获取网格节点的中心点世界坐标
  691. const gridCenterWorldPos = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(targetGrid.position);
  692. // 计算B1节点应该移动到的位置
  693. const targetWorldPos = gridCenterWorldPos.clone();
  694. // 计算根节点需要移动的位置
  695. // 1. 先计算B1节点相对于根节点的偏移
  696. const b1LocalPos = b1Node.position.clone();
  697. // 2. 计算根节点的目标世界坐标
  698. let rootTargetWorldPos;
  699. if (b1Node === block) {
  700. rootTargetWorldPos = targetWorldPos.clone();
  701. } else {
  702. // 如果B1是子节点,需要计算根节点应该在的位置,使B1对准网格中心
  703. rootTargetWorldPos = new Vec3(
  704. targetWorldPos.x - b1LocalPos.x,
  705. targetWorldPos.y - b1LocalPos.y,
  706. targetWorldPos.z
  707. );
  708. }
  709. // 3. 将世界坐标转换为根节点父节点的本地坐标
  710. const rootTargetLocalPos = block.parent.getComponent(UITransform).convertToNodeSpaceAR(rootTargetWorldPos);
  711. // 设置方块位置,确保B1节点的中心与网格节点的中心对齐
  712. block.position = rootTargetLocalPos;
  713. // 标记占用的格子
  714. this.markOccupiedPositions(block, targetGrid);
  715. return true;
  716. }
  717. // 获取方块的所有部分节点及其相对坐标
  718. getBlockParts(block: Node): { node: Node, x: number, y: number }[] {
  719. const parts: { node: Node, x: number, y: number }[] = [];
  720. // 添加B1节点本身(根节点)
  721. parts.push({ node: block, x: 0, y: 0 });
  722. // 递归查找所有子节点
  723. this.findBlockParts(block, parts, 0, 0);
  724. return parts;
  725. }
  726. // 递归查找方块的所有部分
  727. findBlockParts(node: Node, result: { node: Node, x: number, y: number }[], parentX: number, parentY: number) {
  728. // 遍历所有子节点
  729. for (let i = 0; i < node.children.length; i++) {
  730. const child = node.children[i];
  731. // 跳过不参与占用的节点
  732. if (this.NON_BLOCK_NODES.indexOf(child.name) !== -1) {
  733. continue;
  734. }
  735. let x = parentX;
  736. let y = parentY;
  737. // 尝试从节点名称中解析坐标
  738. const match = child.name.match(/^\((-?\d+),(-?\d+)\)$/);
  739. if (match) {
  740. // 如果节点名称是坐标格式,直接使用
  741. x = parseInt(match[1]);
  742. y = parseInt(match[2]);
  743. result.push({ node: child, x, y });
  744. } else if (child.name.startsWith('B')) {
  745. // 如果是B开头的节点,使用其相对位置
  746. // 计算相对于父节点的位置,并转换为网格单位
  747. const relativeX = Math.round(child.position.x / this.gridSpacing);
  748. const relativeY = -Math.round(child.position.y / this.gridSpacing); // Y轴向下为正
  749. x = parentX + relativeX;
  750. y = parentY + relativeY;
  751. result.push({ node: child, x, y });
  752. }
  753. // 递归处理子节点
  754. this.findBlockParts(child, result, x, y);
  755. }
  756. }
  757. // 检查方块是否可以放置在指定位置
  758. canPlaceBlockAt(block: Node, targetGrid: Node): boolean {
  759. if (!this.gridInitialized) return false;
  760. // 获取目标网格的行列索引
  761. const targetRowCol = this.getGridRowCol(targetGrid);
  762. if (!targetRowCol) return false;
  763. // 获取方块的所有部分
  764. const parts = this.getBlockParts(block);
  765. // 检查每个部分是否与已占用的格子重叠
  766. for (const part of parts) {
  767. // 计算在网格中的行列位置
  768. const row = targetRowCol.row - part.y; // Y轴向下为正,所以是减法
  769. const col = targetRowCol.col + part.x; // X轴向右为正
  770. // 检查是否超出网格范围
  771. if (row < 0 || row >= this.GRID_ROWS || col < 0 || col >= this.GRID_COLS) {
  772. return false;
  773. }
  774. // 检查该位置是否已被占用
  775. if (this.gridOccupationMap[row][col] === 1) {
  776. return false;
  777. }
  778. }
  779. return true;
  780. }
  781. // 标记方块占用的格子
  782. markOccupiedPositions(block: Node, targetGrid: Node) {
  783. if (!this.gridInitialized) return;
  784. // 获取目标网格的行列索引
  785. const targetRowCol = this.getGridRowCol(targetGrid);
  786. if (!targetRowCol) return;
  787. // 获取方块的所有部分
  788. const parts = this.getBlockParts(block);
  789. // 清除之前的占用记录
  790. block['occupiedGrids'] = [];
  791. // 为每个部分标记占用位置
  792. for (const part of parts) {
  793. // 计算在网格中的行列位置
  794. const row = targetRowCol.row - part.y; // Y轴向下为正,所以是减法
  795. const col = targetRowCol.col + part.x; // X轴向右为正
  796. // 检查是否在有效范围内
  797. if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
  798. // 更新网格占用情况
  799. this.gridOccupationMap[row][col] = 1;
  800. // 存储方块与网格的关联
  801. block['occupiedGrids'] = block['occupiedGrids'] || [];
  802. block['occupiedGrids'].push({ row, col });
  803. }
  804. }
  805. }
  806. // 移除方块占用的格子
  807. removeBlockFromGrid(block: Node) {
  808. // 获取方块占用的网格
  809. const occupiedGrids = block['occupiedGrids'];
  810. if (!occupiedGrids) return;
  811. // 移除占用标记
  812. for (const grid of occupiedGrids) {
  813. if (grid.row >= 0 && grid.row < this.GRID_ROWS &&
  814. grid.col >= 0 && grid.col < this.GRID_COLS) {
  815. this.gridOccupationMap[grid.row][grid.col] = 0;
  816. }
  817. }
  818. // 清空方块的占用记录
  819. block['occupiedGrids'] = [];
  820. }
  821. clearBlocks() {
  822. // 只移除kuang区域的块,保留已放置在网格中的块
  823. const blocksToRemove = [];
  824. // 找出所有在kuang区域的块
  825. for (const block of this.blocks) {
  826. if (block.isValid) {
  827. const location = this.blockLocations.get(block);
  828. if (location === 'kuang') {
  829. blocksToRemove.push(block);
  830. }
  831. }
  832. }
  833. // 移除kuang区域的块
  834. for (const block of blocksToRemove) {
  835. // 如果有关联的db节点,恢复其位置
  836. const dbNode = block['dbNode'];
  837. if (dbNode && dbNode.isValid) {
  838. // 移除方块移动时的监听
  839. block.off(Node.EventType.TRANSFORM_CHANGED);
  840. // 恢复db节点到原始位置
  841. const kuangNode = find('Canvas/GameLevelUI/BlockSelectionUI/diban/kuang');
  842. if (kuangNode) {
  843. // 确保db节点回到原来的父节点下
  844. const dbName = dbNode.name;
  845. if (!kuangNode.getChildByName(dbName)) {
  846. dbNode.parent = kuangNode;
  847. }
  848. }
  849. }
  850. // 从blocks数组中移除
  851. const index = this.blocks.indexOf(block);
  852. if (index !== -1) {
  853. this.blocks.splice(index, 1);
  854. }
  855. // 清除相关映射
  856. this.originalPositions.delete(block);
  857. this.blockLocations.delete(block);
  858. this.blockPriceMap.delete(block);
  859. // 销毁方块
  860. block.destroy();
  861. }
  862. // 注意:不重置网格占用情况,保留已放置的方块占用信息
  863. }
  864. // 游戏开始时,确保已放置的方块正确显示
  865. onGameStart() {
  866. // 遍历所有方块
  867. for (const block of this.blocks) {
  868. if (block.isValid) {
  869. const location = this.blockLocations.get(block);
  870. // 如果方块在网格中,将其移动到PlacedBlocks节点下
  871. if (location === 'grid') {
  872. // 隐藏价格标签
  873. this.hidePriceLabel(block);
  874. // 隐藏db节点
  875. const dbNode = block['dbNode'];
  876. if (dbNode) {
  877. dbNode.active = false;
  878. }
  879. // 将方块移动到PlacedBlocks节点下
  880. this.moveBlockToPlacedBlocks(block);
  881. }
  882. }
  883. }
  884. // 设置游戏已开始标志
  885. this.gameStarted = true;
  886. }
  887. // 将方块移动到PlacedBlocks节点下
  888. moveBlockToPlacedBlocks(block: Node) {
  889. // 查找PlacedBlocks节点
  890. const placedBlocksNode = find('Canvas/PlacedBlocks');
  891. if (!placedBlocksNode) {
  892. console.error('找不到PlacedBlocks节点');
  893. return;
  894. }
  895. // 保存方块的世界位置
  896. const worldPosition = new Vec3();
  897. block.getWorldPosition(worldPosition);
  898. // 将方块从当前父节点移除
  899. block.removeFromParent();
  900. // 添加到PlacedBlocks节点下
  901. placedBlocksNode.addChild(block);
  902. // 设置方块的世界位置,确保在屏幕上的位置不变
  903. block.setWorldPosition(worldPosition);
  904. console.log(`已将方块 ${block.name} 移动到PlacedBlocks节点下`);
  905. }
  906. onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  907. console.log('方块碰到了:', otherCollider.node.name);
  908. // 如果碰到球,可以在这里发射子弹
  909. if (otherCollider.node.name === 'Ball') {
  910. console.log('方块被球击中!');
  911. // 这里可以调用发射子弹的逻辑
  912. }
  913. }
  914. }