import { _decorator, Component, Node, Prefab, instantiate, Vec3, EventTouch, Vec2, UITransform, find } from 'cc'; const { ccclass, property } = _decorator; @ccclass('BlockManager') export class BlockManager extends Component { // 预制体数组,存储5个预制体 @property([Prefab]) public blockPrefabs: Prefab[] = []; // 网格容器节点 @property({ type: Node, tooltip: '拖拽GridContainer节点到这里' }) public gridContainer: Node = null; // 已经生成的块 private blocks: Node[] = []; // 当前拖拽的块 private currentDragBlock: Node | null = null; // 拖拽起始位置 private startPos = new Vec2(); // 块的起始位置 private blockStartPos: Vec3 = new Vec3(); // 网格占用情况,用于控制台输出 private gridOccupationMap: number[][] = []; // 网格行数和列数 private readonly GRID_ROWS = 6; private readonly GRID_COLS = 11; // 是否已初始化网格信息 private gridInitialized = false; // 存储网格节点信息 private gridNodes: Node[][] = []; // 网格间距 private gridSpacing = 54; // 不参与占用的节点名称列表 private readonly NON_BLOCK_NODES: string[] = ['Weapon']; // 调试模式 private readonly DEBUG_MODE = true; // 网格偏移修正 private readonly GRID_OFFSET_X = 0; private readonly GRID_OFFSET_Y = 0; // 添加一个新属性来保存方块的原始占用格子 private tempRemovedOccupiedGrids: { block: Node, occupiedGrids: { row: number, col: number }[] }[] = []; start() { // 如果没有指定GridContainer,尝试找到它 if (!this.gridContainer) { this.gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer'); if (!this.gridContainer) { console.error('找不到GridContainer节点'); return; } } // 初始化网格信息 this.initGridInfo(); // 初始化网格占用情况 this.initGridOccupationMap(); // 生成随机的三个块 this.generateRandomBlocks(); } // 调试日志 logDebug(message: string) { if (this.DEBUG_MODE) { console.log(message); } } // 初始化网格信息 initGridInfo() { if (!this.gridContainer || this.gridInitialized) return; this.logDebug('初始化网格信息...'); // 创建二维数组存储网格节点 this.gridNodes = []; for (let row = 0; row < this.GRID_ROWS; row++) { this.gridNodes[row] = []; } // 遍历所有网格节点,将它们放入二维数组 for (let i = 0; i < this.gridContainer.children.length; i++) { const grid = this.gridContainer.children[i]; if (grid.name.startsWith('Grid_')) { // 从节点名称解析行列信息,例如Grid_2_3表示第2行第3列 const parts = grid.name.split('_'); if (parts.length === 3) { const row = parseInt(parts[1]); const col = parseInt(parts[2]); if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) { this.gridNodes[row][col] = grid; // 输出网格节点的世界坐标和锚点信息 const gridWorldPos = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(grid.position); const gridTransform = grid.getComponent(UITransform); this.logDebug(`网格节点 ${grid.name} 世界坐标: (${gridWorldPos.x}, ${gridWorldPos.y}), 锚点: (${gridTransform.anchorX}, ${gridTransform.anchorY}), 大小: ${gridTransform.width}x${gridTransform.height}`); } } } } // 计算网格间距 if (this.GRID_ROWS > 1 && this.GRID_COLS > 0) { if (this.gridNodes[0][0] && this.gridNodes[1][0]) { const pos1 = this.gridNodes[0][0].position; const pos2 = this.gridNodes[1][0].position; this.gridSpacing = Math.abs(pos2.y - pos1.y); this.logDebug(`计算得到网格间距: ${this.gridSpacing}`); } } this.gridInitialized = true; this.logDebug('网格信息初始化完成'); // 输出GridContainer信息 const containerTransform = this.gridContainer.getComponent(UITransform); console.log(`GridContainer信息 - 大小: ${containerTransform.width}x${containerTransform.height}, 锚点: (${containerTransform.anchorX}, ${containerTransform.anchorY})`); } // 初始化网格占用情况 initGridOccupationMap() { this.gridOccupationMap = []; for (let row = 0; row < this.GRID_ROWS; row++) { const rowArray: number[] = []; for (let col = 0; col < this.GRID_COLS; col++) { rowArray.push(0); } this.gridOccupationMap.push(rowArray); } } // 打印网格占用情况 printGridOccupation() { console.log('网格占用情况 (1=占用, 0=空闲):'); // 打印列索引 let colHeader = ' '; for (let col = 0; col < this.GRID_COLS; col++) { colHeader += ` ${col} `; } console.log(colHeader); // 打印分隔线 console.log(' ' + '-'.repeat(this.GRID_COLS * 3)); // 打印每一行 for (let row = 0; row < this.GRID_ROWS; row++) { let rowStr = ` ${row} |`; for (let col = 0; col < this.GRID_COLS; col++) { rowStr += ` ${this.gridOccupationMap[row][col]} `; } console.log(rowStr); } } generateRandomBlocks() { // 清除现有的块 this.clearBlocks(); // 检查是否有预制体可用 if (this.blockPrefabs.length === 0) { console.error('没有可用的预制体'); return; } // 位置偏移量 const offsets = [ new Vec3(-200, 0, 0), new Vec3(0, 0, 0), new Vec3(200, 0, 0) ]; // 生成三个随机块 for (let i = 0; i < 3; i++) { // 随机选择一个预制体 const randomIndex = Math.floor(Math.random() * this.blockPrefabs.length); const prefab = this.blockPrefabs[randomIndex]; // 实例化预制体 const block = instantiate(prefab); this.node.addChild(block); // 设置位置 block.position = offsets[i]; // 添加到块数组 this.blocks.push(block); // 添加触摸事件 this.setupDragEvents(block); // 输出方块B1节点(根节点)的世界坐标和锚点信息 const blockWorldPos = block.parent.getComponent(UITransform).convertToWorldSpaceAR(block.position); const blockTransform = block.getComponent(UITransform); console.log(`方块 ${block.name} B1节点(根节点)世界坐标: (${blockWorldPos.x}, ${blockWorldPos.y}), 锚点: (${blockTransform.anchorX}, ${blockTransform.anchorY}), 大小: ${blockTransform.width}x${blockTransform.height}`); } } setupDragEvents(block: Node) { // 添加触摸开始事件 block.on(Node.EventType.TOUCH_START, (event: EventTouch) => { // 记录当前拖拽的块 this.currentDragBlock = block; // 记录触摸起始位置 this.startPos = event.getUILocation(); // 记录块的起始位置 this.blockStartPos.set(block.position); // 将块置于顶层 block.setSiblingIndex(this.node.children.length - 1); // 临时保存方块占用的网格,而不是直接移除 this.tempStoreBlockOccupiedGrids(block); // 更新并打印网格占用情况 this.printGridOccupation(); // 输出方块B1节点(根节点)的世界坐标 const b1Node = block.name === 'B1' ? block : block.getChildByName('B1'); if (b1Node) { const blockWorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position); console.log(`拖拽开始 - 方块 ${block.name} B1节点世界坐标: (${blockWorldPos.x}, ${blockWorldPos.y})`); } }, this); // 添加触摸移动事件 block.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => { if (!this.currentDragBlock) return; // 计算触摸点位移 const location = event.getUILocation(); const deltaX = location.x - this.startPos.x; const deltaY = location.y - this.startPos.y; // 更新块的位置 this.currentDragBlock.position = new Vec3( this.blockStartPos.x + deltaX, this.blockStartPos.y + deltaY, this.blockStartPos.z ); }, this); // 添加触摸结束事件 block.on(Node.EventType.TOUCH_END, (event: EventTouch) => { if (this.currentDragBlock) { // 输出拖拽结束时方块B1节点的世界坐标 const b1Node = this.currentDragBlock.name === 'B1' ? this.currentDragBlock : this.currentDragBlock.getChildByName('B1'); if (b1Node) { const blockWorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position); console.log(`拖拽结束 - 方块 ${this.currentDragBlock.name} B1节点世界坐标: (${blockWorldPos.x}, ${blockWorldPos.y})`); } // 尝试将方块放置到网格中 if (!this.tryPlaceBlockToGrid(this.currentDragBlock)) { // 放置失败,返回原位 this.currentDragBlock.position = this.blockStartPos.clone(); console.log('方块放置失败,返回原位'); // 恢复方块原来的占用状态 this.restoreBlockOccupiedGrids(this.currentDragBlock); } else { // 放置成功,清除临时保存的占用状态 this.clearTempStoredOccupiedGrids(this.currentDragBlock); // 更新并打印网格占用情况 this.printGridOccupation(); } this.currentDragBlock = null; } }, this); // 添加触摸取消事件 block.on(Node.EventType.TOUCH_CANCEL, () => { if (this.currentDragBlock) { // 触摸取消,返回原位 this.currentDragBlock.position = this.blockStartPos.clone(); // 恢复方块原来的占用状态 this.restoreBlockOccupiedGrids(this.currentDragBlock); this.currentDragBlock = null; } }, this); } // 获取网格行列索引 getGridRowCol(gridNode: Node): { row: number, col: number } | null { if (!gridNode || !gridNode.name.startsWith('Grid_')) return null; const parts = gridNode.name.split('_'); if (parts.length === 3) { const row = parseInt(parts[1]); const col = parseInt(parts[2]); if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) { return { row, col }; } } return null; } // 找到最近的网格节点 findNearestGridNode(position: Vec3): Node { if (!this.gridContainer || !this.gridInitialized) return null; let nearestNode: Node = null; let minDistance = Number.MAX_VALUE; // 遍历所有网格节点 for (let row = 0; row < this.GRID_ROWS; row++) { for (let col = 0; col < this.GRID_COLS; col++) { const grid = this.gridNodes[row][col]; if (grid) { const distance = Vec3.distance(position, grid.position); if (distance < minDistance) { minDistance = distance; nearestNode = grid; } } } } // 增加容错性,放宽距离限制 // 如果距离太远,但我们仍然有一个最近的节点,就使用它 // 只有在极端情况下才返回null if (minDistance > this.gridSpacing * 2) { console.log(`警告: 距离最近的网格节点 ${nearestNode ? nearestNode.name : '无'} 距离较远: ${minDistance}`); // 如果距离超过网格间距的4倍,可能是完全偏离了网格区域 if (minDistance > this.gridSpacing * 4) { return null; } } return nearestNode; } // 获取方块的所有部分节点及其相对坐标 getBlockParts(block: Node): { node: Node, x: number, y: number }[] { const parts: { node: Node, x: number, y: number }[] = []; // 添加B1节点本身(根节点) parts.push({ node: block, x: 0, y: 0 }); // 递归查找所有子节点 this.findBlockParts(block, parts, 0, 0); return parts; } // 递归查找方块的所有部分 findBlockParts(node: Node, result: { node: Node, x: number, y: number }[], parentX: number, parentY: number) { // 遍历所有子节点 for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; // 跳过不参与占用的节点 if (this.NON_BLOCK_NODES.indexOf(child.name) !== -1) { continue; } let x = parentX; let y = parentY; // 尝试从节点名称中解析坐标 const match = child.name.match(/^\((-?\d+),(-?\d+)\)$/); if (match) { // 如果节点名称是坐标格式,直接使用 x = parseInt(match[1]); y = parseInt(match[2]); result.push({ node: child, x, y }); this.logDebug(`找到坐标节点: ${child.name}, x=${x}, y=${y}`); } else if (child.name.startsWith('B')) { // 如果是B开头的节点,使用其相对位置 // 计算相对于父节点的位置,并转换为网格单位 const relativeX = Math.round(child.position.x / this.gridSpacing); const relativeY = -Math.round(child.position.y / this.gridSpacing); // Y轴向下为正 x = parentX + relativeX; y = parentY + relativeY; result.push({ node: child, x, y }); this.logDebug(`找到B节点: ${child.name}, 相对位置: (${relativeX}, ${relativeY}), 累计位置: (${x}, ${y})`); } // 递归处理子节点 this.findBlockParts(child, result, x, y); } } // 检查方块是否可以放置在指定位置 canPlaceBlockAt(block: Node, targetGrid: Node): boolean { if (!this.gridInitialized) return false; // 获取目标网格的行列索引 const targetRowCol = this.getGridRowCol(targetGrid); if (!targetRowCol) return false; // 获取方块的所有部分 const parts = this.getBlockParts(block); console.log(`方块有 ${parts.length} 个部分`); // 检查每个部分是否与已占用的格子重叠 for (const part of parts) { // 计算在网格中的行列位置 const row = targetRowCol.row - part.y; // Y轴向下为正,所以是减法 const col = targetRowCol.col + part.x; // X轴向右为正 this.logDebug(`检查部分 ${part.node.name} 在位置 (${part.x}, ${part.y}) 对应网格位置: 行=${row}, 列=${col}`); // 检查是否超出网格范围 if (row < 0 || row >= this.GRID_ROWS || col < 0 || col >= this.GRID_COLS) { console.log(`部分 ${part.node.name} 超出网格范围`); return false; } // 检查该位置是否已被占用 if (this.gridOccupationMap[row][col] === 1) { console.log(`部分 ${part.node.name} 对应的网格位置已被占用: 行=${row}, 列=${col}`); return false; } } return true; } // 标记方块占用的格子 markOccupiedPositions(block: Node, targetGrid: Node) { if (!this.gridInitialized) return; // 获取目标网格的行列索引 const targetRowCol = this.getGridRowCol(targetGrid); if (!targetRowCol) return; // 获取方块的所有部分 const parts = this.getBlockParts(block); // 清除之前的占用记录 block['occupiedGrids'] = []; // 为每个部分标记占用位置 for (const part of parts) { // 计算在网格中的行列位置 const row = targetRowCol.row - part.y; // Y轴向下为正,所以是减法 const col = targetRowCol.col + part.x; // X轴向右为正 // 检查是否在有效范围内 if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) { // 更新网格占用情况 this.gridOccupationMap[row][col] = 1; // 存储方块与网格的关联 block['occupiedGrids'] = block['occupiedGrids'] || []; block['occupiedGrids'].push({ row, col }); this.logDebug(`标记占用: 部分=${part.node.name}, 位置=(${part.x}, ${part.y}), 行=${row}, 列=${col}`); } } } // 临时保存方块占用的网格 tempStoreBlockOccupiedGrids(block: Node) { // 获取方块占用的网格 const occupiedGrids = block['occupiedGrids']; if (!occupiedGrids || occupiedGrids.length === 0) return; console.log(`临时保存占用: 方块=${block.name}, 占用网格数=${occupiedGrids.length}`); // 保存到临时数组 this.tempRemovedOccupiedGrids.push({ block: block, occupiedGrids: [...occupiedGrids] // 复制一份,避免引用问题 }); // 移除占用标记 for (const grid of occupiedGrids) { if (grid.row >= 0 && grid.row < this.GRID_ROWS && grid.col >= 0 && grid.col < this.GRID_COLS) { this.gridOccupationMap[grid.row][grid.col] = 0; } } // 清空方块的占用记录 block['occupiedGrids'] = []; } // 恢复方块原来的占用状态 restoreBlockOccupiedGrids(block: Node) { // 查找临时保存的占用状态 const index = this.tempRemovedOccupiedGrids.findIndex(item => item.block === block); if (index === -1) return; const savedItem = this.tempRemovedOccupiedGrids[index]; console.log(`恢复占用: 方块=${block.name}, 占用网格数=${savedItem.occupiedGrids.length}`); // 恢复占用标记 for (const grid of savedItem.occupiedGrids) { if (grid.row >= 0 && grid.row < this.GRID_ROWS && grid.col >= 0 && grid.col < this.GRID_COLS) { this.gridOccupationMap[grid.row][grid.col] = 1; } } // 恢复方块的占用记录 block['occupiedGrids'] = [...savedItem.occupiedGrids]; // 从临时数组中移除 this.tempRemovedOccupiedGrids.splice(index, 1); // 更新并打印网格占用情况 this.printGridOccupation(); } // 清除临时保存的占用状态 clearTempStoredOccupiedGrids(block: Node) { // 查找临时保存的占用状态 const index = this.tempRemovedOccupiedGrids.findIndex(item => item.block === block); if (index === -1) return; // 从临时数组中移除 this.tempRemovedOccupiedGrids.splice(index, 1); } // 移除方块占用的格子 removeBlockFromGrid(block: Node) { // 获取方块占用的网格 const occupiedGrids = block['occupiedGrids']; if (!occupiedGrids) return; console.log(`移除占用: 方块=${block.name}, 占用网格数=${occupiedGrids.length}`); // 移除占用标记 for (const grid of occupiedGrids) { if (grid.row >= 0 && grid.row < this.GRID_ROWS && grid.col >= 0 && grid.col < this.GRID_COLS) { this.gridOccupationMap[grid.row][grid.col] = 0; } } // 清空方块的占用记录 block['occupiedGrids'] = []; } clearBlocks() { // 移除所有已经生成的块 for (const block of this.blocks) { if (block.isValid) { // 移除占用的格子 this.removeBlockFromGrid(block); // 销毁方块 block.destroy(); } } this.blocks = []; // 重置网格占用情况 this.initGridOccupationMap(); // 打印网格占用情况 this.printGridOccupation(); } // 尝试将方块放置到网格中 tryPlaceBlockToGrid(block: Node): boolean { if (!this.gridContainer || !this.gridInitialized) return false; // 获取方块信息 const blockTransform = block.getComponent(UITransform); console.log(`方块 ${block.name} 信息 - 大小: ${blockTransform.width}x${blockTransform.height}, 锚点: (${blockTransform.anchorX}, ${blockTransform.anchorY})`); // 获取B1节点(在这个情况下,B1就是block自身,因为预制体的根节点就是B1) // 如果B1不是根节点,而是子节点,则需要查找B1节点 let b1Node = block; if (block.name !== 'B1') { // 查找B1节点 b1Node = block.getChildByName('B1'); if (!b1Node) { console.error(`找不到B1节点,无法放置方块`); return false; } } console.log(`B1节点名称: ${b1Node.name}`); // 获取方块B1节点在世界坐标中的位置 const b1WorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position); console.log(`放置前 - B1节点世界坐标: (${b1WorldPos.x}, ${b1WorldPos.y})`); // 将B1节点的世界坐标转换为GridContainer的本地坐标 const gridPos = this.gridContainer.getComponent(UITransform).convertToNodeSpaceAR(b1WorldPos); // 检查是否在GridContainer范围内 const gridSize = this.gridContainer.getComponent(UITransform).contentSize; const halfWidth = gridSize.width / 2; const halfHeight = gridSize.height / 2; // 增加容错性,放宽边界检查 const tolerance = this.gridSpacing * 0.5; if (gridPos.x < -halfWidth - tolerance || gridPos.x > halfWidth + tolerance || gridPos.y < -halfHeight - tolerance || gridPos.y > halfHeight + tolerance) { console.log('方块不在GridContainer范围内'); return false; } // 找到最近的网格节点 const nearestGrid = this.findNearestGridNode(gridPos); if (!nearestGrid) { console.log('找不到合适的网格节点'); // 尝试使用网格行列直接定位 const row = Math.floor((gridPos.y + halfHeight) / this.gridSpacing); const col = Math.floor((gridPos.x + halfWidth) / this.gridSpacing); // 检查计算出的行列是否在有效范围内 if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) { const grid = this.gridNodes[row][col]; if (grid) { console.log(`尝试使用计算的行列 (${row}, ${col}) 找到网格节点 ${grid.name}`); return this.tryPlaceBlockToSpecificGrid(block, grid); } } return false; } return this.tryPlaceBlockToSpecificGrid(block, nearestGrid); } // 尝试将方块放置到指定的网格节点 tryPlaceBlockToSpecificGrid(block: Node, targetGrid: Node): boolean { // 获取B1节点 let b1Node = block; if (block.name !== 'B1') { b1Node = block.getChildByName('B1'); if (!b1Node) { console.error(`找不到B1节点,无法放置方块`); return false; } } // 获取网格节点信息 const gridTransform = targetGrid.getComponent(UITransform); console.log(`网格节点 ${targetGrid.name} 信息 - 大小: ${gridTransform.width}x${gridTransform.height}, 锚点: (${gridTransform.anchorX}, ${gridTransform.anchorY})`); // 获取网格节点的中心点世界坐标 const gridCenterWorldPos = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(targetGrid.position); console.log(`最近网格节点 ${targetGrid.name} 中心点世界坐标: (${gridCenterWorldPos.x}, ${gridCenterWorldPos.y})`); // 检查方块的所有部分是否会与已占用的格子重叠 if (!this.canPlaceBlockAt(block, targetGrid)) { console.log('方块放置位置已被占用或超出边界'); return false; } // 计算B1节点应该移动到的位置 const targetWorldPos = gridCenterWorldPos.clone(); // 计算根节点需要移动的位置 // 1. 先计算B1节点相对于根节点的偏移 const b1LocalPos = b1Node.position.clone(); console.log(`B1节点相对于根节点的偏移: (${b1LocalPos.x}, ${b1LocalPos.y}, ${b1LocalPos.z})`); // 2. 计算根节点的目标世界坐标 // 如果B1是根节点,则直接使用网格中心点 let rootTargetWorldPos; if (b1Node === block) { rootTargetWorldPos = targetWorldPos.clone(); } else { // 如果B1是子节点,需要计算根节点应该在的位置,使B1对准网格中心 // 根节点位置 = 网格中心位置 - B1相对于根节点的偏移 rootTargetWorldPos = new Vec3( targetWorldPos.x - b1LocalPos.x, targetWorldPos.y - b1LocalPos.y, targetWorldPos.z ); } // 3. 将世界坐标转换为根节点父节点的本地坐标 const rootTargetLocalPos = block.parent.getComponent(UITransform).convertToNodeSpaceAR(rootTargetWorldPos); // 设置方块位置,确保B1节点的中心与网格节点的中心对齐 block.position = rootTargetLocalPos; // 输出放置后B1节点的世界坐标 const placedB1WorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position); console.log(`放置后 - B1节点世界坐标: (${placedB1WorldPos.x}, ${placedB1WorldPos.y})`); // 输出对比信息,查看是否完全对齐 console.log(`对齐检查 - 网格中心点: (${gridCenterWorldPos.x}, ${gridCenterWorldPos.y}), B1节点中心点: (${placedB1WorldPos.x}, ${placedB1WorldPos.y})`); console.log(`对齐差值 - X: ${placedB1WorldPos.x - gridCenterWorldPos.x}, Y: ${placedB1WorldPos.y - gridCenterWorldPos.y}`); // 标记占用的格子 this.markOccupiedPositions(block, targetGrid); console.log('方块放置成功'); return true; } }