Files
cocos-hehe/assets/Scripts/GameBoard.ts
leiyuwei 1342dfb078 fix: 所有 Graphics 节点添加 UITransform,修复 CC 3.8.8 空白问题
CC 3.8.8 中 UI 渲染管线要求 Graphics 节点自身必须有 UITransform 组件,
否则不会提交渲染。所有 Graphics 节点(方块/棋盘/UI)均已补充。
2026-06-16 13:46:05 +08:00

198 lines
5.6 KiB
TypeScript
Raw Permalink 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, Graphics, Color, UITransform, 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.drawBg(w, h);
this.drawCells();
this.initGrid();
}
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;
}
}
}
/** 用 Graphics 画棋盘背景 */
private drawBg(w: number, h: number) {
const g = new Node('bg');
const gfx = g.addComponent(Graphics);
g.addComponent(UITransform).setContentSize(w + 16, h + 16);
gfx.fillColor = new Color(28, 28, 40, 220);
gfx.roundRect(-w / 2 - 8, -h / 2 - 8, w + 16, h + 16, 8);
gfx.fill();
this.node.addChild(g);
}
/** 用 Graphics 画所有格子底纹 */
private drawCells() {
const g = new Node('cells');
const gfx = g.addComponent(Graphics);
g.addComponent(UITransform).setContentSize(COLS * (CELL + PAD), ROWS * (CELL + PAD));
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const p = this.toWorld(r, c);
const s = CELL - 4;
const h = s / 2;
gfx.fillColor = new Color(60, 60, 60, 80);
gfx.roundRect(p.x - h, p.y - h, s, s, 4);
gfx.fill();
}
}
this.node.addChild(g);
}
// ============================================================
// 坐标
// ============================================================
toWorld(r: number, c: number): Vec3 {
return v3(
this.originX + c * (CELL + PAD) + CELL / 2,
this.originY + r * (CELL + PAD) + CELL / 2,
0,
);
}
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();
}
// ============================================================
// 合并
// ============================================================
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; }
}
}
}