import type GameplayIntro from "../../GameplayIntro/script/GameplayIntro"; import { getLevelGameplay } from "../../Script/module/Tool/LevelGameplay"; const { ccclass } = cc._decorator; /** 独立测试面板:直接加载正式 GameplayIntro 预制体,不运行登录/对局,也不写入存档。 */ @ccclass export default class TestLayoutPanel extends cc.Component { private config: any[] = []; // 配置准备好才能打开;loading 防止资源未返回时重复创建多个面板。 private ready = false; private loading = false; private popup: cc.Node = null; private controller: any = null; private stage: cc.Node = null; private status: cc.Label = null; private levelInput: cc.EditBox = null; private level = 511; private allGameplay = false; private entries: any[] = []; private levelRequest = 0; private infoButton: cc.Node = null; /** 创建测试按钮、关卡输入框和预览区域;这些辅助控件都属于可删除的 test 包。 */ onLoad() { this.node.color = cc.Color.WHITE; this.box(this.node, "Background", 0, 0, 1080, 2400, new cc.Color(27, 32, 43)); this.label(this.node, "本关玩法 · 问号按钮测试", 0, 867, 42); this.label(this.node, "按关卡检查,或查看全部玩法 / 标题、道具、说明、分页", 0, 811, 25); this.stage = new cc.Node("PreviewStage"); this.stage.parent = this.node; this.stage.setPosition(0, 25); this.stage.setContentSize(1080, 1600); this.stage.scale = 0.78; // 测试控件显示在遮罩上方,方便面板打开时继续切换测试关卡。 this.infoButton = this.createInfoButton(this.node, 440, 840, () => this.reopen()); this.infoButton.active = false; const controls = new cc.Node("Controls"); controls.parent = this.node; controls.zIndex = 10; // 读取真实关卡,分别验证零项、单项、双项和多项。 [1, 7, 44, 511].forEach((level, index) => { this.button(controls, level === 1 ? "无道具 · 1关" : level === 7 ? "1页 · 7关" : level === 44 ? "2页 · 44关" : "5页 · 511关", -375 + index * 250, 735, 235, () => this.open(level)); }); const input = this.box(controls, "LevelInput", -375, 660, 205, 58, new cc.Color(65, 75, 92)); input.active = false; this.levelInput = input.addComponent(cc.EditBox); this.levelInput.textLabel = this.label(input, "511", 0, 0, 28); this.levelInput.placeholderLabel = this.label(input, "输入关卡", 0, 0, 26); [this.levelInput.textLabel, this.levelInput.placeholderLabel].forEach(label => { label.node.setContentSize(185, 58); label.node.setPosition(-92.5, 29); }); this.levelInput.fontSize = 28; this.levelInput.fontColor = cc.Color.WHITE; this.levelInput.inputMode = cc.EditBox.InputMode.NUMERIC; this.levelInput.string = "511"; input.active = true; this.button(controls, "按关卡打开", -115, 660, 230, () => { const level = Number(this.levelInput.string); if (!Number.isInteger(level) || level < 1) { this.status.string = "请输入大于或等于 1 的整数关卡"; return; } this.open(level); }); this.button(controls, "重新打开", 140, 660, 230, () => this.reopen()); this.button(controls, "全部玩法", 395, 660, 230, () => this.openAllGameplay()); this.status = this.label(controls, "正在加载正式弹窗和介绍资源…", 0, -635, 28); this.status.node.width = 1020; this.status.overflow = cc.Label.Overflow.RESIZE_HEIGHT; ["第一页", "上一页", "下一页", "最后页"].forEach((title, index) => { const actions = ["first", "previous", "next", "last"]; this.button(controls, title, -375 + index * 250, -730, 230, () => { if (this.popup && this.popup.active && this.controller) this.controller.previewNavigate(actions[index]); }); }); this.label(controls, "无道具隐藏问号,单页不可滑动,打开先看最左端最新玩法", 0, -812, 25); this.label(controls, "点击面板外空白处关闭;读取本地关卡,不修改游戏进度", 0, -856, 25); // 只读取正式解锁配置,输入的测试关卡保存在本组件,不修改玩家实际进度。 cc.resources.load("Json/NEW_LEVEL", cc.JsonAsset, (error, asset: cc.JsonAsset) => { if (!cc.isValid(this, true)) return; if (error) { this.status.string = "解锁配置加载失败:" + error.message; return; } this.config = asset.json.NEW_LEVEL; this.ready = true; this.open(this.level); }); } /** 用指定测试关卡打开正式预制体;已有面板直接复用,否则异步加载并实例化。 */ private open(level: number) { if (!this.ready) return; this.allGameplay = false; this.level = level; this.levelInput.string = String(level); this.infoButton.active = false; if (this.popup && this.popup.active) this.controller.close(); this.entries = []; this.status.string = "正在读取第 " + level + " 关…"; const token = ++this.levelRequest; cc.assetManager.loadBundle("custom", (error, bundle) => { if (!cc.isValid(this, true) || token !== this.levelRequest) return; if (error) { this.status.string = "关卡包加载失败:" + error.message; return; } bundle.load("Json/level" + level, cc.JsonAsset, (loadError, asset: cc.JsonAsset) => { if (!cc.isValid(this, true) || token !== this.levelRequest) return; if (loadError) { this.status.string = "本地关卡加载失败:" + loadError.message; return; } this.entries = getLevelGameplay(this.config, asset.json); this.infoButton.active = this.entries.length > 0; if (!this.entries.length) { this.status.string = "本关没有特殊道具,问号已隐藏"; return; } this.showEntries(); }); }); } /** 策划验图入口:不按关卡过滤,仍使用正式面板按相邻三页加载资源。 */ private openAllGameplay() { if (!this.ready) return; ++this.levelRequest; // 丢弃切换前尚未返回的关卡请求。 this.allGameplay = true; const seen = new Set(); this.entries = this.config.filter(entry => { if (seen.has(entry.name)) return false; seen.add(entry.name); return true; }); this.infoButton.active = this.entries.length > 0; this.showEntries(); } private reopen() { if (this.allGameplay) this.openAllGameplay(); else this.open(this.level); } private showEntries() { if (this.popup) { this.controller.open(this.entries); return; } if (this.loading) return; this.loading = true; cc.assetManager.loadBundle("gameplay_intro", (error, bundle) => { if (!cc.isValid(this, true)) return; if (error) { this.loading = false; this.status.string = "功能包加载失败:" + error.message; return; } bundle.load("prefab/GameplayIntro", cc.Prefab, (prefabError, prefab: cc.Prefab) => { if (!cc.isValid(this, true)) return; this.loading = false; if (prefabError) { this.status.string = "面板预制体加载失败:" + prefabError.message; return; } // 测试挂在 stage 下;正式游戏由 SceneManager 挂在场景 tipPanel 下。 this.popup = cc.instantiate(prefab); this.popup.parent = this.stage; this.popup.active = false; this.controller = this.popup.getComponent("GameplayIntro") as GameplayIntro; this.popup.on("guide-page-changed", state => { this.status.string = (this.allGameplay ? "全部玩法" : "测试关卡 " + this.level) + " | " + (state.index + 1) + " / " + state.total + " 页\n" + (state.entry ? state.entry.name + " · " + state.entry.tips : "暂无玩法"); }); this.popup.on("intro-closed", () => this.status.string = this.entries.length ? "面板已关闭,点击右上角问号重新查看" : "本关没有特殊道具,问号已隐藏"); if (this.entries.length) this.controller.open(this.entries); }); }); } // 仅测试场景用代码创建问号;正式游戏入口应由 GameScene 中的 Button 节点提供。 private createInfoButton(parent: cc.Node, x: number, y: number, open: () => void): cc.Node { const node = new cc.Node("GameplayInfoButton"); node.parent = parent; node.setPosition(x, y); node.setContentSize(88, 88); const sprite = node.addComponent(cc.Sprite); sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM; cc.assetManager.loadBundle("gameplay_intro", (error, bundle) => { if (!cc.isValid(node, true)) return; if (error) { cc.error("History button bundle", error); return; } bundle.load("Texture/wenhao", cc.SpriteFrame, (imageError, frame: cc.SpriteFrame) => { if (!cc.isValid(node, true)) return; if (imageError) { cc.error("History button image", imageError); return; } sprite.spriteFrame = frame; const size = frame.getOriginalSize(); const scale = Math.min(88 / size.width, 88 / size.height); node.setContentSize(size.width * scale, size.height * scale); }); }); node.addComponent(cc.Button); node.on("click", open); return node; } // 以下三个工具方法用于绘制测试控件的底色、文字和按钮。 private box(parent: cc.Node, name: string, x: number, y: number, width: number, height: number, color: cc.Color) { const node = new cc.Node(name); node.parent = parent; node.setPosition(x, y); node.setContentSize(width, height); const graphics = node.addComponent(cc.Graphics); graphics.fillColor = color; graphics.roundRect(-width / 2, -height / 2, width, height, 12); graphics.fill(); return node; } private label(parent: cc.Node, text: string, x: number, y: number, size: number) { const node = new cc.Node("Label"); node.parent = parent; node.setPosition(x, y); const label = node.addComponent(cc.Label); label.string = text; label.fontSize = size; label.lineHeight = size + 10; label.horizontalAlign = cc.Label.HorizontalAlign.CENTER; return label; } private button(parent: cc.Node, text: string, x: number, y: number, width: number, action: () => void) { const node = this.box(parent, text, x, y, width, 58, new cc.Color(55, 91, 143)); this.label(node, text, 0, 0, 26); node.addComponent(cc.Button); node.on("click", action, this); } }