import { _decorator, Component, director, Node, Label, Button, UITransform, Color, Vec3, Graphics } from 'cc'; import { UIFlowMgr, ISceneEnter } from '../ui/UIFlowMgr'; import { DESIGN_WIDTH, DESIGN_HEIGHT } from '../common/Constants'; const { ccclass, property } = _decorator; /** Maps abstract SceneId → physical Cocos Creator scene name. */ const SCENE_MAP: Record = { boot: 'Boot', story_intro: 'StoryIntro', main_menu: 'MainMenu', // level_select currently reuses MainMenu until a dedicated LevelSelect scene exists. level_select: 'MainMenu', // `gameplay` is dispatched by levelId — resolved at runtime. gameplay: 'Level_1_1', settlement: 'Settlement', // Settings panel is overlayed on MainMenu for the MVP. settings: 'MainMenu', }; /** levelId → physical scene name mapping (chapter 1 only). */ const LEVEL_SCENE_MAP: Record = { '1-1': 'Level_1_1', '1-2': 'Level_1_2', '1-3': 'Level_1_3', '1-4': 'Level_1_4', '1-5': 'Level_1_5', '1-5-boss': 'Boss_ShuangHuanFang', }; /** * MainMenu scene entry (task 9.2 hookup). * * Owns a `UIFlowMgr` instance and translates each abstract `onSceneEnter` * callback into a concrete `director.loadScene` call. Attach this component * to the root node of `MainMenu.scene`. * * When `autoBuildUI` is enabled (default), two centered buttons (Start / * Settings) and a title label are created programmatically so the scene is * usable out of the box even before any art pass. */ @ccclass('MainMenuEntry') export class MainMenuEntry extends Component { @property({ tooltip: '是否自动生成 Start / Settings 按钮 (方便没美术时就能跑通)' }) public autoBuildUI: boolean = true; private flow: UIFlowMgr | undefined; protected onLoad(): void { this.flow = new UIFlowMgr(undefined, { onSceneEnter: (ev) => this.handleSceneEnter(ev), }); if (this.autoBuildUI) this.buildDefaultUI(); } /** Bind this to the "Start" button's click event in the Inspector. */ public onPressStart(): void { this.flow?.onPressStartGame(); } /** Bind this to the "Settings" button's click event. */ public onPressSettings(): void { this.flow?.onOpenSettings(); } private handleSceneEnter(ev: ISceneEnter): void { const payload = ev.payload ?? {}; if (ev.scene === 'gameplay' && typeof payload.levelId === 'string') { const physical = LEVEL_SCENE_MAP[payload.levelId]; if (physical) { director.loadScene(physical); return; } } const physical = SCENE_MAP[ev.scene]; if (physical) director.loadScene(physical); } // ------------------------------------------------------------------ // Auto-built UI (development affordance; art pass will replace it) // ------------------------------------------------------------------ private buildDefaultUI(): void { // Ensure the host node has a UITransform matching the design resolution. ensureCanvasSize(this.node); // Title. createLabel(this.node, '影 之 传 说', 0, 140, 44, Color.WHITE); // Start button (centered, 40 above origin). createButton(this.node, 'Start', 0, 40, 220, 60, () => this.onPressStart()); // Settings button (centered, 40 below origin). createButton(this.node, 'Settings', 0, -40, 220, 60, () => this.onPressSettings()); // Hint line at the bottom. createLabel(this.node, 'Chapter 1 · MVP', 0, -200, 20, new Color(180, 180, 180, 255)); } } // ====================================================================== // Shared UI helpers — intentionally kept inline (no external module) so // each Scene Entry stays self-contained and easy to remove once real UI is // authored in the editor. // ====================================================================== function ensureCanvasSize(host: Node): void { let ut = host.getComponent(UITransform); if (!ut) ut = host.addComponent(UITransform); ut.setContentSize(DESIGN_WIDTH, DESIGN_HEIGHT); host.setPosition(0, 0, 0); } function createLabel(parent: Node, text: string, x: number, y: number, fontSize: number, color: Color): Node { const n = new Node('AutoLabel'); n.layer = parent.layer; parent.addChild(n); const ut = n.addComponent(UITransform); ut.setContentSize(DESIGN_WIDTH, fontSize * 1.6); const lb = n.addComponent(Label); lb.useSystemFont = true; lb.string = text; lb.fontSize = fontSize; lb.lineHeight = Math.floor(fontSize * 1.2); lb.color = color; lb.horizontalAlign = 1; // CENTER lb.verticalAlign = 1; // CENTER n.setPosition(new Vec3(x, y, 0)); return n; } function createButton( parent: Node, text: string, x: number, y: number, w: number, h: number, onClick: () => void ): Node { const n = new Node(`Btn_${text}`); n.layer = parent.layer; parent.addChild(n); const ut = n.addComponent(UITransform); ut.setContentSize(w, h); // Background drawn via Graphics — avoids needing any texture asset. const g = n.addComponent(Graphics); g.fillColor = new Color(40, 40, 60, 230); g.rect(-w / 2, -h / 2, w, h); g.fill(); g.strokeColor = new Color(200, 200, 220, 255); g.lineWidth = 2; g.rect(-w / 2, -h / 2, w, h); g.stroke(); // Label child for the text. const labelNode = new Node('Label'); labelNode.layer = parent.layer; n.addChild(labelNode); const lut = labelNode.addComponent(UITransform); lut.setContentSize(w, h); const lb = labelNode.addComponent(Label); lb.useSystemFont = true; lb.string = text; lb.fontSize = 24; lb.lineHeight = 28; lb.color = Color.WHITE; lb.horizontalAlign = 1; lb.verticalAlign = 1; const btn = n.addComponent(Button); btn.transition = Button.Transition.SCALE; btn.target = n; btn.zoomScale = 0.95; n.on(Node.EventType.TOUCH_END, onClick, n); n.setPosition(new Vec3(x, y, 0)); return n; } // Re-export helpers so sibling Scene Entries can reuse them. export { ensureCanvasSize, createLabel, createButton };