BlockManager.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. import { _decorator, Component, Node, Prefab, instantiate, Vec3, EventTouch, Vec2, UITransform, find } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. // 格子信息接口
  4. interface GridInfo {
  5. node: Node;
  6. position: Vec3;
  7. isOccupied: boolean;
  8. }
  9. @ccclass('BlockManager')
  10. export class BlockManager extends Component {
  11. // 预制体数组,存储5个预制体
  12. @property([Prefab])
  13. public blockPrefabs: Prefab[] = [];
  14. // 网格容器节点
  15. @property({
  16. type: Node,
  17. tooltip: '拖拽GridContainer节点到这里'
  18. })
  19. public gridContainer: Node = null;
  20. // 网格大小
  21. @property
  22. public gridSize: number = 48;
  23. // 已经生成的块
  24. private blocks: Node[] = [];
  25. // 当前拖拽的块
  26. private currentDragBlock: Node | null = null;
  27. // 拖拽起始位置
  28. private startPos = new Vec2();
  29. // 块的起始位置
  30. private blockStartPos: Vec3 = new Vec3();
  31. // 网格信息
  32. private grids: GridInfo[] = [];
  33. // 已放置的方块占用的格子
  34. private occupiedGrids: Set<string> = new Set();
  35. start() {
  36. // 初始化网格信息
  37. this.initGrids();
  38. // 生成随机的三个块
  39. this.generateRandomBlocks();
  40. }
  41. // 初始化网格信息
  42. initGrids() {
  43. if (!this.gridContainer) {
  44. this.gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer');
  45. if (!this.gridContainer) {
  46. console.error('找不到GridContainer节点');
  47. return;
  48. }
  49. }
  50. // 清空网格信息
  51. this.grids = [];
  52. // 遍历GridContainer的子节点,获取所有格子信息
  53. for (let i = 0; i < this.gridContainer.children.length; i++) {
  54. const grid = this.gridContainer.children[i];
  55. if (grid.name.startsWith('Grid_')) {
  56. this.grids.push({
  57. node: grid,
  58. position: grid.position.clone(),
  59. isOccupied: false
  60. });
  61. }
  62. }
  63. console.log(`初始化了 ${this.grids.length} 个网格`);
  64. }
  65. generateRandomBlocks() {
  66. // 清除现有的块
  67. this.clearBlocks();
  68. // 检查是否有预制体可用
  69. if (this.blockPrefabs.length === 0) {
  70. console.error('没有可用的预制体');
  71. return;
  72. }
  73. // 位置偏移量
  74. const offsets = [
  75. new Vec3(-200, 0, 0),
  76. new Vec3(0, 0, 0),
  77. new Vec3(200, 0, 0)
  78. ];
  79. // 生成三个随机块
  80. for (let i = 0; i < 3; i++) {
  81. // 随机选择一个预制体
  82. const randomIndex = Math.floor(Math.random() * this.blockPrefabs.length);
  83. const prefab = this.blockPrefabs[randomIndex];
  84. // 实例化预制体
  85. const block = instantiate(prefab);
  86. this.node.addChild(block);
  87. // 设置位置
  88. block.position = offsets[i];
  89. // 添加到块数组
  90. this.blocks.push(block);
  91. // 添加触摸事件
  92. this.setupDragEvents(block);
  93. }
  94. }
  95. setupDragEvents(block: Node) {
  96. // 添加触摸开始事件
  97. block.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  98. // 记录当前拖拽的块
  99. this.currentDragBlock = block;
  100. // 记录触摸起始位置
  101. this.startPos = event.getUILocation();
  102. // 记录块的起始位置
  103. this.blockStartPos.set(block.position);
  104. // 将块置于顶层
  105. block.setSiblingIndex(this.node.children.length - 1);
  106. // 如果方块已经放置在网格中,则移除占用状态
  107. this.removeBlockFromGrid(block);
  108. }, this);
  109. // 添加触摸移动事件
  110. block.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
  111. if (!this.currentDragBlock) return;
  112. // 计算触摸点位移
  113. const location = event.getUILocation();
  114. const deltaX = location.x - this.startPos.x;
  115. const deltaY = location.y - this.startPos.y;
  116. // 更新块的位置
  117. this.currentDragBlock.position = new Vec3(
  118. this.blockStartPos.x + deltaX,
  119. this.blockStartPos.y + deltaY,
  120. this.blockStartPos.z
  121. );
  122. // 显示吸附预览
  123. this.showSnapPreview(this.currentDragBlock);
  124. }, this);
  125. // 添加触摸结束事件
  126. block.on(Node.EventType.TOUCH_END, () => {
  127. if (this.currentDragBlock) {
  128. // 尝试将方块吸附到网格
  129. this.snapBlockToGrid(this.currentDragBlock);
  130. this.currentDragBlock = null;
  131. }
  132. }, this);
  133. // 添加触摸取消事件
  134. block.on(Node.EventType.TOUCH_CANCEL, () => {
  135. if (this.currentDragBlock) {
  136. // 尝试将方块吸附到网格
  137. this.snapBlockToGrid(this.currentDragBlock);
  138. this.currentDragBlock = null;
  139. }
  140. }, this);
  141. }
  142. // 显示吸附预览
  143. showSnapPreview(block: Node) {
  144. // 找到B1节点(吸附点)
  145. const b1Node = this.findB1Node(block);
  146. if (!b1Node) return;
  147. // 计算B1节点在世界坐标系中的位置
  148. const blockWorldPos = this.node.getComponent(UITransform).convertToWorldSpaceAR(block.position);
  149. const b1LocalPos = b1Node.position;
  150. const b1WorldPos = new Vec3(
  151. blockWorldPos.x + b1LocalPos.x,
  152. blockWorldPos.y + b1LocalPos.y,
  153. blockWorldPos.z + b1LocalPos.z
  154. );
  155. // 将B1节点的世界坐标转换为网格容器的本地坐标
  156. const gridContainerPos = this.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(b1WorldPos);
  157. // 找到最近的网格
  158. const nearestGrid = this.findNearestGrid(gridContainerPos);
  159. if (nearestGrid && this.canPlaceBlockAt(block, nearestGrid)) {
  160. // 可以显示预览效果,例如高亮网格等
  161. }
  162. }
  163. // 将方块吸附到网格
  164. snapBlockToGrid(block: Node) {
  165. // 找到B1节点(吸附点)
  166. const b1Node = this.findB1Node(block);
  167. if (!b1Node) return;
  168. // 计算B1节点在世界坐标系中的位置
  169. const blockWorldPos = this.node.getComponent(UITransform).convertToWorldSpaceAR(block.position);
  170. const b1LocalPos = b1Node.position;
  171. const b1WorldPos = new Vec3(
  172. blockWorldPos.x + b1LocalPos.x,
  173. blockWorldPos.y + b1LocalPos.y,
  174. blockWorldPos.z + b1LocalPos.z
  175. );
  176. // 将B1节点的世界坐标转换为网格容器的本地坐标
  177. const gridContainerPos = this.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(b1WorldPos);
  178. // 找到最近的网格
  179. const nearestGrid = this.findNearestGrid(gridContainerPos);
  180. if (!nearestGrid) return;
  181. // 检查是否可以放置
  182. if (!this.canPlaceBlockAt(block, nearestGrid)) {
  183. // 不能放置,恢复到原始位置
  184. block.position = this.blockStartPos.clone();
  185. return;
  186. }
  187. // 计算方块应该移动到的位置
  188. const targetWorldPos = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(nearestGrid.position);
  189. const targetNodePos = this.node.getComponent(UITransform).convertToNodeSpaceAR(targetWorldPos);
  190. // 设置方块位置,使B1节点对齐到网格
  191. block.position = new Vec3(
  192. targetNodePos.x - b1LocalPos.x,
  193. targetNodePos.y - b1LocalPos.y,
  194. block.position.z
  195. );
  196. // 标记占用的格子
  197. this.markOccupiedGrids(block, nearestGrid);
  198. }
  199. // 找到B1节点
  200. findB1Node(block: Node): Node {
  201. // 遍历子节点,查找名为B1的节点
  202. for (let i = 0; i < block.children.length; i++) {
  203. const child = block.children[i];
  204. if (child.name === 'B1') {
  205. return child;
  206. }
  207. }
  208. return null;
  209. }
  210. // 找到最近的网格
  211. findNearestGrid(position: Vec3): GridInfo {
  212. let nearestGrid: GridInfo = null;
  213. let minDistance = Number.MAX_VALUE;
  214. for (const grid of this.grids) {
  215. const distance = Vec3.distance(position, grid.position);
  216. if (distance < minDistance && !grid.isOccupied) {
  217. minDistance = distance;
  218. nearestGrid = grid;
  219. }
  220. }
  221. // 如果距离太远,不吸附
  222. if (minDistance > this.gridSize / 2) {
  223. return null;
  224. }
  225. return nearestGrid;
  226. }
  227. // 检查方块是否可以放置在指定网格
  228. canPlaceBlockAt(block: Node, grid: GridInfo): boolean {
  229. // 获取方块的所有子节点(B1, B2, B3...)
  230. const blockParts = this.getBlockParts(block);
  231. // 假设B1已经对齐到目标网格,计算其他部分的位置
  232. const b1Node = this.findB1Node(block);
  233. if (!b1Node) return false;
  234. // 计算方块应该移动到的位置
  235. const targetWorldPos = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(grid.position);
  236. const targetNodePos = this.node.getComponent(UITransform).convertToNodeSpaceAR(targetWorldPos);
  237. // 计算方块的新位置
  238. const newBlockPos = new Vec3(
  239. targetNodePos.x - b1Node.position.x,
  240. targetNodePos.y - b1Node.position.y,
  241. block.position.z
  242. );
  243. // 检查每个部分是否会与已占用的格子重叠
  244. for (const part of blockParts) {
  245. if (part === b1Node) continue; // B1已经对齐到目标网格
  246. // 计算部分在新位置下的世界坐标
  247. const partWorldPos = new Vec3(
  248. newBlockPos.x + part.position.x,
  249. newBlockPos.y + part.position.y,
  250. newBlockPos.z + part.position.z
  251. );
  252. const partWorldPosAR = this.node.getComponent(UITransform).convertToWorldSpaceAR(partWorldPos);
  253. const gridContainerPos = this.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(partWorldPosAR);
  254. // 找到该部分对应的网格
  255. const partGrid = this.findExactGrid(gridContainerPos);
  256. if (!partGrid) {
  257. // 超出网格范围
  258. return false;
  259. }
  260. // 检查该网格是否已被占用
  261. if (partGrid.isOccupied) {
  262. return false;
  263. }
  264. }
  265. return true;
  266. }
  267. // 找到精确匹配的网格
  268. findExactGrid(position: Vec3): GridInfo {
  269. for (const grid of this.grids) {
  270. const distance = Vec3.distance(position, grid.position);
  271. if (distance < this.gridSize / 2) {
  272. return grid;
  273. }
  274. }
  275. return null;
  276. }
  277. // 获取方块的所有部分(B1, B2, B3...)
  278. getBlockParts(block: Node): Node[] {
  279. const parts: Node[] = [];
  280. // 遍历子节点,查找名称以B开头的节点
  281. for (let i = 0; i < block.children.length; i++) {
  282. const child = block.children[i];
  283. if (child.name.startsWith('B') && child.name.length > 1) {
  284. parts.push(child);
  285. }
  286. }
  287. return parts;
  288. }
  289. // 标记方块占用的格子
  290. markOccupiedGrids(block: Node, mainGrid: GridInfo) {
  291. // 获取方块的所有部分
  292. const blockParts = this.getBlockParts(block);
  293. // 为每个部分找到对应的网格并标记为已占用
  294. for (const part of blockParts) {
  295. // 计算部分在世界坐标系中的位置
  296. const blockWorldPos = this.node.getComponent(UITransform).convertToWorldSpaceAR(block.position);
  297. const partLocalPos = part.position;
  298. const partWorldPos = new Vec3(
  299. blockWorldPos.x + partLocalPos.x,
  300. blockWorldPos.y + partLocalPos.y,
  301. blockWorldPos.z + partLocalPos.z
  302. );
  303. // 将部分的世界坐标转换为网格容器的本地坐标
  304. const gridContainerPos = this.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(partWorldPos);
  305. // 找到该部分对应的网格
  306. const grid = this.findExactGrid(gridContainerPos);
  307. if (grid) {
  308. grid.isOccupied = true;
  309. // 记录占用信息,格式为"blockID:gridID"
  310. this.occupiedGrids.add(`${block.uuid}:${grid.node.uuid}`);
  311. }
  312. }
  313. }
  314. // 移除方块占用的格子
  315. removeBlockFromGrid(block: Node) {
  316. // 遍历所有占用记录,找到与当前方块相关的记录
  317. const toRemove: string[] = [];
  318. this.occupiedGrids.forEach(record => {
  319. if (record.startsWith(`${block.uuid}:`)) {
  320. const gridId = record.split(':')[1];
  321. // 找到对应的网格并标记为未占用
  322. for (const grid of this.grids) {
  323. if (grid.node.uuid === gridId) {
  324. grid.isOccupied = false;
  325. break;
  326. }
  327. }
  328. toRemove.push(record);
  329. }
  330. });
  331. // 移除记录
  332. toRemove.forEach(record => {
  333. this.occupiedGrids.delete(record);
  334. });
  335. }
  336. clearBlocks() {
  337. // 移除所有已经生成的块
  338. for (const block of this.blocks) {
  339. if (block.isValid) {
  340. // 移除占用的格子
  341. this.removeBlockFromGrid(block);
  342. // 销毁方块
  343. block.destroy();
  344. }
  345. }
  346. this.blocks = [];
  347. // 重置所有网格状态
  348. for (const grid of this.grids) {
  349. grid.isOccupied = false;
  350. }
  351. this.occupiedGrids.clear();
  352. }
  353. }