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);
|
|||
|
|
}
|
|||
|
|
}
|