import { _decorator, Component, Node, Prefab, instantiate, Vec3, EventTouch, Vec2, UITransform, find, Rect, Label, Color, Size } 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; // 方块容器节点(kuang) @property({ type: Node, tooltip: '拖拽kuang节点到这里' }) public kuangContainer: Node = null; // 金币标签节点 @property({ type: Node, tooltip: '拖拽CoinLabel节点到这里' }) public coinLabelNode: Node = null; // 玩家金币数量 private playerCoins: number = 200; // 方块价格标签映射 private blockPriceMap: Map = new Map(); // 已经生成的块 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', 'Price']; // 临时保存方块的原始占用格子 private tempRemovedOccupiedGrids: { block: Node, occupiedGrids: { row: number, col: number }[] }[] = []; // 方块原始位置(在kuang中的位置) private originalPositions: Map = new Map(); // 方块当前所在的区域 private blockLocations: Map = new Map(); start() { // 如果没有指定GridContainer,尝试找到它 if (!this.gridContainer) { this.gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer'); if (!this.gridContainer) { console.error('找不到GridContainer节点'); return; } } // 如果没有指定kuangContainer,尝试找到它 if (!this.kuangContainer) { this.kuangContainer = find('Canvas/GameLevelUI/kuang'); if (!this.kuangContainer) { console.error('找不到kuang节点'); return; } } // 如果没有指定coinLabelNode,尝试找到它 if (!this.coinLabelNode) { this.coinLabelNode = find('Canvas/GameLevelUI/CoinNode/CoinLabel'); if (!this.coinLabelNode) { console.error('找不到CoinLabel节点'); return; } } // 初始化玩家金币显示 this.updateCoinDisplay(); // 初始化网格信息 this.initGridInfo(); // 初始化网格占用情况 this.initGridOccupationMap(); // 生成随机的三个块 this.generateRandomBlocks(); } // 更新金币显示 updateCoinDisplay() { if (this.coinLabelNode) { const label = this.coinLabelNode.getComponent(Label); if (label) { label.string = this.playerCoins.toString(); } } } // 初始化网格信息 initGridInfo() { if (!this.gridContainer || this.gridInitialized) return; // 创建二维数组存储网格节点 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; } } } } // 计算网格间距 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.gridInitialized = true; } // 初始化网格占用情况 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); } } 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.originalPositions.set(block, offsets[i].clone()); // 标记方块位置为kuang this.blockLocations.set(block, 'kuang'); // 添加到块数组 this.blocks.push(block); // 查找并保存价格标签节点 this.findAndSavePriceNode(block); // 添加触摸事件 this.setupDragEvents(block); } } // 查找并保存价格标签节点 findAndSavePriceNode(block: Node) { // 查找db01节点 let db01 = block.getChildByName('db01'); // 如果没有db01节点,创建一个 if (!db01) { db01 = new Node('db01'); block.addChild(db01); db01.position = new Vec3(0, -80, 0); // 放在方块下方 } // 查找Price节点 let priceNode = db01.getChildByName('Price'); // 如果没有Price节点,创建一个 if (!priceNode) { priceNode = new Node('Price'); db01.addChild(priceNode); // 添加UI变换组件 const transform = priceNode.addComponent(UITransform); transform.contentSize = new Size(80, 30); // 添加标签组件 const label = priceNode.addComponent(Label); label.string = this.generateRandomPrice().toString(); label.fontSize = 20; label.color = new Color(255, 255, 0, 255); // 黄色 // 居中显示 label.horizontalAlign = Label.HorizontalAlign.CENTER; label.verticalAlign = Label.VerticalAlign.CENTER; } // 保存价格节点引用 this.blockPriceMap.set(block, priceNode); // 确保价格标签可见 priceNode.active = true; } // 生成随机价格 generateRandomPrice(): number { // 生成50-100之间的随机价格 return Math.floor(Math.random() * 51) + 50; } // 获取方块价格 getBlockPrice(block: Node): number { const priceNode = this.blockPriceMap.get(block); if (priceNode) { const label = priceNode.getComponent(Label); if (label) { // 尝试解析价格 const price = parseInt(label.string); if (!isNaN(price)) { return price; } } } // 默认价格 return 50; } // 隐藏价格标签 hidePriceLabel(block: Node) { const priceNode = this.blockPriceMap.get(block); if (priceNode) { priceNode.active = false; } } // 显示价格标签 showPriceLabel(block: Node) { const priceNode = this.blockPriceMap.get(block); if (priceNode) { priceNode.active = true; } } // 扣除玩家金币 deductPlayerCoins(amount: number): boolean { if (this.playerCoins >= amount) { this.playerCoins -= amount; this.updateCoinDisplay(); return true; } return false; } 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); // 添加触摸移动事件 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) { // 获取当前触摸位置 const touchPos = event.getUILocation(); // 检查是否拖到了kuang区域 if (this.isInKuangArea(touchPos)) { // 如果拖回kuang区域,恢复到原始位置 const originalPos = this.originalPositions.get(this.currentDragBlock); if (originalPos) { this.currentDragBlock.position = originalPos.clone(); } // 恢复方块原来的占用状态(如果有) this.restoreBlockOccupiedGrids(this.currentDragBlock); // 更新方块位置标记 this.blockLocations.set(this.currentDragBlock, 'kuang'); // 确保价格标签显示 this.showPriceLabel(this.currentDragBlock); } else if (this.tryPlaceBlockToGrid(this.currentDragBlock)) { // 获取方块价格 const price = this.getBlockPrice(this.currentDragBlock); // 尝试扣除金币 if (this.deductPlayerCoins(price)) { // 扣除成功,清除临时保存的占用状态 this.clearTempStoredOccupiedGrids(this.currentDragBlock); // 更新方块位置标记 this.blockLocations.set(this.currentDragBlock, 'grid'); // 隐藏价格标签 this.hidePriceLabel(this.currentDragBlock); } else { // 金币不足,放置失败,返回原位 const originalPos = this.originalPositions.get(this.currentDragBlock); if (originalPos) { this.currentDragBlock.position = originalPos.clone(); } // 恢复方块原来的占用状态 this.restoreBlockOccupiedGrids(this.currentDragBlock); // 确保价格标签显示 this.showPriceLabel(this.currentDragBlock); } } else { // 放置失败,返回原位 const currentLocation = this.blockLocations.get(this.currentDragBlock); if (currentLocation === 'kuang') { // 如果之前在kuang中,返回kuang中的位置 const originalPos = this.originalPositions.get(this.currentDragBlock); if (originalPos) { this.currentDragBlock.position = originalPos.clone(); } } else { // 否则返回拖拽开始的位置 this.currentDragBlock.position = this.blockStartPos.clone(); } // 恢复方块原来的占用状态 this.restoreBlockOccupiedGrids(this.currentDragBlock); // 确保价格标签显示 this.showPriceLabel(this.currentDragBlock); } this.currentDragBlock = null; } }, this); // 添加触摸取消事件 block.on(Node.EventType.TOUCH_CANCEL, () => { if (this.currentDragBlock) { // 触摸取消,返回原位 this.currentDragBlock.position = this.blockStartPos.clone(); // 恢复方块原来的占用状态 this.restoreBlockOccupiedGrids(this.currentDragBlock); // 确保价格标签显示 this.showPriceLabel(this.currentDragBlock); this.currentDragBlock = null; } }, this); } // 检查是否在kuang区域内 isInKuangArea(touchPos: Vec2): boolean { if (!this.kuangContainer) return false; // 获取kuang的世界矩形 const kuangTransform = this.kuangContainer.getComponent(UITransform); if (!kuangTransform) return false; // 计算kuang的世界边界 const kuangBoundingBox = new Rect( this.kuangContainer.worldPosition.x - kuangTransform.width * kuangTransform.anchorX, this.kuangContainer.worldPosition.y - kuangTransform.height * kuangTransform.anchorY, kuangTransform.width, kuangTransform.height ); // 检查触摸点是否在kuang矩形内 return kuangBoundingBox.contains(new Vec2(touchPos.x, touchPos.y)); } // 临时保存方块占用的网格 tempStoreBlockOccupiedGrids(block: Node) { // 获取方块占用的网格 const occupiedGrids = block['occupiedGrids']; if (!occupiedGrids || occupiedGrids.length === 0) return; // 保存到临时数组 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]; // 恢复占用标记 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); } // 清除临时保存的占用状态 clearTempStoredOccupiedGrids(block: Node) { // 查找临时保存的占用状态 const index = this.tempRemovedOccupiedGrids.findIndex(item => item.block === block); if (index === -1) return; // 从临时数组中移除 this.tempRemovedOccupiedGrids.splice(index, 1); } // 获取网格行列索引 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; } } } } // 增加容错性,放宽距离限制 if (minDistance > this.gridSpacing * 2) { // 如果距离超过网格间距的4倍,可能是完全偏离了网格区域 if (minDistance > this.gridSpacing * 4) { return null; } } return nearestNode; } // 尝试将方块放置到网格中 tryPlaceBlockToGrid(block: Node): boolean { if (!this.gridContainer || !this.gridInitialized) return false; // 获取B1节点 let b1Node = block; if (block.name !== 'B1') { // 查找B1节点 b1Node = block.getChildByName('B1'); if (!b1Node) { return false; } } // 获取方块B1节点在世界坐标中的位置 const b1WorldPos = b1Node.parent.getComponent(UITransform).convertToWorldSpaceAR(b1Node.position); // 将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) { return false; } // 找到最近的网格节点 const nearestGrid = this.findNearestGridNode(gridPos); if (!nearestGrid) { // 尝试使用网格行列直接定位 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) { 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) { return false; } } // 检查方块的所有部分是否会与已占用的格子重叠 if (!this.canPlaceBlockAt(block, targetGrid)) { return false; } // 获取网格节点的中心点世界坐标 const gridCenterWorldPos = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(targetGrid.position); // 计算B1节点应该移动到的位置 const targetWorldPos = gridCenterWorldPos.clone(); // 计算根节点需要移动的位置 // 1. 先计算B1节点相对于根节点的偏移 const b1LocalPos = b1Node.position.clone(); // 2. 计算根节点的目标世界坐标 let rootTargetWorldPos; if (b1Node === block) { rootTargetWorldPos = targetWorldPos.clone(); } else { // 如果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; // 标记占用的格子 this.markOccupiedPositions(block, targetGrid); return true; } // 获取方块的所有部分节点及其相对坐标 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 }); } 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.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); // 检查每个部分是否与已占用的格子重叠 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) { return false; } // 检查该位置是否已被占用 if (this.gridOccupationMap[row][col] === 1) { 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 }); } } } // 移除方块占用的格子 removeBlockFromGrid(block: Node) { // 获取方块占用的网格 const occupiedGrids = block['occupiedGrids']; if (!occupiedGrids) return; // 移除占用标记 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.originalPositions.clear(); // 清空位置标记 this.blockLocations.clear(); // 清空价格标签映射 this.blockPriceMap.clear(); // 重置网格占用情况 this.initGridOccupationMap(); } }