268 lines
7.2 KiB
TypeScript
268 lines
7.2 KiB
TypeScript
import {
|
||
Node,
|
||
Prefab,
|
||
instantiate,
|
||
Sprite,
|
||
SpriteFrame,
|
||
UITransform,
|
||
Vec3,
|
||
} from "cc";
|
||
import { BoardShapes, BoardShapeName } from "./BoardShape";
|
||
|
||
export interface LevelConfig {
|
||
rows?: number;
|
||
cols?: number;
|
||
blockSize?: number;
|
||
tileTypes?: number;
|
||
currentShape?: BoardShapeName;
|
||
isTutorial?: boolean;
|
||
fixedBoard?: number[][];
|
||
}
|
||
|
||
export class LevelBuilder {
|
||
public board: number[][] = [];
|
||
public blockNodes: (Node | null)[][] = [];
|
||
public rows: number = 0;
|
||
public cols: number = 0;
|
||
|
||
private node: Node;
|
||
private blockPrefab: Prefab;
|
||
private spriteFrames: SpriteFrame[] = [];
|
||
private blockSize: number = 100;
|
||
private tileTypes: number = 5;
|
||
|
||
public currentShape: BoardShapeName = "rectangle";
|
||
private fixedBoard: number[][] | null = null;
|
||
|
||
constructor(
|
||
node: Node,
|
||
blockPrefab: Prefab,
|
||
spriteFrames: SpriteFrame[],
|
||
config?: LevelConfig,
|
||
) {
|
||
this.node = node;
|
||
this.blockPrefab = blockPrefab;
|
||
this.spriteFrames = spriteFrames;
|
||
|
||
if (config) {
|
||
this.blockSize = config.blockSize ?? this.blockSize;
|
||
this.tileTypes = config.tileTypes ?? this.tileTypes;
|
||
if (config.currentShape) this.currentShape = config.currentShape;
|
||
if (config.fixedBoard) this.fixedBoard = config.fixedBoard;
|
||
}
|
||
}
|
||
|
||
public buildBoard(): void {
|
||
if (this.fixedBoard) {
|
||
this.buildFixedBoard();
|
||
return;
|
||
}
|
||
|
||
const shapeTemplate = BoardShapes[this.currentShape];
|
||
if (!shapeTemplate) throw new Error(`Shape "${this.currentShape}" 未定义`);
|
||
|
||
const maxCols = Math.max(...shapeTemplate.map((row) => row.length));
|
||
const rows = shapeTemplate.length;
|
||
|
||
// 自动补齐矩形
|
||
const fixedTemplate: number[][] = [];
|
||
for (let r = 0; r < rows; r++) {
|
||
fixedTemplate[r] = [];
|
||
for (let c = 0; c < maxCols; c++) {
|
||
fixedTemplate[r][c] = shapeTemplate[r][c] ?? 0;
|
||
}
|
||
}
|
||
|
||
// 收集可用格子
|
||
const validCells = [];
|
||
for (let r = 0; r < rows; r++) {
|
||
for (let c = 0; c < maxCols; c++) {
|
||
if (fixedTemplate[r][c] === 1) validCells.push({ r, c });
|
||
}
|
||
}
|
||
|
||
if (validCells.length % 2 !== 0) throw new Error("有效格子数量必须为偶数");
|
||
|
||
// 随机生成配对
|
||
let tiles: number[] = [];
|
||
for (let i = 0; i < validCells.length / 2; i++) {
|
||
const id = i % this.tileTypes;
|
||
tiles.push(id, id);
|
||
}
|
||
for (let i = tiles.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[tiles[i], tiles[j]] = [tiles[j], tiles[i]];
|
||
}
|
||
|
||
let tileIndex = 0;
|
||
this.board = [];
|
||
this.blockNodes = [];
|
||
this.rows = rows;
|
||
this.cols = maxCols;
|
||
|
||
for (let r = 0; r < rows; r++) {
|
||
this.board[r] = [];
|
||
this.blockNodes[r] = [];
|
||
for (let c = 0; c < maxCols; c++) {
|
||
if (fixedTemplate[r][c] === 1) {
|
||
const tileId = tiles[tileIndex++];
|
||
this.board[r][c] = tileId;
|
||
const block = this.createNormalBlock(tileId);
|
||
block.setPosition(this.getPos(r, c));
|
||
this.node.addChild(block);
|
||
this.blockNodes[r][c] = block;
|
||
} else if (fixedTemplate[r][c] === -1) {
|
||
this.board[r][c] = -1;
|
||
this.blockNodes[r][c] = null;
|
||
} else {
|
||
this.board[r][c] = -2;
|
||
const stone = this.createStoneBlock();
|
||
stone.setPosition(this.getPos(r, c));
|
||
this.node.addChild(stone);
|
||
this.blockNodes[r][c] = stone;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private buildFixedBoard() {
|
||
this.rows = this.fixedBoard.length;
|
||
this.cols = this.fixedBoard[0].length;
|
||
this.board = [];
|
||
this.blockNodes = [];
|
||
|
||
for (let r = 0; r < this.rows; r++) {
|
||
this.board[r] = [];
|
||
this.blockNodes[r] = [];
|
||
for (let c = 0; c < this.cols; c++) {
|
||
const val = this.fixedBoard[r][c];
|
||
this.board[r][c] = val;
|
||
|
||
if (val >= 0) {
|
||
const block = this.createNormalBlock(val);
|
||
block.setPosition(this.getPos(r, c));
|
||
this.node.addChild(block);
|
||
this.blockNodes[r][c] = block;
|
||
} else if (val === -1) {
|
||
this.blockNodes[r][c] = null;
|
||
} else if (val === -2) {
|
||
const stone = this.createStoneBlock();
|
||
stone.setPosition(this.getPos(r, c));
|
||
this.node.addChild(stone);
|
||
this.blockNodes[r][c] = stone;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private createNormalBlock(tileId: number) {
|
||
const block = instantiate(this.blockPrefab);
|
||
const bgNode = block.getChildByName("Bg");
|
||
const bgUI =
|
||
bgNode.getComponent(UITransform) ?? bgNode.addComponent(UITransform);
|
||
bgUI.setContentSize(this.blockSize, this.blockSize);
|
||
|
||
const iconNode = block.getChildByName("Icon");
|
||
const iconUI =
|
||
iconNode.getComponent(UITransform) ?? iconNode.addComponent(UITransform);
|
||
const iconUI_sprite = iconNode.getComponent(Sprite)!;
|
||
iconUI_sprite.spriteFrame = this.spriteFrames[tileId];
|
||
iconUI.setContentSize(this.blockSize * 0.7, this.blockSize * 0.7);
|
||
iconNode.setPosition(0, 0, 0);
|
||
|
||
return block;
|
||
}
|
||
|
||
private createStoneBlock() {
|
||
const stone = instantiate(this.blockPrefab);
|
||
const bgNode = stone.getChildByName("Bg");
|
||
const bgUI =
|
||
bgNode.getComponent(UITransform) ?? bgNode.addComponent(UITransform);
|
||
bgUI.setContentSize(this.blockSize, this.blockSize);
|
||
|
||
const iconNode = stone.getChildByName("Icon");
|
||
iconNode.active = false;
|
||
|
||
return stone;
|
||
}
|
||
|
||
private 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);
|
||
}
|
||
|
||
/**
|
||
* 新建 UI 节点,并设置类型和位置
|
||
*/
|
||
public createBlockNode(type: number, r: number, c: number): Node {
|
||
let block: Node;
|
||
|
||
if (type >= 0) {
|
||
// 普通方块
|
||
block = this.createNormalBlock(type);
|
||
} else if (type === -2) {
|
||
// 石头障碍
|
||
block = this.createStoneBlock();
|
||
} else {
|
||
// 空格不需要节点
|
||
return null!;
|
||
}
|
||
|
||
block.setPosition(this.getPos(r, c));
|
||
this.node.addChild(block);
|
||
|
||
return block;
|
||
}
|
||
|
||
/**
|
||
* 更新节点的贴图/颜色/数字等显示
|
||
*/
|
||
public setBlockType(node: Node, type: number): void {
|
||
if (!node) return;
|
||
|
||
const iconNode = node.getChildByName("Icon");
|
||
if (!iconNode) return;
|
||
|
||
if (type >= 0) {
|
||
// 普通方块
|
||
iconNode.active = true;
|
||
const iconUI =
|
||
iconNode.getComponent(UITransform) ??
|
||
iconNode.addComponent(UITransform);
|
||
const iconUI_sprite = iconNode.getComponent(Sprite)!;
|
||
iconUI_sprite.spriteFrame = this.spriteFrames[type];
|
||
iconUI.setContentSize(this.blockSize * 0.7, this.blockSize * 0.7);
|
||
iconNode.setPosition(0, 0, 0);
|
||
} else if (type === -2) {
|
||
// 石头
|
||
iconNode.active = false;
|
||
} else {
|
||
// 空格
|
||
iconNode.active = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新节点的坐标位置(UI 上)
|
||
*/
|
||
public setBlockPosition(node: Node, r: number, c: number): void {
|
||
if (!node) return;
|
||
node.setPosition(this.getPos(r, c));
|
||
}
|
||
|
||
/**
|
||
* 销毁节点或隐藏
|
||
*/
|
||
public destroyBlockNode(node: Node): void {
|
||
if (!node) return;
|
||
node.destroy(); // 如果想复用可改为 node.active = false
|
||
}
|
||
}
|