diff --git a/assets/main.scene b/assets/main.scene index 3e58bd1..3d8f34c 100644 --- a/assets/main.scene +++ b/assets/main.scene @@ -478,6 +478,10 @@ "__uuid__": "cff2809d-6daa-4749-a911-bb99e97b4b54", "__expectedType__": "cc.Prefab" }, + "passModalPrefab": { + "__uuid__": "29ff0bfc-d5cf-4ad1-b8cb-61bdfd4850ef", + "__expectedType__": "cc.Prefab" + }, "_id": "c2b3nbzv9JuZmP2jxQyN72" }, { diff --git a/assets/prefabs/PageLevel.prefab b/assets/prefabs/PageLevel.prefab index 4332765..a21849b 100644 --- a/assets/prefabs/PageLevel.prefab +++ b/assets/prefabs/PageLevel.prefab @@ -404,10 +404,7 @@ "b": 255, "a": 255 }, - "_spriteFrame": { - "__uuid__": "388a4fd2-4c46-46ae-b796-10ab85c39e04@f9941", - "__expectedType__": "cc.SpriteFrame" - }, + "_spriteFrame": null, "_type": 0, "_fillType": 0, "_sizeMode": 0, @@ -6274,6 +6271,10 @@ "__uuid__": "be83ca42-3579-46e8-821f-7a0f0b9d8464", "__expectedType__": "cc.AudioClip" }, + "passModalPrefab": { + "__uuid__": "29ff0bfc-d5cf-4ad1-b8cb-61bdfd4850ef", + "__expectedType__": "cc.Prefab" + }, "_id": "" }, { diff --git a/assets/prefabs/PageLevel.ts b/assets/prefabs/PageLevel.ts index 9e6fcd5..1c40fa3 100644 --- a/assets/prefabs/PageLevel.ts +++ b/assets/prefabs/PageLevel.ts @@ -1,4 +1,4 @@ -import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, UITransform } from 'cc'; +import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, UITransform, Prefab } from 'cc'; import { BaseView } from 'db://assets/scripts/core/BaseView'; import { ViewManager } from 'db://assets/scripts/core/ViewManager'; import { StorageManager } from 'db://assets/scripts/utils/StorageManager'; @@ -6,6 +6,7 @@ import { WxSDK } from 'db://assets/scripts/utils/WxSDK'; import { LevelDataManager } from 'db://assets/scripts/utils/LevelDataManager'; import { RuntimeLevelConfig } from 'db://assets/scripts/types/LevelTypes'; import { ToastManager } from 'db://assets/scripts/utils/ToastManager'; +import { PassModal } from 'db://assets/prefabs/PassModal'; const { ccclass, property } = _decorator; /** @@ -14,6 +15,9 @@ const { ccclass, property } = _decorator; */ @ccclass('PageLevel') export class PageLevel extends BaseView { + /** 静态常量:零位置 */ + private static readonly ZERO_POS = new Vec3(0, 0, 0); + // ========== 节点引用 ========== @property(Node) inputLayout: Node | null = null; @@ -73,6 +77,9 @@ export class PageLevel extends BaseView { @property(AudioClip) failAudio: AudioClip | null = null; + @property(Prefab) + passModalPrefab: Prefab | null = null; + // ========== 内部状态 ========== /** 当前创建的输入框节点数组 */ private _inputNodes: Node[] = []; @@ -89,6 +96,9 @@ export class PageLevel extends BaseView { /** 是否正在切换关卡(防止重复提交) */ private _isTransitioning: boolean = false; + /** 通关弹窗实例 */ + private _passModalNode: Node | null = null; + /** * 页面首次加载时调用 */ @@ -132,6 +142,7 @@ export class PageLevel extends BaseView { console.log('[PageLevel] onViewDestroy'); this.clearInputNodes(); this.stopCountdown(); + this._closePassModal(); } /** @@ -217,7 +228,7 @@ export class PageLevel extends BaseView { inputNode.name = 'singleInput'; // 设置位置 - inputNode.setPosition(new Vec3(0, 0, 0)); + inputNode.setPosition(PageLevel.ZERO_POS); // 获取 EditBox 组件 const editBox = inputNode.getComponent(EditBox); @@ -664,10 +675,71 @@ export class PageLevel extends BaseView { // 通关奖励:增加一颗生命值 this.addLife(); - // 延迟后进入下一关 - this.scheduleOnce(() => { - this.nextLevel(); - }, 1.0); + // 显示通关弹窗 + this._showPassModal(); + } + + /** + * 显示通关弹窗 + * 将弹窗添加到 Canvas 根节点下(而非 PageLevel 子节点) + * 这样 Widget 可以正确对齐到全屏 + */ + private _showPassModal(): void { + if (!this.passModalPrefab) { + console.warn('[PageLevel] passModalPrefab 未设置'); + return; + } + + // 如果弹窗已显示,不再重复创建 + if (this._passModalNode && this._passModalNode.isValid) { + return; + } + + // 实例化弹窗 + const modalNode = instantiate(this.passModalPrefab); + modalNode.setPosition(PageLevel.ZERO_POS); + modalNode.setSiblingIndex(PassModal.MODAL_Z_INDEX); + + // 找到 Canvas 根节点并添加弹窗 + const canvasNode = this.node.parent; + if (canvasNode) { + canvasNode.addChild(modalNode); + } else { + this.node.addChild(modalNode); + } + this._passModalNode = modalNode; + + // 获取 PassModal 组件并设置回调 + const passModal = modalNode.getComponent(PassModal); + if (passModal) { + passModal.setParams({ levelIndex: this.currentLevelIndex + 1 }); + passModal.setCallbacks({ + onNextLevel: () => { + this._closePassModal(); + this.nextLevel(); + }, + onShare: () => { + // 分享后不关闭弹窗,用户可继续点击下一关 + console.log('[PageLevel] 分享完成'); + } + }); + // 手动调用 onViewLoad 和 onViewShow + passModal.onViewLoad(); + passModal.onViewShow(); + } + + console.log('[PageLevel] 显示通关弹窗'); + } + + /** + * 关闭通关弹窗 + */ + private _closePassModal(): void { + if (this._passModalNode && this._passModalNode.isValid) { + this._passModalNode.destroy(); + this._passModalNode = null; + console.log('[PageLevel] 关闭通关弹窗'); + } } /** diff --git a/assets/prefabs/PassModal.prefab b/assets/prefabs/PassModal.prefab index e7dae42..65d90a5 100644 --- a/assets/prefabs/PassModal.prefab +++ b/assets/prefabs/PassModal.prefab @@ -22,35 +22,38 @@ "__id__": 2 }, { - "__id__": 10 + "__id__": 12 }, { - "__id__": 18 + "__id__": 20 }, { - "__id__": 26 + "__id__": 28 }, { - "__id__": 32 + "__id__": 34 }, { - "__id__": 38 + "__id__": 40 }, { - "__id__": 54 + "__id__": 56 } ], "_active": true, "_components": [ { - "__id__": 70 + "__id__": 72 }, { - "__id__": 72 + "__id__": 74 + }, + { + "__id__": 76 } ], "_prefab": { - "__id__": 74 + "__id__": 78 }, "_lpos": { "__type__": "cc.Vec3", @@ -100,10 +103,13 @@ }, { "__id__": 7 + }, + { + "__id__": 9 } ], "_prefab": { - "__id__": 9 + "__id__": 11 }, "_lpos": { "__type__": "cc.Vec3", @@ -182,7 +188,7 @@ "r": 0, "g": 0, "b": 0, - "a": 79 + "a": 255 }, "_spriteFrame": { "__uuid__": "7d8f9b89-4fd1-4c9f-a3ab-38ec7cded7ca@f9941", @@ -243,6 +249,25 @@ "__type__": "cc.CompPrefabInfo", "fileId": "4devqNmdRDJY0Xex13oISf" }, + { + "__type__": "cc.UIOpacity", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 2 + }, + "_enabled": true, + "__prefab": { + "__id__": 10 + }, + "_opacity": 150, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "50fUxKQcdEvbrdUjRNf4+4" + }, { "__type__": "cc.PrefabInfo", "root": { @@ -267,18 +292,18 @@ "_children": [], "_active": true, "_components": [ - { - "__id__": 11 - }, { "__id__": 13 }, { "__id__": 15 + }, + { + "__id__": 17 } ], "_prefab": { - "__id__": 17 + "__id__": 19 }, "_lpos": { "__type__": "cc.Vec3", @@ -315,11 +340,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 10 + "__id__": 12 }, "_enabled": true, "__prefab": { - "__id__": 12 + "__id__": 14 }, "_contentSize": { "__type__": "cc.Size", @@ -343,11 +368,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 10 + "__id__": 12 }, "_enabled": true, "__prefab": { - "__id__": 14 + "__id__": 16 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -411,11 +436,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 10 + "__id__": 12 }, "_enabled": true, "__prefab": { - "__id__": 16 + "__id__": 18 }, "_alignFlags": 0, "_target": null, @@ -465,18 +490,18 @@ "_children": [], "_active": true, "_components": [ - { - "__id__": 19 - }, { "__id__": 21 }, { "__id__": 23 + }, + { + "__id__": 25 } ], "_prefab": { - "__id__": 25 + "__id__": 27 }, "_lpos": { "__type__": "cc.Vec3", @@ -513,11 +538,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 18 + "__id__": 20 }, "_enabled": true, "__prefab": { - "__id__": 20 + "__id__": 22 }, "_contentSize": { "__type__": "cc.Size", @@ -541,11 +566,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 18 + "__id__": 20 }, "_enabled": true, "__prefab": { - "__id__": 22 + "__id__": 24 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -586,11 +611,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 18 + "__id__": 20 }, "_enabled": true, "__prefab": { - "__id__": 24 + "__id__": 26 }, "_alignFlags": 0, "_target": null, @@ -641,14 +666,14 @@ "_active": true, "_components": [ { - "__id__": 27 + "__id__": 29 }, { - "__id__": 29 + "__id__": 31 } ], "_prefab": { - "__id__": 31 + "__id__": 33 }, "_lpos": { "__type__": "cc.Vec3", @@ -685,11 +710,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 26 + "__id__": 28 }, "_enabled": true, "__prefab": { - "__id__": 28 + "__id__": 30 }, "_contentSize": { "__type__": "cc.Size", @@ -713,11 +738,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 26 + "__id__": 28 }, "_enabled": true, "__prefab": { - "__id__": 30 + "__id__": 32 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -800,14 +825,14 @@ "_active": true, "_components": [ { - "__id__": 33 + "__id__": 35 }, { - "__id__": 35 + "__id__": 37 } ], "_prefab": { - "__id__": 37 + "__id__": 39 }, "_lpos": { "__type__": "cc.Vec3", @@ -844,11 +869,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 32 + "__id__": 34 }, "_enabled": true, "__prefab": { - "__id__": 34 + "__id__": 36 }, "_contentSize": { "__type__": "cc.Size", @@ -872,11 +897,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 32 + "__id__": 34 }, "_enabled": true, "__prefab": { - "__id__": 36 + "__id__": 38 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -934,14 +959,11 @@ }, "_children": [ { - "__id__": 39 + "__id__": 41 } ], "_active": true, "_components": [ - { - "__id__": 45 - }, { "__id__": 47 }, @@ -950,10 +972,13 @@ }, { "__id__": 51 + }, + { + "__id__": 53 } ], "_prefab": { - "__id__": 53 + "__id__": 55 }, "_lpos": { "__type__": "cc.Vec3", @@ -990,20 +1015,20 @@ "_objFlags": 0, "__editorExtras__": {}, "_parent": { - "__id__": 38 + "__id__": 40 }, "_children": [], "_active": true, "_components": [ { - "__id__": 40 + "__id__": 42 }, { - "__id__": 42 + "__id__": 44 } ], "_prefab": { - "__id__": 44 + "__id__": 46 }, "_lpos": { "__type__": "cc.Vec3", @@ -1040,11 +1065,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 39 + "__id__": 41 }, "_enabled": true, "__prefab": { - "__id__": 41 + "__id__": 43 }, "_contentSize": { "__type__": "cc.Size", @@ -1068,11 +1093,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 39 + "__id__": 41 }, "_enabled": true, "__prefab": { - "__id__": 43 + "__id__": 45 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -1149,11 +1174,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 38 + "__id__": 40 }, "_enabled": true, "__prefab": { - "__id__": 46 + "__id__": 48 }, "_contentSize": { "__type__": "cc.Size", @@ -1177,11 +1202,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 38 + "__id__": 40 }, "_enabled": true, "__prefab": { - "__id__": 48 + "__id__": 50 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -1222,15 +1247,15 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 38 + "__id__": 40 }, "_enabled": true, "__prefab": { - "__id__": 50 + "__id__": 52 }, "clickEvents": [], "_interactable": true, - "_transition": 0, + "_transition": 3, "_normalColor": { "__type__": "cc.Color", "r": 255, @@ -1278,11 +1303,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 38 + "__id__": 40 }, "_enabled": true, "__prefab": { - "__id__": 52 + "__id__": 54 }, "_alignFlags": 4, "_target": null, @@ -1331,14 +1356,11 @@ }, "_children": [ { - "__id__": 55 + "__id__": 57 } ], "_active": true, "_components": [ - { - "__id__": 61 - }, { "__id__": 63 }, @@ -1347,10 +1369,13 @@ }, { "__id__": 67 + }, + { + "__id__": 69 } ], "_prefab": { - "__id__": 69 + "__id__": 71 }, "_lpos": { "__type__": "cc.Vec3", @@ -1387,20 +1412,20 @@ "_objFlags": 0, "__editorExtras__": {}, "_parent": { - "__id__": 54 + "__id__": 56 }, "_children": [], "_active": true, "_components": [ { - "__id__": 56 + "__id__": 58 }, { - "__id__": 58 + "__id__": 60 } ], "_prefab": { - "__id__": 60 + "__id__": 62 }, "_lpos": { "__type__": "cc.Vec3", @@ -1437,11 +1462,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 55 + "__id__": 57 }, "_enabled": true, "__prefab": { - "__id__": 57 + "__id__": 59 }, "_contentSize": { "__type__": "cc.Size", @@ -1465,11 +1490,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 55 + "__id__": 57 }, "_enabled": true, "__prefab": { - "__id__": 59 + "__id__": 61 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -1546,11 +1571,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 54 + "__id__": 56 }, "_enabled": true, "__prefab": { - "__id__": 62 + "__id__": 64 }, "_contentSize": { "__type__": "cc.Size", @@ -1574,11 +1599,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 54 + "__id__": 56 }, "_enabled": true, "__prefab": { - "__id__": 64 + "__id__": 66 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -1619,15 +1644,15 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 54 + "__id__": 56 }, "_enabled": true, "__prefab": { - "__id__": 66 + "__id__": 68 }, "clickEvents": [], "_interactable": true, - "_transition": 0, + "_transition": 3, "_normalColor": { "__type__": "cc.Color", "r": 255, @@ -1675,11 +1700,11 @@ "_objFlags": 0, "__editorExtras__": {}, "node": { - "__id__": 54 + "__id__": 56 }, "_enabled": true, "__prefab": { - "__id__": 68 + "__id__": 70 }, "_alignFlags": 4, "_target": null, @@ -1728,7 +1753,7 @@ }, "_enabled": true, "__prefab": { - "__id__": 71 + "__id__": 73 }, "_contentSize": { "__type__": "cc.Size", @@ -1754,9 +1779,9 @@ "node": { "__id__": 1 }, - "_enabled": true, + "_enabled": false, "__prefab": { - "__id__": 73 + "__id__": 75 }, "_alignFlags": 45, "_target": null, @@ -1782,6 +1807,37 @@ "__type__": "cc.CompPrefabInfo", "fileId": "7fIHnHRk5G7pY0zPQKjszk" }, + { + "__type__": "c08bfqz0UtDmL8nr95ncLZl", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 1 + }, + "_enabled": true, + "__prefab": { + "__id__": 77 + }, + "nextLevelButton": { + "__id__": 40 + }, + "shareButton": { + "__id__": 56 + }, + "tipLabel": { + "__id__": 31 + }, + "successAudio": { + "__uuid__": "45d1f0b7-d9d7-41d8-84b2-7abad9304148", + "__expectedType__": "cc.AudioClip" + }, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "8dIxz/04pNJJyhxtqzxpPD" + }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/prefabs/PassModal.ts b/assets/prefabs/PassModal.ts index 306faaa..d9fc472 100644 --- a/assets/prefabs/PassModal.ts +++ b/assets/prefabs/PassModal.ts @@ -1,14 +1,154 @@ -import { _decorator, Component, Node } from 'cc'; +import { _decorator, Node, Label, AudioClip, AudioSource, view, UITransform, Size } from 'cc'; +import { BaseView } from 'db://assets/scripts/core/BaseView'; +import { WxSDK } from 'db://assets/scripts/utils/WxSDK'; const { ccclass, property } = _decorator; -@ccclass('PassModal') -export class PassModal extends Component { - start() { - - } - - update(deltaTime: number) { - - } +/** + * PassModal 回调接口 + */ +export interface PassModalCallbacks { + /** 点击下一关回调 */ + onNextLevel?: () => void; + /** 点击分享回调 */ + onShare?: () => void; } +/** + * 通关弹窗组件 + * 继承 BaseView,显示通关成功弹窗,提供"下一关"和"分享给好友"两个按钮 + */ +@ccclass('PassModal') +export class PassModal extends BaseView { + /** 静态常量:弹窗层级 */ + public static readonly MODAL_Z_INDEX = 999; + + /** 下一关按钮 */ + @property(Node) + nextLevelButton: Node | null = null; + + /** 分享按钮 */ + @property(Node) + shareButton: Node | null = null; + + /** 提示Label(如 +1 生命) */ + @property(Label) + tipLabel: Label | null = null; + + /** 通关音效 */ + @property(AudioClip) + successAudio: AudioClip | null = null; + + /** 回调函数 */ + private _callbacks: PassModalCallbacks = {}; + + /** 缓存的屏幕尺寸 */ + private _screenSize: Size | null = null; + + /** + * 设置回调函数 + */ + setCallbacks(callbacks: PassModalCallbacks): void { + this._callbacks = callbacks; + } + + /** + * 页面首次加载时调用 + */ + onViewLoad(): void { + console.log('[PassModal] onViewLoad'); + this._bindButtonEvents(); + } + + /** + * 页面每次显示时调用 + */ + onViewShow(): void { + this._updateWidget(); + this._playSuccessSound(); + } + + /** + * 页面销毁时调用 + */ + onViewDestroy(): void { + this._unbindButtonEvents(); + } + + /** + * 设置弹窗尺寸为全屏 + * 动态实例化后,手动设置节点尺寸覆盖整个屏幕 + */ + private _updateWidget(): void { + // 缓存屏幕尺寸,避免重复计算 + if (!this._screenSize) { + this._screenSize = view.getVisibleSize(); + } + + const uiTransform = this.node.getComponent(UITransform); + if (uiTransform) { + uiTransform.setContentSize(this._screenSize.width, this._screenSize.height); + } + } + + /** + * 绑定按钮事件 + */ + private _bindButtonEvents(): void { + if (this.nextLevelButton) { + this.nextLevelButton.on(Node.EventType.TOUCH_END, this._onNextLevelClick, this); + } + if (this.shareButton) { + this.shareButton.on(Node.EventType.TOUCH_END, this._onShareClick, this); + } + } + + /** + * 解除按钮事件绑定 + */ + private _unbindButtonEvents(): void { + // 节点可能在销毁过程中已被置空,需要检查 isValid + if (this.nextLevelButton && this.nextLevelButton.isValid) { + this.nextLevelButton.off(Node.EventType.TOUCH_END, this._onNextLevelClick, this); + } + if (this.shareButton && this.shareButton.isValid) { + this.shareButton.off(Node.EventType.TOUCH_END, this._onShareClick, this); + } + } + + /** + * 播放通关音效 + */ + private _playSuccessSound(): void { + if (!this.successAudio) { + return; + } + + const audioSource = this.node.getComponent(AudioSource); + if (audioSource) { + audioSource.playOneShot(this.successAudio); + } + } + + /** + * 下一关按钮点击 + */ + private _onNextLevelClick(): void { + console.log('[PassModal] 点击下一关'); + this._callbacks.onNextLevel?.(); + } + + /** + * 分享按钮点击 + */ + private _onShareClick(): void { + console.log('[PassModal] 点击分享'); + + // 调用微信分享 + WxSDK.shareAppMessage({ + title: '快来一起玩这款游戏吧', + query: `level=${this._params?.levelIndex ?? 1}` + }); + + this._callbacks.onShare?.(); + } +}