Files
cocos-hehe/assets/Scripts/GameBoard.ts
leiyuwei e9e6ddb8a7 重构: 合成大作战改为拖拽合成机制
- 全部代码重写,改为拖拽合成(drag-and-drop merge)
- 拖拽方块到相邻同等级方块上完成合成
- 拖到空格移动,拖到无效位置弹回
- Cocos Creator 3.8.8 全面适配
- 移除 GameManager.ts → 改为 GameController.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 12:12:39 +08:00

209 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 棋盘 — 8×12 网格管理、生成、移动、合并
*/
import { _decorator, Component, Node, UITransform, Color, Sprite, Vec3, v3 } from 'cc';
import { ROWS, COLS, CELL, PAD, MAX_LEVEL, MERGE_BASE_SCORE } from './BlockConfig';
import { Block } from './Block';
const { ccclass } = _decorator;
@ccclass('GameBoard')
export class GameBoard extends Component {
private grid: (Block | null)[][] = [];
private originX = 0;
private originY = 0;
/** 合并得分回调 */
onScore: ((pts: number) => void) | null = null;
// ============================================================
// 初始化
// ============================================================
onLoad() {
const w = COLS * (CELL + PAD) + PAD;
const h = ROWS * (CELL + PAD) + PAD;
this.originX = -w / 2 + PAD;
this.originY = -h / 2 + PAD;
this.initGrid();
this.drawBg(w, h);
}
private initGrid() {
this.grid = [];
for (let r = 0; r < ROWS; r++) {
this.grid[r] = [];
for (let c = 0; c < COLS; c++) {
this.grid[r][c] = null;
this.drawCell(r, c);
}
}
}
/** 每个格子的半透明底纹 */
private drawCell(r: number, c: number) {
const n = new Node('cell');
const s = n.addComponent(Sprite);
s.type = Sprite.Type.SIMPLE;
s.color = new Color(60, 60, 60, 80);
n.addComponent(UITransform).setContentSize(CELL, CELL);
n.setPosition(this.toWorld(r, c));
this.node.addChild(n);
}
/** 棋盘背景面板 */
private drawBg(w: number, h: number) {
const bg = new Node('bg');
const s = bg.addComponent(Sprite);
s.type = Sprite.Type.SIMPLE;
s.color = new Color(28, 28, 40, 220);
bg.addComponent(UITransform).setContentSize(w + 16, h + 16);
bg.setPosition(0, 0, -1);
this.node.addChild(bg);
}
// ============================================================
// 坐标
// ============================================================
toWorld(r: number, c: number): Vec3 {
return v3(
this.originX + c * (CELL + PAD) + CELL / 2,
this.originY + r * (CELL + PAD) + CELL / 2,
0,
);
}
/** 世界坐标 → 网格坐标,超出范围返回 null */
toGrid(x: number, y: number): { r: number; c: number } | null {
const c = Math.round((x - this.originX) / (CELL + PAD));
const r = Math.round((y - this.originY) / (CELL + PAD));
if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return null;
return { r, c };
}
// ============================================================
// 查询
// ============================================================
isEmpty(r: number, c: number) {
return r >= 0 && r < ROWS && c >= 0 && c < COLS && !this.grid[r][c];
}
get(r: number, c: number): Block | null {
if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return null;
return this.grid[r][c];
}
/** 四方向邻居 */
neighbors(r: number, c: number): Block[] {
const out: Block[] = [];
for (const [dr, dc] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) {
const b = this.get(r + dr, c + dc);
if (b) out.push(b);
}
return out;
}
// ============================================================
// 方块操作
// ============================================================
/** 在随机空位生成新方块 */
spawn(level: number): Block | null {
const empty: { r: number; c: number }[] = [];
for (let r = 0; r < ROWS; r++)
for (let c = 0; c < COLS; c++)
if (this.isEmpty(r, c)) empty.push({ r, c });
if (!empty.length) return null;
const g = empty[Math.floor(Math.random() * empty.length)];
return this._create(g.r, g.c, level);
}
/** 在指定位置创建方块 */
_create(r: number, c: number, level: number): Block {
const n = new Node('block');
const b = n.addComponent(Block);
b.init(r, c, level);
n.setPosition(this.toWorld(r, c));
this.node.addChild(n);
this.grid[r][c] = b;
return b;
}
/** 移动方块到新位置(目标必须为空) */
move(block: Block, nr: number, nc: number): boolean {
if (!this.isEmpty(nr, nc)) return false;
this.grid[block.row][block.col] = null;
block.row = nr;
block.col = nc;
this.grid[nr][nc] = block;
block.node.setPosition(this.toWorld(nr, nc));
return true;
}
/** 移除方块 */
remove(block: Block) {
this.grid[block.row][block.col] = null;
block.node.destroy();
}
// ============================================================
// 合并
// ============================================================
/** 尝试合并 a 和 b必须相邻同等级 */
tryMerge(a: Block, b: Block): boolean {
if (a.level !== b.level || a.isMerging || b.isMerging) return false;
if (Math.abs(a.row - b.row) + Math.abs(a.col - b.col) !== 1) return false;
this._doMerge(a, b);
return true;
}
/** 触发某个方块周围的合并检测 */
checkMerge(block: Block) {
if (block.isMerging) return;
for (const nb of this.neighbors(block.row, block.col)) {
if (nb.level === block.level && !nb.isMerging) {
this._doMerge(block, nb);
return;
}
}
}
private _doMerge(a: Block, b: Block) {
a.isMerging = true;
b.isMerging = true;
const lv = Math.min(a.level + 1, MAX_LEVEL);
this.remove(b);
a.upgradeTo(lv);
if (this.onScore) this.onScore(lv * MERGE_BASE_SCORE);
// 链式合并
this.scheduleOnce(() => this.checkMerge(a), 0.25);
}
// ============================================================
// 全局
// ============================================================
isFull(): boolean {
for (let r = 0; r < ROWS; r++)
for (let c = 0; c < COLS; c++)
if (!this.grid[r][c]) return false;
return true;
}
clearAll() {
for (let r = 0; r < ROWS; r++)
for (let c = 0; c < COLS; c++) {
const b = this.grid[r][c];
if (b) { b.node.destroy(); this.grid[r][c] = null; }
}
}
}