import { _decorator, BoxCollider, Color, Component, Graphics, instantiate, Label, Node, Prefab, sp, Sprite, SpriteFrame, tween, v3, Vec3 } from 'cc'; import { Block } from './Block'; import { BlockUtil } from './BlockUitl'; import { blockMap } from './blockMap'; import { LvDir } from '../../LvData/LvData'; const { ccclass, property } = _decorator; @ccclass('BlockLink') export class BlockLink extends Component { mapConfig: blockMap = new blockMap(); // 记录选择的两个方块 public selectedBlocks: Node[] = []; @property(Graphics) lineGraphics: Graphics | null = null; // 绘制连线的Graphics组件 lineNode//连线节点 @property(sp.Skeleton) lianSkeleton: sp.Skeleton | null = null;//绘制连线的骨骼 @property(Prefab) lineSegmentPrefab: Prefab | null = null; // 线段骨骼预制 @property({ type: sp.SkeletonData }) spineData: sp.SkeletonData | null = null; spineRefLength: number = 70; // 骨骼动画原始长度 // 存储所有方块节点 static blockArry: (Node | null)[][] = []; //当前选择的方块 public sleeBlock: Node = null; public static totalBlocks: number = 0; public static remainingBlocks: number = 0; // 匹配锁定 public isMatching: boolean = false; public isMoving: boolean = false; static inti: BlockLink start() { BlockLink.inti = this; this.node.getChildByName("line").active = false this.isMoving = false this.isMatching = false; } isConnected(block1: Node, block2: Node, isSearch = false): boolean { if (!this.node.isValid) return; //debugger this.selectedBlocks = [block1, block2]; // 直连(最快) const isLineConnected = this.isLineConnected(block1, block2); console.log(`是否直连: ${isLineConnected}`); if (isLineConnected) { if (!this.node.isValid) return; if (!isSearch) { const block1Comp = block1.getComponent(Block); const block2Comp = block2.getComponent(Block); if (block1Comp && block2Comp) { this.drawDirectLine(block2, block1); block1Comp.handleMatchSuccess(block2Comp); } } return true; } // 再检查一个拐点 const isOneLinkcrease = this.isOneLinkcrease(block1, block2); console.log(`是否一个拐点连接: ${isOneLinkcrease}`); if (isOneLinkcrease) { if (!this.node.isValid) return; if (!isSearch) { const block1Comp = block1.getComponent(Block); const block2Comp = block2.getComponent(Block); if (block1Comp && block2Comp) { // 获取拐点世界坐标 const cornerPos = this.getLinkCorner(block1, block2); if (cornerPos) { this.drawPathWithCorner(block2, cornerPos, block1); // 绘制路径时使用:起点 -> cornerPos -> 终点 } else { console.log("没有找到有效拐点路径"); } block1Comp.handleMatchSuccess(block2Comp); } } return true; } // 两个拐点(最慢) const istwoLinkcrease = this.istwoLinkcrease(block1, block2); console.log(`是否两个拐点连接: ${istwoLinkcrease}`); if (istwoLinkcrease) { if (!this.node.isValid) return; if (!isSearch) { const block1Comp = block1.getComponent(Block); const block2Comp = block2.getComponent(Block); if (block1Comp && block2Comp) { // 新增:获取两个拐点坐标 const start = this.getBlockPosition(block1); const end = this.getBlockPosition(block2); const corners = BlockUtil.getTwoLinkCorners( start.row, start.col, end.row, end.col, this.mapConfig.rows, this.mapConfig.cols, (row, col) => this.canPass(row, col) ); if (corners) { // 将行列坐标转换为世界坐标 const corner1Pos = v3( this.mapConfig.gridOrigin.x + corners[0].j * this.mapConfig.spacing, this.mapConfig.gridOrigin.y - corners[0].i * this.mapConfig.spacing, 0 ); const corner2Pos = v3( this.mapConfig.gridOrigin.x + corners[1].j * this.mapConfig.spacing, this.mapConfig.gridOrigin.y - corners[1].i * this.mapConfig.spacing, 0 ); // 绘制双拐点路径(需先实现 drawPathWithTwoCorners 方法) this.drawPathWithTwoCorners(block2, corner2Pos, corner1Pos, block1); } block1Comp.handleMatchSuccess(block2Comp); } } return true; } } isLineConnected(block1: Node, block2: Node): boolean { if (!this.node.isValid) return; //debugger console.log("直线") const start = this.getBlockPosition(block1) const end = this.getBlockPosition(block2) return BlockUtil.isLineConnectedByIndex( start.row, start.col, end.row, end.col, (row, col) => this.canPass(row, col) ); } isOneLinkcrease(block1: Node, block2: Node): boolean { if (!this.node.isValid) return; //debugger console.log("1个折角") const start = this.getBlockPosition(block1) const end = this.getBlockPosition(block2) return BlockUtil.isOneLink( start.row, start.col, end.row, end.col, (row, col) => this.canPass(row, col) ); } istwoLinkcrease(block1: Node, block2: Node): boolean { if (!this.node.isValid) return; //debugger console.log("两个折角") const start = this.getBlockPosition(block1) const end = this.getBlockPosition(block2) return BlockUtil.isTwoLink( start.row, start.col, end.row, end.col, this.mapConfig.rows, this.mapConfig.cols, (row, col) => this.canPass(row, col) ); } canPass(_row: number, _col: number): boolean { //debugger; // 检查边界外的位置 if (_row < 0 || _row >= this.mapConfig.rows || _col < 0 || _col >= this.mapConfig.cols) { //console.log(`位置 (${_row}, ${_col}) 超出边界`); return true; } // 明确区分起点终点检查 const isStart = this.isSameBlock(_row, _col, this.selectedBlocks[0]); const isEnd = this.isSameBlock(_row, _col, this.selectedBlocks[1]); if (isStart) { return true; } if (isEnd) { return true; } // 检查空位 const isEmpty = BlockLink.blockArry[_row][_col] === null; if (!isEmpty) { // console.log(`位置 (${_row}, ${_col}) 已被占用。`); return false; } //console.log(`位置 (${_row}, ${_col}) 可以通过。`); return true; } // 判断指定行列位置是否是某个方块 private isSameBlock(_row: number, _col: number, block: Node): boolean { //debugger if (!block) return false; const pos = this.getBlockPosition(block); return pos.row === _row && pos.col === _col; } //获取行列 public getRowCol(pos: Vec3): { i: number, j: number } { // debugger const offsetX = pos.x - this.mapConfig.gridOrigin.x; const offsetY = this.mapConfig.gridOrigin.y - pos.y; const j = Math.round(offsetX / this.mapConfig.spacing); const i = Math.round(offsetY / this.mapConfig.spacing); if (i < 0 || i >= this.mapConfig.rows || j < 0 || j >= this.mapConfig.cols) { console.log(`坐标转换: (${pos.x}, ${pos.y}) -> (${i}, ${j})`); } console.log(`getRowCol行: ${i}, 列: ${j}`) return { i, j }; } getBlockPosition(blockNode: Node) { for (let row = 0; row < this.mapConfig.rows; row++) { for (let col = 0; col < this.mapConfig.cols; col++) { if (BlockLink.blockArry[row][col] === blockNode) { return { row, col }; // 正确返回行列 } } } return null; } getLinkCorner(block1: Node, block2: Node): Vec3 | null { const start = this.getBlockPosition(block1); const end = this.getBlockPosition(block2); const corner = BlockUtil.getOneLinkCorner( start.row, start.col, end.row, end.col, (row, col) => this.canPass(row, col) ); if (!corner) return null; // 将行列坐标转换为世界坐标 return v3( this.mapConfig.gridOrigin.x + corner.j * this.mapConfig.spacing, this.mapConfig.gridOrigin.y - corner.i * this.mapConfig.spacing, 0 ); } /** * 绘制两个方块的直连路径 * @param startNode 起点方块节点 * @param endNode 终点方块节点 */ private drawDirectLine(startNode: Node, endNode: Node): void { const graphics = this.node.getChildByName("Graphics").getComponent(Graphics); graphics.node.setSiblingIndex(graphics.node.parent.children.length - 1); // 置顶 graphics.clear(); // graphics.fillColor.fromHEX('#0000FF'); // graphics.fillColor.fromHEX('#ff0000'); // graphics.fillColor = new Color().fromHEX('#0000ff'); let fillColor = new Color().fromHEX('#3562ff') graphics.strokeColor = fillColor graphics.lineWidth = 12; const startPos = startNode.position; const endPos = endNode.position; // 绘制直线路径 graphics.moveTo(startPos.x, startPos.y); graphics.lineTo(endPos.x, endPos.y); graphics.stroke(); this.scheduleOnce(() => { graphics.clear(); console.log("路径已清除"); }, 0.35); } /** * 绘制从起点到拐点再到终点的折线 * @param startNode 起点方块节点 * @param cornerPos 拐点世界坐标 * @param endNode 终点方块节点 */ private drawPathWithCorner(startNode: Node, cornerPos: Vec3, endNode: Node): void { const graphics = this.node.getChildByName("Graphics").getComponent(Graphics); graphics.node.setSiblingIndex(graphics.node.parent.children.length - 1); // 置顶 graphics.clear(); //graphics.strokeColor = Color.BLUE; // graphics.fillColor.fromHEX('#0000FF') //graphics.fillColor = new Color().fromHEX('#0000ff'); let fillColor = new Color().fromHEX('#3562ff') graphics.strokeColor = fillColor graphics.lineWidth = 12; // 增强调试输出 const startPos = startNode.position; const endPos = endNode.position; const cornerWorldPos = cornerPos.clone(); // 已经是世界坐标 // 绘制路径 graphics.moveTo(startPos.x, startPos.y); graphics.lineTo(cornerWorldPos.x, cornerWorldPos.y); // 直接到拐点 graphics.lineTo(endPos.x, endPos.y); // 再到终点 graphics.stroke(); this.scheduleOnce(() => { graphics.clear(); console.log("路径已清除"); }, 0.5); } /** * 绘制带两个拐点的路径 * @param startNode 起点节点 * @param corner1Pos 第一个拐点世界坐标 * @param corner2Pos 第二个拐点世界坐标 * @param endNode 终点节点 */ private drawPathWithTwoCorners( startNode: Node, corner1Pos: Vec3, corner2Pos: Vec3, endNode: Node ): void { const graphics = this.node.getChildByName("Graphics").getComponent(Graphics); graphics.node.setSiblingIndex(graphics.node.parent.children.length - 1); // 置顶 graphics.clear(); //graphics.fillColor.fromHEX('#0000FF'); graphics.lineWidth = 12; // graphics.color = Color.BLUE; let fillColor = new Color().fromHEX('#3562ff') graphics.strokeColor = fillColor const startPos = startNode.position; const endPos = endNode.position; // 绘制路径 graphics.moveTo(startPos.x, startPos.y); graphics.lineTo(corner1Pos.x, corner1Pos.y); graphics.lineTo(corner2Pos.x, corner2Pos.y); graphics.lineTo(endPos.x, endPos.y); graphics.stroke(); this.scheduleOnce(() => { graphics.clear(); console.log("路径已清除"); }, 0.5); } // /**方块移动*/ // BlockLink.ts public moveByLevelDirection(lvDir: LvDir): void { switch (lvDir) { case LvDir.left: this.moveAllLeft(); break; case LvDir.right: this.moveAllRight(); break; case LvDir.up: this.moveAllUp(); break; case LvDir.down: this.moveAllDown(); break; case LvDir.xCenter: this.moveToCenter(true); break; case LvDir.xOut: this.moveFromCenter(false); break; case LvDir.yCenter: this.moveToCenter(false); break; case LvDir.yOut: this.moveFromCenter(true); break; } } private moveAllLeft() { this.isMoving = true; for (let row = 0; row < this.mapConfig.rows; row++) { const originalRow = [...BlockLink.blockArry[row]]; const rowBlocks = originalRow.filter(block => block !== null); const newRow = new Array(this.mapConfig.cols).fill(null); for (let i = 0; i < rowBlocks.length; i++) { newRow[i] = rowBlocks[i]; } BlockLink.blockArry[row] = newRow; for (let col = 0; col < this.mapConfig.cols; col++) { const block = BlockLink.blockArry[row][col]; if (block) { const newX = this.mapConfig.gridOrigin.x + col * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - row * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); } } } } private moveAllRight() { this.isMoving = true; for (let row = 0; row < this.mapConfig.rows; row++) { const originalRow = [...BlockLink.blockArry[row]]; const blocksInRow = originalRow.filter(block => block !== null); const startCol = this.mapConfig.cols - blocksInRow.length; const newRow = new Array(this.mapConfig.cols).fill(null); for (let i = 0; i < blocksInRow.length; i++) { newRow[startCol + i] = blocksInRow[i]; } BlockLink.blockArry[row] = newRow; for (let col = 0; col < this.mapConfig.cols; col++) { const block = BlockLink.blockArry[row][col]; if (block) { const newX = this.mapConfig.gridOrigin.x + col * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - row * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); } } } } /**上移 */ private moveAllUp() { this.isMoving = true; for (let col = 0; col < this.mapConfig.cols; col++) { const blocksInCol = BlockLink.blockArry.map(row => row[col]).filter(block => block !== null); BlockLink.blockArry.forEach(row => row[col] = null); const startRow = 0; for (let row = 0; row < blocksInCol.length; row++) { const block = blocksInCol[row]; BlockLink.blockArry[startRow + row][col] = block; const newX = this.mapConfig.gridOrigin.x + col * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - (startRow + row) * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); } } } /**下移 */ private moveAllDown() { this.isMoving = true; for (let col = 0; col < this.mapConfig.cols; col++) { const blocksInCol = BlockLink.blockArry.map(row => row[col]).filter(block => block !== null); BlockLink.blockArry.forEach(row => row[col] = null); const startRow = this.mapConfig.rows - blocksInCol.length; for (let row = 0; row < blocksInCol.length; row++) { const block = blocksInCol[row]; BlockLink.blockArry[startRow + row][col] = block; const newX = this.mapConfig.gridOrigin.x + col * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - (startRow + row) * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); } } } /** 移动到中心 */ private moveToCenter(isHorizontal: boolean) { this.isMoving = true; if (isHorizontal) { for (let row = 0; row < this.mapConfig.rows; row++) { const blocks = BlockLink.blockArry[row].filter(b => b !== null); const startCol = Math.floor((this.mapConfig.cols - blocks.length) / 2); BlockLink.blockArry[row].fill(null); blocks.forEach((block, idx) => { const targetCol = startCol + idx; BlockLink.blockArry[row][targetCol] = block; const newX = this.mapConfig.gridOrigin.x + targetCol * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - row * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); }); } } else { for (let col = 0; col < this.mapConfig.cols; col++) { const blocks = BlockLink.blockArry.map(row => row[col]).filter(b => b !== null); const startRow = Math.floor((this.mapConfig.rows - blocks.length) / 2); BlockLink.blockArry.forEach(row => row[col] = null); blocks.forEach((block, idx) => { const targetRow = startRow + idx; BlockLink.blockArry[targetRow][col] = block; const newX = this.mapConfig.gridOrigin.x + col * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - targetRow * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); }); } } } /** 从中心移出 */ private moveFromCenter(isHorizontal: boolean) { this.isMoving = true; if (isHorizontal) { for (let row = 0; row < this.mapConfig.rows; row++) { const leftBlocks = []; const rightBlocks = []; const centerCol = Math.floor(this.mapConfig.cols / 2); for (let col = 0; col < centerCol; col++) { if (BlockLink.blockArry[row][col]) { leftBlocks.push({ block: BlockLink.blockArry[row][col], originalCol: col }); } } for (let col = centerCol; col < this.mapConfig.cols; col++) { if (BlockLink.blockArry[row][col]) { rightBlocks.push({ block: BlockLink.blockArry[row][col], originalCol: col }); } } BlockLink.blockArry[row].fill(null); leftBlocks.sort((a, b) => a.originalCol - b.originalCol); for (let i = 0; i < leftBlocks.length; i++) { const block = leftBlocks[i].block; const targetCol = i; BlockLink.blockArry[row][targetCol] = block; const newX = this.mapConfig.gridOrigin.x + targetCol * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - row * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); } rightBlocks.sort((a, b) => b.originalCol - a.originalCol); for (let i = 0; i < rightBlocks.length; i++) { const block = rightBlocks[i].block; const targetCol = this.mapConfig.cols - 1 - i; BlockLink.blockArry[row][targetCol] = block; const newX = this.mapConfig.gridOrigin.x + targetCol * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - row * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); } } } else { for (let col = 0; col < this.mapConfig.cols; col++) { const topBlocks = []; const bottomBlocks = []; for (let row = 0; row < 5; row++) { if (BlockLink.blockArry[row][col]) { topBlocks.push({ block: BlockLink.blockArry[row][col], originalRow: row }); } } for (let row = 5; row < this.mapConfig.rows; row++) { if (BlockLink.blockArry[row][col]) { bottomBlocks.push({ block: BlockLink.blockArry[row][col], originalRow: row }); } } BlockLink.blockArry.forEach(row => row[col] = null); topBlocks.sort((a, b) => a.originalRow - b.originalRow); for (let i = 0; i < topBlocks.length; i++) { const block = topBlocks[i].block; const targetRow = i; BlockLink.blockArry[targetRow][col] = block; const newX = this.mapConfig.gridOrigin.x + col * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - targetRow * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); } bottomBlocks.sort((a, b) => b.originalRow - a.originalRow); for (let i = 0; i < bottomBlocks.length; i++) { const block = bottomBlocks[i].block; const targetRow = this.mapConfig.rows - 1 - i; BlockLink.blockArry[targetRow][col] = block; const newX = this.mapConfig.gridOrigin.x + col * this.mapConfig.spacing; const newY = this.mapConfig.gridOrigin.y - targetRow * this.mapConfig.spacing; tween(block) .to(0.2, { position: v3(newX, newY, 0) }) .call(() => { this.isMoving = false; }) .start(); } } } } }