Complete Cocos Creator port with level bundles, themes, and tooling.

Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 15:30:58 +08:00
parent cba5105908
commit d393302388
6248 changed files with 17322729 additions and 11036 deletions

View File

@@ -1,19 +1,48 @@
import {
_decorator, Component, Node, Canvas, Camera, UITransform, view, Color,
director, Label, find,
_decorator, Component, Node, Camera, Canvas, UITransform, view, Color,
director, find, Layers,
} from 'cc';
import { GameController } from './GameController';
import { GameManager } from './manager/GameManager';
import { VisualAssets } from './visual/VisualAssets';
import { ViewController } from './controller/ViewController';
import { LineGridRenderer } from './gameplay/LineGridRenderer';
import { UIMain } from './ui/UIMain';
import { loadLevelDatabase, refreshLevelIdBounds, LEVEL_ID_BASE } from './level/LevelRegistry';
import { loadThemeDatabase } from './theme/ThemeRegistry';
import { ensureResourcesBundle } from './core/ResourcesBundle';
import { loadTileDisplayMeta } from './visual/TileDisplayMeta';
import { ThemeBackground } from './theme/ThemeBackground';
import { GridSnapHelper } from './level/GridSnapHelper';
import { GameAudio } from './audio/GameAudio';
import { GameplayDebugBar } from './ui/GameplayDebugBar';
import {
CELL_PIXEL, CAMERA_ORTHO_HALF, DESIGN_WIDTH, DESIGN_HEIGHT,
} from './core/GridConstants';
import { applyEmbeddedDesignResolution } from './core/EmbeddedView';
export {
CELL_PIXEL, CAMERA_ORTHO_HALF, DESIGN_WIDTH, DESIGN_HEIGHT,
} from './core/GridConstants';
const { ccclass, executionOrder } = _decorator;
/** 每格像素尺寸UI 坐标) */
export const CELL_PIXEL = 56;
/** HUD 专用层:与关卡 UI_2D 分离,不受主相机缩放/拖拽影响 */
const HUD_LAYER = Layers.Enum.UI_3D;
/** 背景专用层BgCamera 固定正交,主相机缩放/平移时不跟随 */
const BG_LAYER = Layers.Enum.DEFAULT;
@ccclass('AppBootstrap')
@executionOrder(-100)
export class AppBootstrap extends Component {
private readonly onCanvasResize = () => {
const sync = (globalThis as { __tfrhSyncEmbeddedCanvas?: () => void }).__tfrhSyncEmbeddedCanvas;
if (typeof sync === 'function') sync();
else applyEmbeddedDesignResolution();
};
onDestroy() {
view.off('canvas-resize', this.onCanvasResize, this);
}
async onLoad() {
try {
await this.bootstrap();
@@ -24,94 +53,200 @@ export class AppBootstrap extends Component {
private async bootstrap() {
console.log('[AppBootstrap] 开始初始化…');
await VisualAssets.preload();
applyEmbeddedDesignResolution();
view.on('canvas-resize', this.onCanvasResize, this);
await ensureResourcesBundle();
await loadThemeDatabase();
await loadTileDisplayMeta();
await loadLevelDatabase();
refreshLevelIdBounds();
await GameAudio.preload();
const scene = director.getScene()!;
find('UICanvas', scene)?.destroy();
let mainCam = find('Main Camera', scene)?.getComponent(Camera) ?? null;
if (!mainCam) {
const camNode = new Node('Main Camera');
camNode.parent = scene;
mainCam = camNode.addComponent(Camera);
} else if (mainCam.node.parent !== scene) {
mainCam.node.parent = scene;
}
this.setupCamera(mainCam);
mainCam.node.getComponent(ViewController) ?? mainCam.node.addComponent(ViewController);
const light = find('Main Light', scene);
if (light) light.active = false;
let canvasNode = find('Canvas', scene);
if (!canvasNode) {
canvasNode = new Node('Canvas');
canvasNode.parent = scene;
canvasNode.addComponent(Canvas);
}
const canvas = canvasNode.getComponent(Canvas)!;
canvas.cameraComponent = mainCam;
let canvasUi = canvasNode.getComponent(UITransform);
if (!canvasUi) canvasUi = canvasNode.addComponent(UITransform);
const size = view.getVisibleSize();
canvasUi.setContentSize(size.width, size.height);
const gameRoot = this.ensureGameRoot(scene, size, mainCam);
this.ensureBgOverlay(scene, size, mainCam);
const uiOverlay = this.ensureUIOverlay(scene, size, mainCam);
this.bindAllCanvases(scene, mainCam);
let gameRoot = canvasNode.getChildByName('GameRoot');
if (!gameRoot) {
gameRoot = new Node('GameRoot');
gameRoot.parent = canvasNode;
const grUi = gameRoot.addComponent(UITransform);
grUi.setContentSize(size.width, size.height);
}
const host = this.node;
const ctl = host.getComponent(GameController) ?? host.addComponent(GameController);
let gcNode = scene.getChildByName('GameController');
if (!gcNode) {
gcNode = new Node('GameController');
gcNode.parent = scene;
gcNode.addComponent(GameManager);
gcNode.addComponent(GameController);
}
const gctl = gcNode.getComponent(GameController);
const gm = gcNode.getComponent(GameManager)!;
if (gctl) {
if (gctl.mainLevelEntrance) gm.mainLevelEntrance = gctl.mainLevelEntrance;
gm.initialLevelID = gctl.initialLevelID;
gm.playerSkin = gctl.playerSkin;
}
let entrance = gameRoot.getChildByName('MainLevelEntrance');
if (!entrance) {
entrance = new Node('MainLevelEntrance');
entrance.parent = gameRoot;
const eUi = entrance.addComponent(UITransform);
eUi.setContentSize(size.width, size.height);
entrance.addComponent(UITransform).setContentSize(size.width, size.height);
}
gm.mainLevelEntrance = entrance;
gm.initialLevelID = 1;
entrance.layer = Layers.Enum.UI_2D;
LineGridRenderer.ensure(entrance);
GridSnapHelper.purgeRuntimeGrids(entrance);
this.ensureHint(canvasNode);
ctl.mainLevelEntrance = entrance;
if (ctl.initialLevelID <= 0) ctl.initialLevelID = LEVEL_ID_BASE;
if (!ctl.inputLevel?.trim()) ctl.inputLevel = String(ctl.initialLevelID);
await gm.createNewLevel(gm.initialLevelID);
console.log('[AppBootstrap] 关卡已加载');
this.removeRuntimeOverlayUI(gameRoot);
gameRoot.getChildByName('UIMain')?.destroy();
UIMain.ensure(uiOverlay);
GameplayDebugBar.ensure(uiOverlay);
ThemeBackground.purgeStaleNodes(entrance);
// 关卡预制体由 SwitchLevel → createNewLevel 按需加载loader 进关再下 levels_all
ctl.markReady();
ctl.onBootstrapReady();
console.log('[AppBootstrap] 引擎已就绪SwitchLevel 进关时再加载关卡预制体');
}
private ensureGameRoot(scene: Node, size: { width: number; height: number }, mainCam: Camera): Node {
let gameRoot = find('GameRoot', scene) ?? find('Canvas', scene);
if (!gameRoot) {
gameRoot = new Node('GameRoot');
gameRoot.parent = scene;
}
gameRoot.name = 'GameRoot';
gameRoot.layer = Layers.Enum.UI_2D;
gameRoot.active = true;
const ui = gameRoot.getComponent(UITransform) ?? gameRoot.addComponent(UITransform);
ui.setContentSize(size.width, size.height);
const canvas = gameRoot.getComponent(Canvas) ?? gameRoot.addComponent(Canvas);
canvas.cameraComponent = mainCam;
canvas.alignCanvasWithScreen = true;
return gameRoot;
}
/** 固定背景相机:先于主相机绘制,缩放/拖拽关卡时背景不动 */
private ensureBgOverlay(scene: Node, size: { width: number; height: number }, mainCam: Camera): Node {
let bgCamNode = find('BgCamera', scene);
if (!bgCamNode) {
bgCamNode = new Node('BgCamera');
bgCamNode.parent = scene;
}
bgCamNode.setPosition(0, 0, 1000);
bgCamNode.setRotationFromEuler(0, 0, 0);
const bgCam = bgCamNode.getComponent(Camera) ?? bgCamNode.addComponent(Camera);
bgCam.projection = Camera.ProjectionType.ORTHO;
bgCam.orthoHeight = CAMERA_ORTHO_HALF;
bgCam.near = 1;
bgCam.far = 2000;
bgCam.priority = mainCam.priority - 10;
bgCam.clearFlags = Camera.ClearFlag.SOLID_COLOR;
bgCam.clearColor = new Color(1, 1, 1, 255);
bgCam.visibility = BG_LAYER;
let overlay = find('BgOverlay', scene);
if (!overlay) {
overlay = new Node('BgOverlay');
overlay.parent = scene;
}
overlay.layer = BG_LAYER;
overlay.active = true;
const ui = overlay.getComponent(UITransform) ?? overlay.addComponent(UITransform);
ui.setContentSize(size.width, size.height);
const canvas = overlay.getComponent(Canvas) ?? overlay.addComponent(Canvas);
canvas.cameraComponent = bgCam;
canvas.alignCanvasWithScreen = true;
return overlay;
}
/** 固定 HUD 相机:缩放/平移主相机时按钮位置不变(对齐 Unity Screen Space Overlay */
private ensureUIOverlay(scene: Node, size: { width: number; height: number }, mainCam: Camera): Node {
let uiCamNode = find('UICamera', scene);
if (!uiCamNode) {
uiCamNode = new Node('UICamera');
uiCamNode.parent = scene;
}
uiCamNode.setPosition(0, 0, 1000);
uiCamNode.setRotationFromEuler(0, 0, 0);
const uiCam = uiCamNode.getComponent(Camera) ?? uiCamNode.addComponent(Camera);
uiCam.projection = Camera.ProjectionType.ORTHO;
uiCam.orthoHeight = CAMERA_ORTHO_HALF;
uiCam.near = 1;
uiCam.far = 2000;
uiCam.priority = mainCam.priority + 10;
uiCam.clearFlags = Camera.ClearFlag.DEPTH_STENCIL;
uiCam.visibility = HUD_LAYER;
let overlay = find('UIOverlay', scene);
if (!overlay) {
overlay = new Node('UIOverlay');
overlay.parent = scene;
}
overlay.layer = HUD_LAYER;
overlay.active = true;
const ui = overlay.getComponent(UITransform) ?? overlay.addComponent(UITransform);
ui.setContentSize(size.width, size.height);
const canvas = overlay.getComponent(Canvas) ?? overlay.addComponent(Canvas);
canvas.cameraComponent = uiCam;
canvas.alignCanvasWithScreen = true;
return overlay;
}
private bindAllCanvases(scene: Node, mainCam: Camera) {
const uiCam = find('UICamera', scene)?.getComponent(Camera) ?? null;
const bgCam = find('BgCamera', scene)?.getComponent(Camera) ?? null;
for (const canvas of scene.getComponentsInChildren(Canvas)) {
if (canvas.node.name === 'BgOverlay') {
if (bgCam) canvas.cameraComponent = bgCam;
canvas.alignCanvasWithScreen = true;
continue;
}
if (canvas.node.name === 'UIOverlay') {
if (uiCam) canvas.cameraComponent = uiCam;
canvas.alignCanvasWithScreen = true;
continue;
}
if (!canvas.cameraComponent?.isValid) {
canvas.cameraComponent = mainCam;
}
canvas.alignCanvasWithScreen = true;
}
// 清理无相机 Canvas避免 PointerEventDispatcher 读 null.cameraPriority
for (const canvas of scene.getComponentsInChildren(Canvas)) {
if (!canvas.cameraComponent) {
canvas.destroy();
}
}
}
private removeRuntimeOverlayUI(gameRoot: Node) {
gameRoot.getChildByName('Hint')?.destroy();
gameRoot.getChildByName('LevelSwitchBar')?.destroy();
}
private setupCamera(cam: Camera) {
const camNode = cam.node;
camNode.setPosition(0, 0, 1000);
camNode.setRotationFromEuler(0, 0, 0);
camNode.layer = Layers.Enum.DEFAULT;
cam.projection = Camera.ProjectionType.ORTHO;
cam.orthoHeight = 360;
cam.orthoHeight = CAMERA_ORTHO_HALF;
cam.near = 1;
cam.far = 2000;
cam.clearFlags = Camera.ClearFlag.SOLID_COLOR;
cam.clearColor = new Color(30, 40, 60, 255);
cam.clearFlags = Camera.ClearFlag.DEPTH_STENCIL;
cam.visibility = Layers.Enum.UI_2D;
}
private ensureHint(canvasNode: Node) {
if (canvasNode.getChildByName('Hint')) return;
const hint = new Node('Hint');
hint.parent = canvasNode;
const ui = hint.addComponent(UITransform);
ui.setContentSize(400, 40);
hint.setPosition(0, 280, 0);
const label = hint.addComponent(Label);
label.string = '主站 Cocos · 关卡运行中';
label.fontSize = 22;
label.color = new Color(200, 220, 255);
switchLevelFromBootstrap() {
const gc = this.getComponent(GameController);
if (!gc) {
console.warn('[AppBootstrap] 未找到 GameController请先点预览 ▶');
return;
}
gc.clickSwitchLevel();
}
}