Initial Cocos Creator port of main-site Unity WebGL game.

Includes core gameplay, 600 exported levels, visual assets, web bridge, and bootstrap scene.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-22 14:57:46 +08:00
commit cba5105908
88 changed files with 13798 additions and 0 deletions

View File

@@ -0,0 +1,275 @@
import {
_decorator, Component, Node, Enum, CCString, CCInteger, CCBoolean, game,
} from 'cc';
import { GameManager } from './manager/GameManager';
import { Skin } from './core/Define';
import { VisualAssets } from './visual/VisualAssets';
import { getLevelCount } from './level/LevelRegistry';
const { ccclass, property, executionOrder } = _decorator;
/** UI 主题(与 Unity ChangeUIStyle 参数一致) */
export enum UIStyleType {
default = 0,
chinese = 1,
redArmy = 2,
numMan = 3,
snow = 4,
sanxing = 5,
}
const UIStyleNames = ['default', 'chinese', 'redArmy', 'numMan', 'snow', 'sanxing'];
/** 多人角色 */
export enum MultRole {
PlayerA1 = 0,
PlayerA2 = 1,
PlayerA3 = 2,
PlayerB1 = 3,
PlayerB2 = 4,
PlayerB3 = 5,
}
const MultRoleNames = ['PlayerA1', 'PlayerA2', 'PlayerA3', 'PlayerB1', 'PlayerB2', 'PlayerB3'];
/**
* 对应 Unity 场景 GameController + Inspector 调试面板TestGame2
* 挂在 GameController 节点,与 GameManager 同级或同节点
*/
@ccclass('GameController')
@executionOrder(-50)
export class GameController extends Component {
@property({ group: { name: '场景', id: '1' }, type: Node, displayName: 'Main Level Entrance' })
mainLevelEntrance: Node | null = null;
@property({ group: { name: '场景', id: '1' }, type: Node, displayName: 'Cur Level', readonly: true })
curLevelNode: Node | null = null;
@property({ group: { name: '关卡', id: '2' }, type: CCInteger, displayName: 'Initial Level ID' })
initialLevelID = 1;
@property({ group: { name: '关卡', id: '2' }, type: CCInteger, displayName: 'Cur Level ID', readonly: true })
curLevelID = 1;
@property({ group: { name: '关卡', id: '2' }, displayName: '已注册关卡数', readonly: true })
registeredLevelCount = 0;
@property({ group: { name: '角色', id: '3' }, type: Enum(Skin), displayName: 'Player Skin' })
playerSkin: Skin = Skin.Silu;
@property({ group: { name: '多人', id: '4' }, displayName: 'Mult Mode' })
multMode = false;
@property({ group: { name: '多人', id: '4' }, type: Enum(MultRole), displayName: 'Mult Player Role' })
multPlayerRoleEnum: MultRole = MultRole.PlayerA1;
@property({ group: { name: '主题', id: '5' }, type: Enum(UIStyleType), displayName: 'UI Style' })
uiStyleEnum: UIStyleType = UIStyleType.default;
// --- 与 Unity Inspector 输入框一致 ---
@property({ group: { name: '调试输入', id: '6' }, displayName: 'inputLevel' })
inputLevel = '1';
@property({ group: { name: '调试输入', id: '6' }, displayName: 'inputStyle' })
inputStyle = 'default';
@property({ group: { name: '调试输入', id: '6' }, multiline: true, displayName: 'coinStr' })
coinStr = '["-2,0","-2,2","-1,2"]';
// --- 播放时勾选即执行(等同 Unity 按钮) ---
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ SwitchLevel' })
execSwitchLevel = false;
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ EndInput' })
execEndInput = false;
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ ChangeStyle' })
execChangeStyle = false;
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ StartMultPlay' })
execStartMultPlay = false;
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ Mute' })
execMute = false;
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ Unmute' })
execUnmute = false;
private gm!: GameManager;
onLoad() {
this.gm = this.getComponent(GameManager) || this.node.getComponentInChildren(GameManager)!;
if (!this.gm) {
this.gm = this.node.addComponent(GameManager);
}
this.syncToManager();
this.registerWebApi();
this.registeredLevelCount = getLevelCount();
}
start() {
this.refreshReadonly();
}
update() {
if (!game.isRunning()) return;
this.refreshReadonly();
this.pollInspectorActions();
}
private refreshReadonly() {
this.curLevelID = this.gm?.curLevelID ?? this.curLevelID;
if (this.gm?.mainLevelEntrance) {
this.mainLevelEntrance = this.gm.mainLevelEntrance;
const lv = this.gm.mainLevelEntrance.children.find((c) => c.name.startsWith('Level_'));
this.curLevelNode = lv ?? null;
}
this.multMode = this.gm?.multMode ?? false;
}
private syncToManager() {
if (!this.gm) return;
if (this.mainLevelEntrance) this.gm.mainLevelEntrance = this.mainLevelEntrance;
this.gm.initialLevelID = this.initialLevelID;
this.gm.playerSkin = this.playerSkin;
this.gm.multMode = this.multMode;
this.gm.multPlayerRole = MultRoleNames[this.multPlayerRoleEnum] ?? 'PlayerA1';
this.gm.uiStyle = UIStyleNames[this.uiStyleEnum] ?? 'default';
this.gm.curLevelID = this.initialLevelID;
}
private pollInspectorActions() {
if (this.execSwitchLevel) {
this.execSwitchLevel = false;
this.onInspectorSwitchLevel();
}
if (this.execEndInput) {
this.execEndInput = false;
this.callSetIsInputEnd(1);
}
if (this.execChangeStyle) {
this.execChangeStyle = false;
this.onInspectorChangeStyle();
}
if (this.execStartMultPlay) {
this.execStartMultPlay = false;
this.onInspectorStartMultPlay();
}
if (this.execMute) {
this.execMute = false;
this.callMute();
}
if (this.execUnmute) {
this.execUnmute = false;
this.callUnmute();
}
}
// --- Inspector / SendMessage 共用 ---
onInspectorSwitchLevel() {
const id = parseInt(this.inputLevel, 10);
if (Number.isNaN(id)) {
console.warn('[GameController] inputLevel 无效:', this.inputLevel);
return;
}
this.syncToManager();
this.gm.switchLevel(id);
this.curLevelID = id;
console.log('[GameController] SwitchLevel', id);
}
onInspectorChangeStyle() {
const style = this.inputStyle.trim() || UIStyleNames[this.uiStyleEnum];
this.gm.changeUIStyle(style);
this.uiStyleEnum = UIStyleNames.indexOf(style) as UIStyleType;
if (this.uiStyleEnum < 0) this.uiStyleEnum = UIStyleType.default;
console.log('[GameController] ChangeUIStyle', style);
}
onInspectorStartMultPlay() {
const role = MultRoleNames[this.multPlayerRoleEnum] ?? 'PlayerA1';
this.gm.startMultPlay(role, this.coinStr);
this.multMode = true;
console.log('[GameController] StartMultPlay', role, this.coinStr);
}
applyPlayerSkinFromInspector() {
const player = this.gm.findNodeByName('Player')
?? this.gm.findNodeByName(this.gm.multPlayerRole);
const pc = player?.getComponent('PlayerController') as { callChangeSkin?: (n: number) => void };
pc?.callChangeSkin?.(this.playerSkin);
this.gm.playerSkin = this.playerSkin;
}
// --- JS Bridge (SendMessage) ---
private registerWebApi() {
const api = {
SendMessage: (objectName: string, methodName: string, param?: string | number) => {
this.sendMessage(objectName, methodName, param);
},
};
if (typeof window !== 'undefined') {
(window as unknown as { cocosIns?: typeof api }).cocosIns = api;
(window as unknown as { unityInstance?: typeof api }).unityInstance = api;
}
}
sendMessage(objectName: string, methodName: string, param?: string | number) {
if (objectName === 'GameController') {
this.invoke(this, methodName, param);
return;
}
if (objectName === 'UIMain') {
const n = this.gm.findNodeByName('UIMain');
const c = n?.getComponent('UIMain');
if (c) this.invoke(c, methodName, param);
return;
}
const node = this.gm.findNodeByName(objectName);
if (!node) {
console.warn(`SendMessage: 未找到 ${objectName}`);
return;
}
for (const comp of node.getComponents(Component)) {
if (typeof (comp as Record<string, unknown>)[methodName] === 'function') {
this.invoke(comp, methodName, param);
return;
}
}
console.warn(`SendMessage: ${objectName}.${methodName} 无此方法`);
}
private invoke(target: object, methodName: string, param?: string | number) {
const fn = (target as Record<string, unknown>)[methodName];
if (typeof fn !== 'function') return;
if (param === undefined) (fn as () => void).call(target);
else (fn as (p: string | number) => void).call(target, param);
}
switchLevel(levelID: number) {
this.inputLevel = String(levelID);
this.onInspectorSwitchLevel();
}
callSetIsInputEnd(v: number) {
this.gm.callSetIsInputEnd(v);
}
changeUIStyle(style: string) {
this.inputStyle = style;
this.onInspectorChangeStyle();
}
startMultPlay(role: string, coins: string) {
this.coinStr = coins;
const idx = MultRoleNames.indexOf(role);
if (idx >= 0) this.multPlayerRoleEnum = idx as MultRole;
this.onInspectorStartMultPlay();
}
callMute() {
this.gm.callMute();
}
callUnmute() {
this.gm.callUnmute();
}
}