From 8d2fbbbcf0a4c7828ad7de38f38a71cd231c3b7c Mon Sep 17 00:00:00 2001 From: richarjiang Date: Sun, 31 May 2026 21:15:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=80=9A=E5=85=B3?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/prefabs/PageHome.prefab | 2 +- assets/prefabs/PageLevel.prefab | 4 +- assets/prefabs/PageLevel.ts | 242 ++++++++++++++-- assets/prefabs/PagePKEnd.prefab | 386 +++++++++++++------------- assets/prefabs/PageWriteLevels.prefab | 6 +- assets/prefabs/PageWriteLevels.ts | 70 ++++- assets/resources/audios/good.mp3 | Bin 0 -> 16977 bytes assets/resources/audios/good.mp3.meta | 14 + 8 files changed, 499 insertions(+), 225 deletions(-) create mode 100644 assets/resources/audios/good.mp3 create mode 100644 assets/resources/audios/good.mp3.meta diff --git a/assets/prefabs/PageHome.prefab b/assets/prefabs/PageHome.prefab index 827a972..974dc1e 100644 --- a/assets/prefabs/PageHome.prefab +++ b/assets/prefabs/PageHome.prefab @@ -5471,7 +5471,7 @@ }, "_contentSize": { "__type__": "cc.Size", - "width": 442.03125, + "width": 461.40625, "height": 75.6 }, "_anchorPoint": { diff --git a/assets/prefabs/PageLevel.prefab b/assets/prefabs/PageLevel.prefab index 148fde1..dcf7fb2 100644 --- a/assets/prefabs/PageLevel.prefab +++ b/assets/prefabs/PageLevel.prefab @@ -9346,8 +9346,8 @@ }, "_lpos": { "__type__": "cc.Vec3", - "x": -8.80499999999995, - "y": 748.5280000000002, + "x": -8.805, + "y": 167.297, "z": 0 }, "_lrot": { diff --git a/assets/prefabs/PageLevel.ts b/assets/prefabs/PageLevel.ts index fa785a3..d4a35ad 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, Prefab, EffectAsset, UITransform, UIOpacity, ProgressBar, tween, Tween, Color, Layout, sp, view } from 'cc'; +import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, Prefab, EffectAsset, UITransform, UIOpacity, ProgressBar, tween, Tween, Color, Layout, Widget, sp, view, resources } from 'cc'; import { BaseView } from 'db://assets/scripts/core/BaseView'; import { ViewManager } from 'db://assets/scripts/core/ViewManager'; import { StaminaManager } from 'db://assets/scripts/utils/StaminaManager'; @@ -41,7 +41,7 @@ export class PageLevel extends BaseView { private static readonly DEFAULT_STAMINA_MAX = 50; /** 答案正确后到弹出通关弹窗之间的停留时间(不论是否有谐音梗都保持一致) */ - private static readonly PASS_MODAL_DELAY_MS = 1000; + private static readonly PASS_MODAL_DELAY_MS = 800; /** 图片2描述默认文案 */ private static readonly DEFAULT_IMAGE2_DESCRIPTION = '这是什么?'; @@ -91,6 +91,12 @@ export class PageLevel extends BaseView { /** 彩带 spine 动画名(一次播放) */ private static readonly CAIDAI_ANIMATION_NAME = 'open'; + /** pose 赞美音效 resources 路径(assets/resources/audios/good.mp3) */ + private static readonly GOOD_AUDIO_RESOURCE_PATH = 'audios/good'; + + /** pose 赞美段提前量:比通关动效 / 成功音效结束点提前 1 秒启动 */ + private static readonly POSE_PRAISE_ADVANCE_SECONDS = 1.5; + /** * 通关赞美 spine(pose 节点)档位:[最小通关数, 动画名],倒序匹配。 * 1-5 关 → "1",6-10 关 → "2",11+ 关 → "3"。 @@ -346,6 +352,9 @@ export class PageLevel extends BaseView { /** PassNode 原始 local position(prefab 摆放位),动画结束后用来回归 */ private _passNodeOriginalPos: Vec3 | null = null; + /** PassNode Widget 初始启用状态;进出场动画期间临时关闭,避免激活首帧回写位置 */ + private _passNodeWidgetOriginalEnabled: boolean | null = null; + /** 通关页所用「已通关数量」(业务数据,给成就体系展示用) */ private _passCompletedLevelCount: number | null = null; @@ -358,6 +367,18 @@ export class PageLevel extends BaseView { /** pose Spine 隐藏延时定时器(setTimeout 句柄);切关 / 关页时需清理避免穿屏触发 */ private _poseHideTimer: ReturnType | null = null; + /** pose 赞美延迟播放定时器;等待通关动画与成功音效结束后再触发 */ + private _posePraiseDelayTimer: ReturnType | null = null; + + /** pose 赞美延迟播放序号,用于取消 resources.load 异步回调里的过期播放 */ + private _posePraiseSequenceId: number = 0; + + /** good.mp3 加载缓存 */ + private _goodAudioClip: AudioClip | null = null; + + /** good.mp3 加载中的 Promise,避免重复请求 resources */ + private _goodAudioLoadPromise: Promise | null = null; + /** 通关页动画起点(通关前)的已通关数量;为 null 表示不播跨称号过渡 */ private _passPreviousCompletedLevelCount: number | null = null; @@ -703,10 +724,8 @@ export class PageLevel extends BaseView { // 设置图片描述 this.setImageDescriptions(config.image1Description, config.image2Description); - // 设置关卡标题 - this.updateTitleLevelLabel(); - this.updatePkLevelProgressLabel(); - this.updatePkNextLevelButtonText(); + // 设置关卡标题与主线 / 分享模式常驻 UI 可见性 + this._refreshModeUI(); // 隐藏包袱答案,通关后再按 punchline 展示 this.hidePunchline(); @@ -1178,10 +1197,36 @@ export class PageLevel extends BaseView { if (this._isSubmittingShareResult) { return; } + + // 通关页已展示:分享模式下 passNextLevelButton 被整体隐藏(见 _setupPassNodeContent), + // pkNextLevelButton 同时承担「通关后下一题」的入口。 + // 已经答对的题不需要二次确认(确认弹窗是「未答对就跳过」时的兜底),直接切关。 + // 注意切关顺序:先在 PassNode 仍覆盖屏幕时切换到下一关内容(分享模式题目均预加载, + // _enterAndInitLevel 几乎瞬时完成),再播放 PassNode 滑出动画。否则 _hidePassNode 先滑出 → + // 异步加载下一关 → _applyLevelConfig,期间玩家会看到上一题的提示 / 谐音梗 / 已填答案闪现。 + // 用 _isPassNodeAnimating 整段加锁防止异步过程中再次点击。 + if (this._isPassNodeShown) { + if (this._isPassNodeAnimating) return; + + this._isPassNodeAnimating = true; + AudioManager.instance.playButtonClick(); + void (async () => { + try { + await this.goToNextLevel(); + await this._hidePassNode(); + } catch (err) { + console.error('[PageLevel] 分享模式切换下一题失败:', err); + this._isPassNodeAnimating = false; + } + })(); + return; + } + if (this._isTransitioning) { return; } + // 未通关就点下一题 = 跳过当前题,弹二次确认避免误触 this.playClickSound(); this._showShareNextConfirmModal(() => { this._recordCurrentShareSubmission(); @@ -2135,11 +2180,17 @@ export class PageLevel extends BaseView { this._setupPassNodeContent(); this._bindPassNodeEvents(); - // 启动彩带 + 滑入动画 + 淡出底部UI + 通关音效 + 赞美 spine - this._playCaidai(); - this._playPosePraise(this._sessionPassCount); - this._playPassNodeShowAnimation(); - this.playSuccessSound(); + // 先让 PassNode 完整入场,再启动通关动效和成功音效;pose 赞美段延后到它们结束后播放。 + this._playPassNodeShowAnimation(() => { + const caidaiDuration = this._playCaidai(); + this.playSuccessSound(); + if (this._isShareMode) { + this._playPosePraise(this._sessionPassCount); + return; + } + + this._schedulePosePraiseAfterPassEffects(this._sessionPassCount, caidaiDuration); + }); console.log('[PageLevel] 显示通关页 PassNode'); } @@ -2186,6 +2237,16 @@ export class PageLevel extends BaseView { } // NextLevelButton 文案 + // 分享模式下「下一题 / 提交答案」入口由 pkNextLevelButton(黄色 PK 按钮)承担, + // PassNode 内部的 passNextLevelButton / passShareButton 整体隐藏避免出现重复入口, + // 但通关页的滑入 / 彩带 / 赞美等动画样式保持一致。 + if (this.passNextLevelButton) { + this.passNextLevelButton.active = !this._isShareMode; + } + if (this.passShareButton) { + this.passShareButton.active = !this._isShareMode; + } + const isFinalShareSubmit = this._isShareMode && this._isFinalShareLevel(); const nextLabel = this.passNextLevelButton?.getChildByName('Label')?.getComponent(Label); if (nextLabel) { @@ -2193,6 +2254,10 @@ export class PageLevel extends BaseView { ? PageLevel.SHARE_SUBMIT_BUTTON_TEXT : '下一关'; } + + // 分享模式下 PassNode 展示后,让 pkNextLevelButton 文案与最终态保持同步 + // (SHARE_SUBMIT_BUTTON_TEXT vs SHARE_NEXT_BUTTON_TEXT),由 updatePkNextLevelButtonText 统一处理 + this.updatePkNextLevelButtonText(); } private _ensureTitleAnimator(): AchievementTitleAnimator { @@ -2213,6 +2278,8 @@ export class PageLevel extends BaseView { private _initPassNodeState(): void { if (this.passNode) { this._passNodeOriginalPos = this.passNode.position.clone(); + const widget = this.passNode.getComponent(Widget); + this._passNodeWidgetOriginalEnabled = widget?.enabled ?? null; this.passNode.active = false; } if (this.caidaiNode) { @@ -2224,6 +2291,7 @@ export class PageLevel extends BaseView { if (this.poseNode) { this.poseNode.active = false; } + this._clearPosePraiseDelayTimer(); this._clearPoseHideTimer(); this._isPassNodeShown = false; @@ -2231,7 +2299,7 @@ export class PageLevel extends BaseView { } /** PassNode 进入动画:底部两层淡出 + PassNode 从屏幕左侧滑入 */ - private _playPassNodeShowAnimation(): void { + private _playPassNodeShowAnimation(onShown?: () => void): void { if (!this.passNode) return; this._isPassNodeAnimating = true; @@ -2250,8 +2318,9 @@ export class PageLevel extends BaseView { const startX = originalPos.x - screenWidth; Tween.stopAllByTarget(this.passNode); - this.passNode.active = true; + this._setPassNodeWidgetEnabledForAnimation(false); this.passNode.setPosition(startX, originalPos.y, originalPos.z); + this.passNode.active = true; tween(this.passNode) .to( @@ -2260,7 +2329,9 @@ export class PageLevel extends BaseView { { easing: 'cubicOut' }, ) .call(() => { + this._setPassNodeWidgetEnabledForAnimation(true); this._isPassNodeAnimating = false; + onShown?.(); }) .start(); } @@ -2269,7 +2340,7 @@ export class PageLevel extends BaseView { * PassNode 退出(用户点下一关时调用):滑出 PassNode + 底部两层淡入 * 返回 Promise,外部链路通常 await 后再调 goToNextLevel */ - private _hidePassNode(): Promise { + private _hidePassNode(restoreBottomLayers: boolean = true): Promise { return new Promise((resolve) => { if (!this.passNode || !this._isPassNodeShown) { resolve(); @@ -2279,7 +2350,9 @@ export class PageLevel extends BaseView { this._isPassNodeAnimating = true; // 底部两层淡入恢复 - this._fadeInBottomLayers(); + if (restoreBottomLayers) { + this._fadeInBottomLayers(); + } // 滑出 PassNode(向左滑出屏幕) const screenWidth = view.getVisibleSize().width; @@ -2287,6 +2360,7 @@ export class PageLevel extends BaseView { const exitX = originalPos.x - screenWidth; Tween.stopAllByTarget(this.passNode); + this._setPassNodeWidgetEnabledForAnimation(false); tween(this.passNode) .to( PageLevel.PASS_NODE_SLIDE_DURATION, @@ -2308,6 +2382,7 @@ export class PageLevel extends BaseView { if (this._passNodeOriginalPos) { this.passNode.setPosition(this._passNodeOriginalPos); } + this._setPassNodeWidgetEnabledForAnimation(true); } // 关掉彩带 @@ -2319,6 +2394,7 @@ export class PageLevel extends BaseView { if (this.poseNode) { this.poseNode.active = false; } + this._clearPosePraiseDelayTimer(); this._clearPoseHideTimer(); this._unbindPassNodeEvents(); @@ -2339,6 +2415,7 @@ export class PageLevel extends BaseView { if (this._passNodeOriginalPos) { this.passNode.setPosition(this._passNodeOriginalPos); } + this._setPassNodeWidgetEnabledForAnimation(true); } if (this.caidaiNode) { @@ -2349,6 +2426,7 @@ export class PageLevel extends BaseView { if (this.poseNode) { this.poseNode.active = false; } + this._clearPosePraiseDelayTimer(); this._clearPoseHideTimer(); this._restoreBottomLayersImmediate(); @@ -2358,6 +2436,19 @@ export class PageLevel extends BaseView { this._isPassNodeAnimating = false; } + private _setPassNodeWidgetEnabledForAnimation(enabled: boolean): void { + const widget = this.passNode?.getComponent(Widget); + if (!widget) { + return; + } + + if (this._passNodeWidgetOriginalEnabled === null) { + this._passNodeWidgetOriginalEnabled = widget.enabled; + } + + widget.enabled = enabled ? this._passNodeWidgetOriginalEnabled : false; + } + /** PassNode 事件绑定(NextLevel + Share) */ private _bindPassNodeEvents(): void { // 防御:先解绑,避免重复绑定 @@ -2390,11 +2481,38 @@ export class PageLevel extends BaseView { AudioManager.instance.playButtonClick(); - // 普通模式 _showShareNextConfirmModal 内部首行就 onConfirm(),所以两条路统一 - this._showShareNextConfirmModal(async () => { - await this._hidePassNode(); - void this.goToNextLevel(); - }); + if (this._isShareMode) { + // 分享模式题目已预加载,继续保持“先换题、再退场”,避免露出上一题内容。 + this._showShareNextConfirmModal(() => { + if (this._isPassNodeAnimating) return; + + this._isPassNodeAnimating = true; + void (async () => { + try { + await this.goToNextLevel(); + await this._hidePassNode(); + } catch (err) { + console.error('[PageLevel] 分享模式切换下一题失败:', err); + this._isPassNodeAnimating = false; + } + })(); + }); + return; + } + + void (async () => { + try { + // 主线模式需要等 PassNode 退场动画结束后再进入下一关; + // 下一关会走 enter 接口和图片刷新,提前切换会在动画过程中露出新题。 + await this._hidePassNode(false); + await this.goToNextLevel(); + this._fadeInBottomLayers(); + } catch (err) { + console.error('[PageLevel] 主线模式切换下一关失败:', err); + this._isPassNodeAnimating = false; + this._fadeInBottomLayers(); + } + })(); } /** @@ -2409,13 +2527,73 @@ export class PageLevel extends BaseView { }); } - /** 启动彩带 spine 动画 "open",单次播放 */ - private _playCaidai(): void { - if (!this.caidaiNode || !this.caidaiSkeleton) return; + /** 启动彩带 spine 动画 "open",单次播放,返回动画时长(秒)用于串联后续赞美段 */ + private _playCaidai(): number { + if (!this.caidaiNode || !this.caidaiSkeleton) return 0; this.caidaiNode.active = true; // setAnimation 三参:trackIndex, name, loop - this.caidaiSkeleton.setAnimation(0, PageLevel.CAIDAI_ANIMATION_NAME, false); + const trackEntry = this.caidaiSkeleton.setAnimation(0, PageLevel.CAIDAI_ANIMATION_NAME, false); + if (!trackEntry) return 0; + + return Math.max(0, trackEntry.animationEnd - trackEntry.animationStart); + } + + /** + * 通关页主氛围顺序: + * 1) PassNode 入场完成后播放彩带 + successAudio + * 2) 等彩带动画和 successAudio 都结束 + * 3) 再播放 pose 赞美 Spine + good.mp3 + */ + private _schedulePosePraiseAfterPassEffects(count: number, caidaiDuration: number): void { + this._clearPosePraiseDelayTimer(); + + const successDuration = this.successAudio?.getDuration() ?? 0; + const delaySeconds = Math.max(0, Math.max(caidaiDuration, successDuration) - PageLevel.POSE_PRAISE_ADVANCE_SECONDS); + const delayMs = delaySeconds * 1000; + const sequenceId = ++this._posePraiseSequenceId; + + void this._loadGoodAudioClip(); + + this._posePraiseDelayTimer = setTimeout(() => { + this._posePraiseDelayTimer = null; + void this._playPosePraiseWithGoodAudio(count, sequenceId); + }, delayMs); + } + + private async _playPosePraiseWithGoodAudio(count: number, sequenceId: number): Promise { + const goodAudio = await this._loadGoodAudioClip(); + if (sequenceId !== this._posePraiseSequenceId || !this._isPassNodeShown || !this.node.isValid) { + return; + } + + this._playPosePraise(count); + this.playSound(goodAudio); + } + + private _loadGoodAudioClip(): Promise { + if (this._goodAudioClip) { + return Promise.resolve(this._goodAudioClip); + } + if (this._goodAudioLoadPromise) { + return this._goodAudioLoadPromise; + } + + this._goodAudioLoadPromise = new Promise((resolve) => { + resources.load(PageLevel.GOOD_AUDIO_RESOURCE_PATH, AudioClip, (err, clip) => { + if (err) { + console.warn('[PageLevel] good.mp3 音效加载失败:', err); + this._goodAudioLoadPromise = null; + resolve(null); + return; + } + + this._goodAudioClip = clip; + resolve(clip); + }); + }); + + return this._goodAudioLoadPromise; } /** @@ -2447,6 +2625,15 @@ export class PageLevel extends BaseView { } } + /** 清理尚未触发的 pose 赞美延迟播放,避免快速切关后串到下一题。 */ + private _clearPosePraiseDelayTimer(): void { + this._posePraiseSequenceId++; + if (this._posePraiseDelayTimer !== null) { + clearTimeout(this._posePraiseDelayTimer); + this._posePraiseDelayTimer = null; + } + } + /** 底部两层淡出(透明度 → 0),完成后 active = false */ private _fadeOutBottomLayers(): void { for (const layer of [this.bottomLayoutNode, this.tipsLayout]) { @@ -2476,6 +2663,13 @@ export class PageLevel extends BaseView { for (const layer of [this.bottomLayoutNode, this.tipsLayout]) { if (!layer) continue; + // 分享模式下 bottomLayoutNode 由 _refreshModeUI 强制隐藏(pkNextLevelButton 接管该位置), + // 不能在 _fadeInBottomLayers 里偷偷打开它,否则两个「下一题」入口又会同时出现。 + // tipsLayout 在两种模式下都需要恢复,这里只跳过 bottomLayoutNode。 + if (layer === this.bottomLayoutNode && this._isShareMode) { + continue; + } + const opacity = this._ensureUIOpacity(layer); Tween.stopAllByTarget(opacity); layer.active = true; diff --git a/assets/prefabs/PagePKEnd.prefab b/assets/prefabs/PagePKEnd.prefab index 508c2c9..c04fda8 100644 --- a/assets/prefabs/PagePKEnd.prefab +++ b/assets/prefabs/PagePKEnd.prefab @@ -2241,6 +2241,8 @@ "__id__": 0 }, "fileId": "90w8HRdbBPLYFqBkhPhWfM", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { @@ -2254,9 +2256,6 @@ "_children": [ { "__id__": 93 - }, - { - "__id__": 147 } ], "_active": true, @@ -2274,7 +2273,7 @@ "_lpos": { "__type__": "cc.Vec3", "x": -8.201, - "y": 492.399, + "y": -183.147, "z": 0 }, "_lrot": { @@ -2311,27 +2310,30 @@ "_children": [ { "__id__": 94 + }, + { + "__id__": 140 } ], "_active": true, "_components": [ { - "__id__": 140 + "__id__": 148 }, { - "__id__": 142 + "__id__": 150 }, { - "__id__": 144 + "__id__": 152 } ], "_prefab": { - "__id__": 146 + "__id__": 154 }, "_lpos": { "__type__": "cc.Vec3", "x": 0, - "y": -80.522, + "y": 46.995, "z": 0 }, "_lrot": { @@ -2382,7 +2384,7 @@ "_lpos": { "__type__": "cc.Vec3", "x": -10, - "y": 125, + "y": 540, "z": 0 }, "_lrot": { @@ -3459,7 +3461,7 @@ "_contentSize": { "__type__": "cc.Size", "width": 780, - "height": 400 + "height": 1080 }, "_anchorPoint": { "__type__": "cc.Vec2", @@ -3485,6 +3487,181 @@ "targetOverrides": null, "nestedPrefabInstanceRoots": null }, + { + "__type__": "cc.Node", + "_name": "ScrolViewMask", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 93 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 141 + }, + { + "__id__": 143 + }, + { + "__id__": 145 + } + ], + "_prefab": { + "__id__": 147 + }, + "_lpos": { + "__type__": "cc.Vec3", + "x": 9.242999999999938, + "y": -517.9897285399853, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 1, + "w": 6.123233995736766e-17 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 0.7336757153338225, + "y": 0.7336757153338225, + "z": 0.7336757153338225 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": 180, + "y": 180, + "z": 7.016709298534876e-15 + }, + "_id": "" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 140 + }, + "_enabled": true, + "__prefab": { + "__id__": 142 + }, + "_contentSize": { + "__type__": "cc.Size", + "width": 1444.78, + "height": 60 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "57cb61nstNg7YGomw8vXcp" + }, + { + "__type__": "cc.Sprite", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 140 + }, + "_enabled": true, + "__prefab": { + "__id__": 144 + }, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 225, + "g": 245, + "b": 197, + "a": 255 + }, + "_spriteFrame": { + "__uuid__": "faab3d46-e885-4c46-8f19-9f872e7d6973@f9941", + "__expectedType__": "cc.SpriteFrame" + }, + "_type": 0, + "_fillType": 0, + "_sizeMode": 0, + "_fillCenter": { + "__type__": "cc.Vec2", + "x": 0, + "y": 0 + }, + "_fillStart": 0, + "_fillRange": 0, + "_isTrimmedMode": true, + "_useGrayscale": false, + "_atlas": null, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "d5kG67QW9Nqplq9kKPqg4X" + }, + { + "__type__": "cc.Widget", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 140 + }, + "_enabled": true, + "__prefab": { + "__id__": 146 + }, + "_alignFlags": 44, + "_target": null, + "_left": -130.757, + "_right": -149.243, + "_top": 0, + "_bottom": 0, + "_horizontalCenter": 0, + "_verticalCenter": 0, + "_isAbsLeft": true, + "_isAbsRight": true, + "_isAbsTop": true, + "_isAbsBottom": true, + "_isAbsHorizontalCenter": true, + "_isAbsVerticalCenter": true, + "_originalWidth": 1080, + "_originalHeight": 0, + "_alignMode": 2, + "_lockFlags": 4, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "60ik9u5ftG4oQNCowOqvJi" + }, + { + "__type__": "cc.PrefabInfo", + "root": { + "__id__": 1 + }, + "asset": { + "__id__": 0 + }, + "fileId": "36lE6eex1I0Jo9+fLrBGoc", + "instance": null, + "targetOverrides": null, + "nestedPrefabInstanceRoots": null + }, { "__type__": "cc.UITransform", "_name": "", @@ -3495,12 +3672,12 @@ }, "_enabled": true, "__prefab": { - "__id__": 141 + "__id__": 149 }, "_contentSize": { "__type__": "cc.Size", "width": 780, - "height": 400 + "height": 1080 }, "_anchorPoint": { "__type__": "cc.Vec2", @@ -3523,7 +3700,7 @@ }, "_enabled": true, "__prefab": { - "__id__": 143 + "__id__": 151 }, "_type": 0, "_inverted": false, @@ -3545,7 +3722,7 @@ }, "_enabled": true, "__prefab": { - "__id__": 145 + "__id__": 153 }, "_customMaterial": null, "_srcBlendFactor": 2, @@ -3594,181 +3771,6 @@ "targetOverrides": null, "nestedPrefabInstanceRoots": null }, - { - "__type__": "cc.Node", - "_name": "ScrolViewMask", - "_objFlags": 0, - "__editorExtras__": {}, - "_parent": { - "__id__": 92 - }, - "_children": [], - "_active": true, - "_components": [ - { - "__id__": 148 - }, - { - "__id__": 150 - }, - { - "__id__": 152 - } - ], - "_prefab": { - "__id__": 154 - }, - "_lpos": { - "__type__": "cc.Vec3", - "x": 9.242999999999938, - "y": -173.667, - "z": 0 - }, - "_lrot": { - "__type__": "cc.Quat", - "x": 0, - "y": 0, - "z": 1, - "w": 6.123233995736766e-17 - }, - "_lscale": { - "__type__": "cc.Vec3", - "x": 1, - "y": 1, - "z": 1 - }, - "_mobility": 0, - "_layer": 1073741824, - "_euler": { - "__type__": "cc.Vec3", - "x": 0, - "y": 0, - "z": 180 - }, - "_id": "" - }, - { - "__type__": "cc.UITransform", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 147 - }, - "_enabled": true, - "__prefab": { - "__id__": 149 - }, - "_contentSize": { - "__type__": "cc.Size", - "width": 1080, - "height": 60 - }, - "_anchorPoint": { - "__type__": "cc.Vec2", - "x": 0.5, - "y": 0.5 - }, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "57cb61nstNg7YGomw8vXcp" - }, - { - "__type__": "cc.Sprite", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 147 - }, - "_enabled": true, - "__prefab": { - "__id__": 151 - }, - "_customMaterial": null, - "_srcBlendFactor": 2, - "_dstBlendFactor": 4, - "_color": { - "__type__": "cc.Color", - "r": 225, - "g": 245, - "b": 197, - "a": 255 - }, - "_spriteFrame": { - "__uuid__": "faab3d46-e885-4c46-8f19-9f872e7d6973@f9941", - "__expectedType__": "cc.SpriteFrame" - }, - "_type": 0, - "_fillType": 0, - "_sizeMode": 0, - "_fillCenter": { - "__type__": "cc.Vec2", - "x": 0, - "y": 0 - }, - "_fillStart": 0, - "_fillRange": 0, - "_isTrimmedMode": true, - "_useGrayscale": false, - "_atlas": null, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "d5kG67QW9Nqplq9kKPqg4X" - }, - { - "__type__": "cc.Widget", - "_name": "", - "_objFlags": 0, - "__editorExtras__": {}, - "node": { - "__id__": 147 - }, - "_enabled": true, - "__prefab": { - "__id__": 153 - }, - "_alignFlags": 40, - "_target": null, - "_left": -130.757, - "_right": -149.243, - "_top": 0, - "_bottom": 0, - "_horizontalCenter": 0, - "_verticalCenter": 0, - "_isAbsLeft": true, - "_isAbsRight": true, - "_isAbsTop": true, - "_isAbsBottom": true, - "_isAbsHorizontalCenter": true, - "_isAbsVerticalCenter": true, - "_originalWidth": 1080, - "_originalHeight": 0, - "_alignMode": 2, - "_lockFlags": 0, - "_id": "" - }, - { - "__type__": "cc.CompPrefabInfo", - "fileId": "60ik9u5ftG4oQNCowOqvJi" - }, - { - "__type__": "cc.PrefabInfo", - "root": { - "__id__": 1 - }, - "asset": { - "__id__": 0 - }, - "fileId": "36lE6eex1I0Jo9+fLrBGoc", - "instance": null, - "targetOverrides": null, - "nestedPrefabInstanceRoots": null - }, { "__type__": "cc.UITransform", "_name": "", @@ -3784,7 +3786,7 @@ "_contentSize": { "__type__": "cc.Size", "width": 800, - "height": 400 + "height": 1600 }, "_anchorPoint": { "__type__": "cc.Vec2", @@ -3837,6 +3839,8 @@ "__id__": 0 }, "fileId": "bdqvu61fVFTIU1TQqs/qES", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { diff --git a/assets/prefabs/PageWriteLevels.prefab b/assets/prefabs/PageWriteLevels.prefab index f5548d2..5ad1128 100644 --- a/assets/prefabs/PageWriteLevels.prefab +++ b/assets/prefabs/PageWriteLevels.prefab @@ -5396,6 +5396,10 @@ "dataBtn": { "__id__": 34 }, + "commonModalPrefab": { + "__uuid__": "5379669e-7cd7-45b6-9dd3-4a021730b23e", + "__expectedType__": "cc.Prefab" + }, "roundedSpriteEffect": { "__uuid__": "f0080a34-1786-4547-8d81-d89cc517b63e", "__expectedType__": "cc.EffectAsset" @@ -5483,4 +5487,4 @@ "instance": null, "targetOverrides": null } -] \ No newline at end of file +] diff --git a/assets/prefabs/PageWriteLevels.ts b/assets/prefabs/PageWriteLevels.ts index 5ea46b4..b21917a 100644 --- a/assets/prefabs/PageWriteLevels.ts +++ b/assets/prefabs/PageWriteLevels.ts @@ -1,6 +1,7 @@ -import { _decorator, Node, Button, Sprite, Label, Toggle, ScrollView, EditBox, instantiate, UITransform, Vec2, EventTouch, EffectAsset } from 'cc'; +import { _decorator, Node, Button, Sprite, Label, Toggle, ScrollView, EditBox, instantiate, UITransform, Vec2, EventTouch, EffectAsset, Prefab } from 'cc'; import { BaseView } from 'db://assets/scripts/core/BaseView'; import { ViewManager } from 'db://assets/scripts/core/ViewManager'; +import { CommonModal } from 'db://assets/prefabs/CommonModal'; import { CompletedLevelsManager } from 'db://assets/scripts/utils/CompletedLevelsManager'; import { ToastManager } from 'db://assets/scripts/utils/ToastManager'; import { ShareManager } from 'db://assets/scripts/utils/ShareManager'; @@ -30,6 +31,7 @@ const LAYOUT_CONFIG = { CENTER_ROWS: 2, VIEW_WIDTH: 900, VIEW_HEIGHT: 1300, + LIST_BOTTOM_GAP_TO_TITLE: 54, }; /** 必须选择的关卡数量 */ @@ -67,6 +69,9 @@ export class PageWriteLevels extends BaseView { @property({ type: EffectAsset, tooltip: '关卡封面圆角材质 EffectAsset' }) roundedSpriteEffect: EffectAsset | null = null; + @property({ type: Prefab, tooltip: '通用弹窗预制体' }) + commonModalPrefab: Prefab | null = null; + @property({ tooltip: '关卡封面圆角半径比例(相对于短边,0-0.5)' }) coverCornerRadius: number = 0.1; @@ -86,6 +91,7 @@ export class PageWriteLevels extends BaseView { console.log('[PageWriteLevels] onViewLoad'); this._initButtons(); this._initScrollView(); + this._resizeScrollViewport(); this._updateSelectionUI(); } @@ -132,12 +138,51 @@ export class PageWriteLevels extends BaseView { onViewShow(): void { console.log('[PageWriteLevels] onViewShow'); + this._resizeScrollViewport(); + this._updateContentSize(); + // 仅首次初始化列表,从预览页返回时保留选中状态 if (this._itemNodes.length === 0) { void this._initLevelList(); } } + private _resizeScrollViewport(): void { + if (!this.scrollView || !this._viewTransform || !this.shareTitleEditBox) { + return; + } + + const rootTransform = this.node.getComponent(UITransform); + const scrollTransform = this.scrollView.getComponent(UITransform); + const scrollWidget = this.scrollView.getComponent('cc.Widget') as any; + const shareTitleTransform = this.shareTitleEditBox.getComponent(UITransform); + const shareTitleWidget = this.shareTitleEditBox.getComponent('cc.Widget') as any; + const bottomMaskNode = this.scrollView.getChildByName('ScrolViewMask'); + + if (!rootTransform || !scrollTransform || !shareTitleTransform || !scrollWidget || !shareTitleWidget) { + return; + } + + const topInset = Number(scrollWidget.top ?? 0); + const shareBottomInset = Number(shareTitleWidget.bottom ?? 0); + const shareTitleHeight = shareTitleTransform.height * Math.abs(this.shareTitleEditBox.scale.y); + const nextHeight = Math.max( + LAYOUT_CONFIG.VIEW_HEIGHT, + rootTransform.height - topInset - shareBottomInset - shareTitleHeight - LAYOUT_CONFIG.LIST_BOTTOM_GAP_TO_TITLE, + ); + + scrollTransform.setContentSize(scrollTransform.width, nextHeight); + this._viewTransform.setContentSize(this._viewTransform.width, nextHeight); + + if (bottomMaskNode) { + bottomMaskNode.setPosition( + bottomMaskNode.position.x, + -(nextHeight / 2) + 30, + bottomMaskNode.position.z, + ); + } + } + private async _initLevelList(): Promise { this._clearList(); @@ -474,11 +519,11 @@ export class PageWriteLevels extends BaseView { } } - // 更新 CompleteButton 和 PreviewButton 的可用状态 + // 预览与分享数量不足时也要允许点击,统一弹出提示弹窗。 if (this.completeBtn) { const btn = this.completeBtn.getComponent(Button); if (btn) { - btn.interactable = isFull; + btn.interactable = true; } } if (this.previewBtn) { @@ -501,18 +546,31 @@ export class PageWriteLevels extends BaseView { } /** - * 校验是否已选满关卡,未满则 Toast 提示 + * 校验是否已选满关卡,未满则弹出统一提示弹窗 * @returns true 表示校验通过 */ private _validateSelection(): boolean { if (this._selectedIndices.size < MAX_SELECTION) { - const remaining = MAX_SELECTION - this._selectedIndices.size; - ToastManager.instance.show(`还需选择${remaining}个关卡`); + this._showSelectionRequiredModal(); return false; } return true; } + private _showSelectionRequiredModal(): void { + if (!this.commonModalPrefab) { + console.warn('[PageWriteLevels] commonModalPrefab 未设置,回退为 Toast 提示'); + ToastManager.instance.show(`请选择${MAX_SELECTION}个关卡后再预览或分享`); + return; + } + + CommonModal.show(this.commonModalPrefab, { + title: '提示', + content: `要选择${MAX_SELECTION}个关卡才能分享和预览`, + buttonConfirm: '知道了', + }); + } + private _onPreviewClick(): void { AudioManager.instance.playButtonClick(); if (!this._validateSelection()) return; diff --git a/assets/resources/audios/good.mp3 b/assets/resources/audios/good.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..3df944b8f8f0875d9d2375dd686681d8d94c90da GIT binary patch literal 16977 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 literal 0 HcmV?d00001 diff --git a/assets/resources/audios/good.mp3.meta b/assets/resources/audios/good.mp3.meta new file mode 100644 index 0000000..1a44ce2 --- /dev/null +++ b/assets/resources/audios/good.mp3.meta @@ -0,0 +1,14 @@ +{ + "ver": "1.0.0", + "importer": "audio-clip", + "imported": true, + "uuid": "f1b26185-7493-4c10-b45a-e35ecc86c507", + "files": [ + ".json", + ".mp3" + ], + "subMetas": {}, + "userData": { + "downloadMode": 0 + } +}