Includes core gameplay, 600 exported levels, visual assets, web bridge, and bootstrap scene. Co-authored-by: Cursor <cursoragent@cursor.com>
276 lines
9.6 KiB
TypeScript
276 lines
9.6 KiB
TypeScript
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();
|
||
}
|
||
}
|