233 lines
5.7 KiB
TypeScript
233 lines
5.7 KiB
TypeScript
import { Node, Vec3, tween } from "cc";
|
||
|
||
export interface DragCell {
|
||
r: number;
|
||
c: number;
|
||
type: number;
|
||
node: Node;
|
||
}
|
||
|
||
export interface DragGroup {
|
||
cells: DragCell[];
|
||
dirR: number;
|
||
dirC: number;
|
||
maxSteps: number;
|
||
}
|
||
|
||
export class DragManager {
|
||
private board: number[][];
|
||
private blockNodes: (Node | null)[][];
|
||
private rows: number;
|
||
private cols: number;
|
||
private blockSize: number;
|
||
|
||
constructor(
|
||
board: number[][],
|
||
blockNodes: (Node | null)[][],
|
||
rows: number,
|
||
cols: number,
|
||
blockSize: number,
|
||
) {
|
||
this.board = board;
|
||
this.blockNodes = blockNodes;
|
||
this.rows = rows;
|
||
this.cols = cols;
|
||
this.blockSize = blockSize;
|
||
}
|
||
|
||
/** 查找拖动组(从点击格子 + 方向出发) */
|
||
findDragGroup(
|
||
r: number,
|
||
c: number,
|
||
dirR: number,
|
||
dirC: number,
|
||
): { r: number; c: number }[] {
|
||
if (this.board[r][c] < 0) return [];
|
||
|
||
const group: { r: number; c: number }[] = [];
|
||
|
||
// 包含起点
|
||
group.push({ r, c });
|
||
|
||
if (dirR === 0 && dirC === 0) {
|
||
// 无方向,返回只有自己
|
||
return group;
|
||
}
|
||
|
||
if (dirR !== 0) {
|
||
// 垂直方向,向前方向查找
|
||
// 前面方向就是 dirR 方向(正负决定上或下)
|
||
|
||
let rr = r + dirR;
|
||
while (rr >= 0 && rr < this.rows) {
|
||
if (this.board[rr][c] >= 0) {
|
||
if (dirR > 0) {
|
||
// 向下,push后面
|
||
group.push({ r: rr, c });
|
||
} else {
|
||
// 向上,unshift前面
|
||
group.unshift({ r: rr, c });
|
||
}
|
||
rr += dirR;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
} else if (dirC !== 0) {
|
||
// 水平方向,向前方向查找
|
||
// 前面方向就是 dirC 方向(正负决定左或右)
|
||
|
||
let cc = c + dirC;
|
||
while (cc >= 0 && cc < this.cols) {
|
||
if (this.board[r][cc] >= 0) {
|
||
if (dirC > 0) {
|
||
// 向右,push后面
|
||
group.push({ r, c: cc });
|
||
} else {
|
||
// 向左,unshift前面
|
||
group.unshift({ r, c: cc });
|
||
}
|
||
cc += dirC;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return group;
|
||
}
|
||
|
||
/** 计算最多能移动多少格 */
|
||
countEmptyStepsGroup(
|
||
group: { r: number; c: number }[],
|
||
dirR: number,
|
||
dirC: number,
|
||
): number {
|
||
if (group.length === 0) return 0;
|
||
// 按滑动方向,找整组前方连续空格最小值
|
||
if (dirR !== 0) {
|
||
// 纵向滑动,找组中最顶部或最底部行号
|
||
const rows = group.map((g) => g.r);
|
||
const col = group[0].c;
|
||
const boundaryRow = dirR === 1 ? Math.max(...rows) : Math.min(...rows);
|
||
let count = 0;
|
||
let checkRow = boundaryRow + dirR;
|
||
while (
|
||
checkRow >= 0 &&
|
||
checkRow < this.rows &&
|
||
this.board[checkRow][col] === -1
|
||
) {
|
||
count++;
|
||
checkRow += dirR;
|
||
}
|
||
return count;
|
||
} else {
|
||
// 横向滑动,找组中最左或最右列号
|
||
const cols = group.map((g) => g.c);
|
||
const row = group[0].r;
|
||
const boundaryCol = dirC === 1 ? Math.max(...cols) : Math.min(...cols);
|
||
let count = 0;
|
||
let checkCol = boundaryCol + dirC;
|
||
while (
|
||
checkCol >= 0 &&
|
||
checkCol < this.cols &&
|
||
this.board[row][checkCol] === -1
|
||
) {
|
||
count++;
|
||
checkCol += dirC;
|
||
}
|
||
return count;
|
||
}
|
||
}
|
||
|
||
/** 执行拖动中的偏移 */
|
||
public applyDragOffset(
|
||
draggingGroup,
|
||
maxSteps: number,
|
||
dirR: number,
|
||
dirC: number,
|
||
dy: number,
|
||
dx: number,
|
||
) {
|
||
// 计算偏移量,滑动方向相反时置0
|
||
let offset = dirR !== 0 ? dy : dx;
|
||
|
||
if (
|
||
(dirR === -1 && offset <= 0) ||
|
||
(dirR === 1 && offset >= 0) ||
|
||
(dirC === 1 && offset <= 0) ||
|
||
(dirC === -1 && offset >= 0)
|
||
)
|
||
offset = 0;
|
||
|
||
const maxOffset = maxSteps * this.blockSize;
|
||
if (Math.abs(offset) > maxOffset) offset = maxOffset * Math.sign(offset);
|
||
|
||
// 整组方块移动
|
||
draggingGroup.forEach(({ r, c }) => {
|
||
const block = this.blockNodes[r][c];
|
||
if (!block) return;
|
||
const startPos = this.getPos(r, c);
|
||
if (dirR !== 0) {
|
||
block.setPosition(startPos.x, startPos.y + offset, 0);
|
||
} else {
|
||
block.setPosition(startPos.x + offset, startPos.y, 0);
|
||
}
|
||
});
|
||
}
|
||
|
||
getPos(row: number, col: number): Vec3 {
|
||
const x =
|
||
col * this.blockSize -
|
||
(this.cols * this.blockSize) / 2 +
|
||
this.blockSize / 2;
|
||
const y =
|
||
(this.rows * this.blockSize) / 2 -
|
||
row * this.blockSize -
|
||
this.blockSize / 2;
|
||
return new Vec3(x, y, 0);
|
||
}
|
||
|
||
/** 松手:确认移动 or 回弹 */
|
||
public async settleDrag(
|
||
blocksInfo: DragCell[],
|
||
reset: boolean,
|
||
steps?: number,
|
||
slidingDir?,
|
||
) {
|
||
if (reset) {
|
||
await this.resetGroup(blocksInfo);
|
||
} else {
|
||
await this.applyMove(blocksInfo, steps, slidingDir);
|
||
}
|
||
}
|
||
|
||
/** 执行动画移动 */
|
||
private async applyMove(blocksInfo: DragCell[], steps: number, slidingDir) {
|
||
blocksInfo.forEach(({ r, c, type, node }) => {
|
||
const tr = r + slidingDir.dirR * steps;
|
||
const tc = c + slidingDir.dirC * steps;
|
||
|
||
this.board[tr][tc] = type;
|
||
this.blockNodes[tr][tc] = node;
|
||
if (node) node.setPosition(this.getPos(tr, tc));
|
||
});
|
||
}
|
||
|
||
/** 回弹动画 */
|
||
private async resetGroup(blocksInfo) {
|
||
const promises = blocksInfo.map(({ r, c, type, node }) => {
|
||
const targetPos = this.getPos(r, c);
|
||
this.board[r][c] = type;
|
||
this.blockNodes[r][c] = node;
|
||
return new Promise<void>((resolve) => {
|
||
tween(node)
|
||
.to(0.15, { position: targetPos })
|
||
.call(() => resolve()) // ✅ 修复
|
||
.start();
|
||
});
|
||
});
|
||
await Promise.all(promises);
|
||
}
|
||
}
|