BlockManager.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. import { _decorator, Component, Node, Prefab, instantiate, Vec3, EventTouch, Vec2, UITransform, find, BoxCollider2D, Collider2D, Contact2DType, IPhysics2DContact, Intersection2D, Rect, PhysicsSystem2D } 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. // 是否启用物理碰撞检测
  15. @property({
  16. tooltip: '是否启用物理碰撞检测'
  17. })
  18. public usePhysicsCollision: boolean = true;
  19. // 已经生成的块
  20. private blocks: Node[] = [];
  21. // 当前拖拽的块
  22. private currentDragBlock: Node | null = null;
  23. // 拖拽起始位置
  24. private startPos = new Vec2();
  25. // 块的起始位置
  26. private blockStartPos: Vec3 = new Vec3();
  27. // 已占用的网格位置
  28. private occupiedPositions: Set<string> = new Set();
  29. start() {
  30. // 如果没有指定GridContainer,尝试找到它
  31. if (!this.gridContainer) {
  32. this.gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer');
  33. if (!this.gridContainer) {
  34. console.error('找不到GridContainer节点');
  35. }
  36. }
  37. // 启用物理系统
  38. if (this.usePhysicsCollision) {
  39. PhysicsSystem2D.instance.enable = true;
  40. }
  41. // 生成随机的三个块
  42. this.generateRandomBlocks();
  43. }
  44. generateRandomBlocks() {
  45. // 清除现有的块
  46. this.clearBlocks();
  47. // 检查是否有预制体可用
  48. if (this.blockPrefabs.length === 0) {
  49. console.error('没有可用的预制体');
  50. return;
  51. }
  52. // 位置偏移量
  53. const offsets = [
  54. new Vec3(-200, 0, 0),
  55. new Vec3(0, 0, 0),
  56. new Vec3(200, 0, 0)
  57. ];
  58. // 生成三个随机块
  59. for (let i = 0; i < 3; i++) {
  60. // 随机选择一个预制体
  61. const randomIndex = Math.floor(Math.random() * this.blockPrefabs.length);
  62. const prefab = this.blockPrefabs[randomIndex];
  63. // 实例化预制体
  64. const block = instantiate(prefab);
  65. this.node.addChild(block);
  66. // 设置位置
  67. block.position = offsets[i];
  68. // 添加到块数组
  69. this.blocks.push(block);
  70. // 添加碰撞组件
  71. if (this.usePhysicsCollision) {
  72. this.addColliders(block);
  73. }
  74. // 添加触摸事件
  75. this.setupDragEvents(block);
  76. }
  77. }
  78. // 为方块的B1、B2、B3等节点添加碰撞组件
  79. addColliders(block: Node) {
  80. const blockParts = this.getBlockParts(block);
  81. for (const part of blockParts) {
  82. // 检查是否已有碰撞组件
  83. let collider = part.getComponent(BoxCollider2D);
  84. if (!collider) {
  85. collider = part.addComponent(BoxCollider2D);
  86. }
  87. // 设置碰撞组件属性
  88. const uiTransform = part.getComponent(UITransform);
  89. if (uiTransform) {
  90. collider.size.width = uiTransform.contentSize.width * 0.8; // 稍微小一点,避免边缘碰撞
  91. collider.size.height = uiTransform.contentSize.height * 0.8;
  92. } else {
  93. collider.size.width = 40; // 默认大小
  94. collider.size.height = 40;
  95. }
  96. // 设置碰撞组和掩码
  97. collider.group = 1 << 0; // 默认组
  98. collider.sensor = true; // 设为传感器,不影响物理行为
  99. // 添加碰撞回调
  100. collider.on(Contact2DType.BEGIN_CONTACT, this.onCollisionEnter, this);
  101. }
  102. }
  103. // 碰撞开始回调
  104. onCollisionEnter(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  105. // 如果当前正在拖拽方块,标记碰撞状态
  106. if (this.currentDragBlock) {
  107. const block = this.findBlockByPart(selfCollider.node);
  108. if (block === this.currentDragBlock) {
  109. block['isColliding'] = true;
  110. }
  111. }
  112. }
  113. // 根据部件节点找到所属的方块
  114. findBlockByPart(partNode: Node): Node | null {
  115. for (const block of this.blocks) {
  116. const blockParts = this.getBlockParts(block);
  117. for (const part of blockParts) {
  118. if (part === partNode) {
  119. return block;
  120. }
  121. }
  122. }
  123. return null;
  124. }
  125. setupDragEvents(block: Node) {
  126. // 添加触摸开始事件
  127. block.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  128. // 记录当前拖拽的块
  129. this.currentDragBlock = block;
  130. // 记录触摸起始位置
  131. this.startPos = event.getUILocation();
  132. // 记录块的起始位置
  133. this.blockStartPos.set(block.position);
  134. // 将块置于顶层
  135. block.setSiblingIndex(this.node.children.length - 1);
  136. // 如果方块已经放置在网格中,移除占用状态
  137. this.removeBlockFromGrid(block);
  138. // 重置碰撞状态
  139. block['isColliding'] = false;
  140. }, this);
  141. // 添加触摸移动事件
  142. block.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
  143. if (!this.currentDragBlock) return;
  144. // 计算触摸点位移
  145. const location = event.getUILocation();
  146. const deltaX = location.x - this.startPos.x;
  147. const deltaY = location.y - this.startPos.y;
  148. // 更新块的位置
  149. this.currentDragBlock.position = new Vec3(
  150. this.blockStartPos.x + deltaX,
  151. this.blockStartPos.y + deltaY,
  152. this.blockStartPos.z
  153. );
  154. }, this);
  155. // 添加触摸结束事件
  156. block.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
  157. if (this.currentDragBlock) {
  158. // 检查是否有碰撞
  159. if (this.usePhysicsCollision && this.currentDragBlock['isColliding']) {
  160. // 有碰撞,返回原位
  161. this.currentDragBlock.position = this.blockStartPos.clone();
  162. console.log('方块放置失败:与其他方块碰撞');
  163. } else {
  164. // 尝试将方块放置到网格中
  165. if (!this.tryPlaceBlockToGrid(this.currentDragBlock)) {
  166. // 放置失败,返回原位
  167. this.currentDragBlock.position = this.blockStartPos.clone();
  168. }
  169. }
  170. this.currentDragBlock = null;
  171. }
  172. }, this);
  173. // 添加触摸取消事件
  174. block.on(Node.EventType.TOUCH_CANCEL, () => {
  175. if (this.currentDragBlock) {
  176. // 触摸取消,返回原位
  177. this.currentDragBlock.position = this.blockStartPos.clone();
  178. this.currentDragBlock = null;
  179. }
  180. }, this);
  181. }
  182. // 尝试将方块放置到网格中
  183. tryPlaceBlockToGrid(block: Node): boolean {
  184. if (!this.gridContainer) return false;
  185. // 找到B1节点(吸附点)
  186. const b1Node = this.findB1Node(block);
  187. if (!b1Node) return false;
  188. // 获取B1节点在世界坐标中的位置
  189. const blockWorldPos = block.parent.getComponent(UITransform).convertToWorldSpaceAR(block.position);
  190. const b1WorldPos = new Vec3(
  191. blockWorldPos.x + b1Node.position.x,
  192. blockWorldPos.y + b1Node.position.y,
  193. blockWorldPos.z
  194. );
  195. // 将B1节点的世界坐标转换为GridContainer的本地坐标
  196. const gridPos = this.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(b1WorldPos);
  197. // 检查是否在GridContainer范围内
  198. const gridSize = this.gridContainer.getComponent(UITransform).contentSize;
  199. const halfWidth = gridSize.width / 2;
  200. const halfHeight = gridSize.height / 2;
  201. if (gridPos.x < -halfWidth || gridPos.x > halfWidth ||
  202. gridPos.y < -halfHeight || gridPos.y > halfHeight) {
  203. console.log('方块不在GridContainer范围内');
  204. return false;
  205. }
  206. // 找到最近的网格节点
  207. const nearestGrid = this.findNearestGridNode(gridPos);
  208. if (!nearestGrid) {
  209. console.log('找不到合适的网格节点');
  210. return false;
  211. }
  212. // 检查方块的所有部分是否会与已占用的格子重叠
  213. if (!this.canPlaceBlockAt(block, nearestGrid)) {
  214. console.log('方块放置位置已被占用或超出边界');
  215. return false;
  216. }
  217. // 计算方块应该移动到的位置,使B1对齐到网格
  218. const targetWorldPos = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(nearestGrid.position);
  219. const targetPos = block.parent.getComponent(UITransform).convertToNodeSpaceAR(targetWorldPos);
  220. // 设置方块位置
  221. block.position = new Vec3(
  222. targetPos.x - b1Node.position.x,
  223. targetPos.y - b1Node.position.y,
  224. block.position.z
  225. );
  226. // 标记占用的格子
  227. this.markOccupiedPositions(block, nearestGrid);
  228. console.log('方块放置成功');
  229. return true;
  230. }
  231. // 检查两个方块是否碰撞(使用几何碰撞检测)
  232. checkBlocksCollision(block1: Node, block2: Node): boolean {
  233. if (block1 === block2) return false;
  234. const parts1 = this.getBlockParts(block1);
  235. const parts2 = this.getBlockParts(block2);
  236. // 检查每个部分之间的碰撞
  237. for (const part1 of parts1) {
  238. const rect1 = this.getNodeRect(part1);
  239. for (const part2 of parts2) {
  240. const rect2 = this.getNodeRect(part2);
  241. // 检查矩形是否相交
  242. if (Intersection2D.rectRect(rect1, rect2)) {
  243. return true;
  244. }
  245. }
  246. }
  247. return false;
  248. }
  249. // 获取节点的世界矩形
  250. getNodeRect(node: Node): Rect {
  251. const uiTransform = node.getComponent(UITransform);
  252. if (!uiTransform) {
  253. return new Rect(0, 0, 0, 0);
  254. }
  255. const size = uiTransform.contentSize;
  256. const worldPos = node.parent.getComponent(UITransform).convertToWorldSpaceAR(node.position);
  257. return new Rect(
  258. worldPos.x - size.width * 0.5,
  259. worldPos.y - size.height * 0.5,
  260. size.width,
  261. size.height
  262. );
  263. }
  264. // 找到B1节点
  265. findB1Node(block: Node): Node {
  266. for (let i = 0; i < block.children.length; i++) {
  267. const child = block.children[i];
  268. if (child.name === 'B1') {
  269. return child;
  270. }
  271. }
  272. return null;
  273. }
  274. // 找到最近的网格节点
  275. findNearestGridNode(position: Vec3): Node {
  276. if (!this.gridContainer) return null;
  277. let nearestNode: Node = null;
  278. let minDistance = Number.MAX_VALUE;
  279. // 遍历所有网格节点
  280. for (let i = 0; i < this.gridContainer.children.length; i++) {
  281. const grid = this.gridContainer.children[i];
  282. if (grid.name.startsWith('Grid_')) {
  283. const distance = Vec3.distance(position, grid.position);
  284. if (distance < minDistance) {
  285. minDistance = distance;
  286. nearestNode = grid;
  287. }
  288. }
  289. }
  290. // 如果距离太远(超过网格大小的一半),不吸附
  291. if (minDistance > 24) { // 假设网格大小为48x48
  292. return null;
  293. }
  294. return nearestNode;
  295. }
  296. // 检查方块是否可以放置在指定位置
  297. canPlaceBlockAt(block: Node, targetGrid: Node): boolean {
  298. // 获取方块的所有部分(B1, B2, B3...)
  299. const blockParts = this.getBlockParts(block);
  300. // 找到B1节点
  301. const b1Node = this.findB1Node(block);
  302. if (!b1Node) return false;
  303. // 计算B1与目标网格的偏移
  304. const offsetX = targetGrid.position.x - b1Node.position.x;
  305. const offsetY = targetGrid.position.y - b1Node.position.y;
  306. // 检查每个部分是否与已占用的格子重叠
  307. for (const part of blockParts) {
  308. // 计算部分在网格中的位置
  309. const gridX = Math.round(offsetX + part.position.x);
  310. const gridY = Math.round(offsetY + part.position.y);
  311. const posKey = `${gridX},${gridY}`;
  312. // 检查该位置是否已被占用
  313. if (this.occupiedPositions.has(posKey)) {
  314. return false;
  315. }
  316. // 检查是否超出GridContainer边界
  317. const gridNode = this.findGridAtPosition(gridX, gridY);
  318. if (!gridNode) {
  319. return false;
  320. }
  321. }
  322. return true;
  323. }
  324. // 根据坐标找到网格节点
  325. findGridAtPosition(x: number, y: number): Node {
  326. if (!this.gridContainer) return null;
  327. for (let i = 0; i < this.gridContainer.children.length; i++) {
  328. const grid = this.gridContainer.children[i];
  329. if (grid.name.startsWith('Grid_')) {
  330. if (Math.abs(grid.position.x - x) < 1 && Math.abs(grid.position.y - y) < 1) {
  331. return grid;
  332. }
  333. }
  334. }
  335. return null;
  336. }
  337. // 获取方块的所有部分(B1, B2, B3...)
  338. getBlockParts(block: Node): Node[] {
  339. const parts: Node[] = [];
  340. for (let i = 0; i < block.children.length; i++) {
  341. const child = block.children[i];
  342. if (child.name.startsWith('B') && /^B\d+$/.test(child.name)) {
  343. parts.push(child);
  344. }
  345. }
  346. return parts;
  347. }
  348. // 标记方块占用的格子
  349. markOccupiedPositions(block: Node, targetGrid: Node) {
  350. // 获取方块的所有部分
  351. const blockParts = this.getBlockParts(block);
  352. // 找到B1节点
  353. const b1Node = this.findB1Node(block);
  354. if (!b1Node) return;
  355. // 计算B1与目标网格的偏移
  356. const offsetX = targetGrid.position.x - b1Node.position.x;
  357. const offsetY = targetGrid.position.y - b1Node.position.y;
  358. // 为每个部分标记占用位置
  359. for (const part of blockParts) {
  360. // 计算部分在网格中的位置
  361. const gridX = Math.round(offsetX + part.position.x);
  362. const gridY = Math.round(offsetY + part.position.y);
  363. const posKey = `${gridX},${gridY}`;
  364. // 记录占用信息
  365. this.occupiedPositions.add(posKey);
  366. // 存储方块与位置的关联
  367. block['occupiedPositions'] = block['occupiedPositions'] || [];
  368. block['occupiedPositions'].push(posKey);
  369. }
  370. }
  371. // 移除方块占用的格子
  372. removeBlockFromGrid(block: Node) {
  373. // 获取方块占用的位置
  374. const occupiedPositions = block['occupiedPositions'];
  375. if (!occupiedPositions) return;
  376. // 移除占用标记
  377. for (const posKey of occupiedPositions) {
  378. this.occupiedPositions.delete(posKey);
  379. }
  380. // 清空方块的占用记录
  381. block['occupiedPositions'] = [];
  382. }
  383. clearBlocks() {
  384. // 移除所有已经生成的块
  385. for (const block of this.blocks) {
  386. if (block.isValid) {
  387. // 移除占用的格子
  388. this.removeBlockFromGrid(block);
  389. // 销毁方块
  390. block.destroy();
  391. }
  392. }
  393. this.blocks = [];
  394. // 清空占用位置
  395. this.occupiedPositions.clear();
  396. }
  397. }