Files
cocos-hehe/assets/Scripts/GameManager.ts
leiyuwei a90a8c7aed feat: 适配 Cocos Creator 3.8.8 + 新增详细使用教程
- 移除手动创建的场景文件(3.8.8 需在编辑器中创建)
- 移除旧的 .meta 文件(3.8.8 会自动重新生成)
- 重写所有脚本,优化代码结构和 3.8.8 兼容性
- 新增完整使用教程 TUTORIAL.md
- 更新 .gitignore 排除 .claude/ 目录

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

291 lines
9.7 KiB
TypeScript

/**
* 游戏主控制器 - UI、计分、输入、游戏流程
* Cocos Creator 3.8.8
*/
import { _decorator, Component, Node, Label, Color, Sprite, UITransform, Vec3, v3, EventTouch } from 'cc';
import { GameBoard } from './GameBoard';
import { Block } from './Block';
import { ROWS, COLS, CELL_SIZE, BOARD_PADDING, SPAWN_INTERVAL, getRandomSpawnLevel } from './BlockConfig';
const { ccclass, property } = _decorator;
const enum GameState { IDLE, PLAYING, GAME_OVER }
@ccclass('GameManager')
export class GameManager extends Component {
// Cocos Creator 属性面板可见(可在此处拖拽引用)
@property({ displayName: '生成间隔(秒)', min: 1, max: 10 })
private spawnInterval: number = SPAWN_INTERVAL;
// ---- 内部状态 ----
private board: GameBoard | null = null;
private boardNode: Node | null = null;
private score = 0;
private spawnTimer = 0;
private selected: Block | null = null;
private state: GameState = GameState.IDLE;
// ---- UI 节点 ----
private scoreLabel: Label | null = null;
private gameOverNode: Node | null = null;
private finalScoreLabel: Label | null = null;
// ============================================================
// 生命周期
// ============================================================
onLoad() {
this.buildUI();
this.buildBoard();
}
start() {
this.startGame();
}
update(dt: number) {
if (this.state !== GameState.PLAYING) return;
this.spawnTimer += dt;
if (this.spawnTimer >= this.spawnInterval) {
this.spawnTimer = 0;
this.spawnBlock();
}
}
// ============================================================
// UI 构建
// ============================================================
private buildUI() {
const root = this.node;
const rh = 960; // Canvas 设计高度
// ---- 背景 ----
{
const bg = new Node('Bg');
const s = bg.addComponent(Sprite);
s.type = Sprite.Type.SIMPLE;
s.color = new Color(25, 25, 35, 255);
bg.addComponent(UITransform).setContentSize(600, 1200);
bg.setPosition(0, 0, -2);
root.addChild(bg);
}
const boardH = ROWS * (CELL_SIZE + BOARD_PADDING);
const topY = boardH / 2 + 60; // 棋盘中点之上的偏移
// ---- 记分板底 ----
{
const sb = new Node('ScoreBg');
const s = sb.addComponent(Sprite);
s.type = Sprite.Type.SIMPLE;
s.color = new Color(40, 40, 50, 220);
sb.addComponent(UITransform).setContentSize(480, 60);
sb.setPosition(0, topY, 0);
root.addChild(sb);
}
// ---- 标题 ----
{
const t = new Node('Title');
const l = t.addComponent(Label);
l.string = '合成大作战';
l.fontSize = 20;
l.color = new Color(200, 200, 200, 255);
t.addComponent(UITransform).setContentSize(160, 30);
t.setPosition(0, topY + 45, 0);
root.addChild(t);
}
// ---- 分数 ----
{
const sn = new Node('ScoreLabel');
this.scoreLabel = sn.addComponent(Label);
this.scoreLabel.string = '得分: 0';
this.scoreLabel.fontSize = 28;
this.scoreLabel.color = Color.WHITE.clone();
sn.addComponent(UITransform).setContentSize(300, 40);
sn.setPosition(0, topY, 0);
root.addChild(sn);
}
// ---- 游戏结束遮罩 ----
{
const go = new Node('GameOver');
const s = go.addComponent(Sprite);
s.type = Sprite.Type.SIMPLE;
s.color = new Color(0, 0, 0, 180);
go.addComponent(UITransform).setContentSize(600, 1200);
go.setPosition(0, 0, 10);
go.active = false;
root.addChild(go);
this.gameOverNode = go;
// "游戏结束"
{
const n = new Node('GoLabel');
const l = n.addComponent(Label);
l.string = '游戏结束';
l.fontSize = 48;
l.color = new Color(255, 200, 50, 255);
n.addComponent(UITransform).setContentSize(300, 80);
n.setPosition(0, 80, 11);
go.addChild(n);
}
// 最终得分
{
const n = new Node('FinalScore');
this.finalScoreLabel = n.addComponent(Label);
this.finalScoreLabel.string = '最终得分: 0';
this.finalScoreLabel.fontSize = 32;
this.finalScoreLabel.color = Color.WHITE.clone();
n.addComponent(UITransform).setContentSize(300, 50);
n.setPosition(0, 10, 11);
go.addChild(n);
}
// 重新开始按钮
{
const btn = new Node('RestartBtn');
const s = btn.addComponent(Sprite);
s.type = Sprite.Type.SIMPLE;
s.color = new Color(76, 175, 80, 255);
btn.addComponent(UITransform).setContentSize(200, 60);
btn.setPosition(0, -80, 11);
const ln = new Node('BtnLabel');
const l = ln.addComponent(Label);
l.string = '重新开始';
l.fontSize = 28;
l.color = Color.WHITE.clone();
ln.addComponent(UITransform).setContentSize(200, 40);
ln.setPosition(0, 0, 12);
btn.addChild(ln);
btn.on(Node.EventType.TOUCH_END, () => this.restartGame());
go.addChild(btn);
}
}
}
// ============================================================
// 棋盘
// ============================================================
private buildBoard() {
this.boardNode = new Node('Board');
this.board = this.boardNode.addComponent(GameBoard);
this.board.onScoreUpdate = (pts) => this.addScore(pts);
this.node.addChild(this.boardNode);
}
// ============================================================
// 游戏流程
// ============================================================
private startGame() {
this.score = 0;
this.spawnTimer = 0;
this.selected = null;
this.state = GameState.PLAYING;
if (this.scoreLabel) this.scoreLabel.string = '得分: 0';
if (this.gameOverNode) this.gameOverNode.active = false;
// 初始生成 3 个
if (this.board) {
for (let i = 0; i < 3; i++) {
this.board.spawnRandom(getRandomSpawnLevel());
}
}
// 注册触摸
this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
}
private spawnBlock() {
if (!this.board) return;
if (!this.board.spawnRandom(getRandomSpawnLevel()) && this.board.isBoardFull()) {
this.gameOver();
}
}
private addScore(pts: number) {
this.score += pts;
if (this.scoreLabel) this.scoreLabel.string = `得分: ${this.score}`;
}
private gameOver() {
this.state = GameState.GAME_OVER;
if (this.gameOverNode) this.gameOverNode.active = true;
if (this.finalScoreLabel) this.finalScoreLabel.string = `最终得分: ${this.score}`;
}
private restartGame() {
if (this.board) this.board.clearAll();
this.startGame();
}
// ============================================================
// 输入处理
// ============================================================
private onTouchEnd(evt: EventTouch) {
if (this.state !== GameState.PLAYING || !this.board) return;
// 屏幕坐标 → Canvas 节点空间坐标
const uiPos = evt.getUILocation();
const uiTransform = this.node.getComponent(UITransform);
if (!uiTransform) return;
const local = uiTransform.convertToNodeSpaceAR(v3(uiPos.x, uiPos.y, 0));
const g = this.board.worldToGrid(local.x, local.y);
if (!g) { this.clearSelected(); return; }
const { row, col } = g;
const tapped = this.board.getBlock(row, col);
if (tapped) {
// 点击了方块
if (this.selected) {
const sel = this.selected;
if (this.isAdj(sel, tapped) && sel.level === tapped.level) {
this.board.tryMerge(sel, tapped);
this.clearSelected();
} else {
this.clearSelected();
tapped.setSelected(true);
this.selected = tapped;
}
} else {
tapped.setSelected(true);
this.selected = tapped;
}
} else if (this.selected && this.board.isEmpty(row, col)) {
// 点击空格 → 移动选中方块
if (this.board.moveBlock(this.selected, row, col)) {
const block = this.selected;
this.clearSelected();
this.scheduleOnce(() => {
if (block.isValid) this.board!.triggerMergeCheck(block);
}, 0.15);
} else {
this.clearSelected();
}
} else {
this.clearSelected();
}
}
private isAdj(a: Block, b: Block): boolean {
return (Math.abs(a.row - b.row) === 1 && a.col === b.col) ||
(Math.abs(a.col - b.col) === 1 && a.row === b.row);
}
private clearSelected() {
if (this.selected) {
this.selected.setSelected(false);
this.selected = null;
}
}
}