feat: 优化通关效果
This commit is contained in:
@@ -5471,7 +5471,7 @@
|
|||||||
},
|
},
|
||||||
"_contentSize": {
|
"_contentSize": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 442.03125,
|
"width": 461.40625,
|
||||||
"height": 75.6
|
"height": 75.6
|
||||||
},
|
},
|
||||||
"_anchorPoint": {
|
"_anchorPoint": {
|
||||||
|
|||||||
@@ -9346,8 +9346,8 @@
|
|||||||
},
|
},
|
||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": -8.80499999999995,
|
"x": -8.805,
|
||||||
"y": 748.5280000000002,
|
"y": 167.297,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
|
|||||||
@@ -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 { BaseView } from 'db://assets/scripts/core/BaseView';
|
||||||
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
||||||
import { StaminaManager } from 'db://assets/scripts/utils/StaminaManager';
|
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 DEFAULT_STAMINA_MAX = 50;
|
||||||
|
|
||||||
/** 答案正确后到弹出通关弹窗之间的停留时间(不论是否有谐音梗都保持一致) */
|
/** 答案正确后到弹出通关弹窗之间的停留时间(不论是否有谐音梗都保持一致) */
|
||||||
private static readonly PASS_MODAL_DELAY_MS = 1000;
|
private static readonly PASS_MODAL_DELAY_MS = 800;
|
||||||
|
|
||||||
/** 图片2描述默认文案 */
|
/** 图片2描述默认文案 */
|
||||||
private static readonly DEFAULT_IMAGE2_DESCRIPTION = '这是什么?';
|
private static readonly DEFAULT_IMAGE2_DESCRIPTION = '这是什么?';
|
||||||
@@ -91,6 +91,12 @@ export class PageLevel extends BaseView {
|
|||||||
/** 彩带 spine 动画名(一次播放) */
|
/** 彩带 spine 动画名(一次播放) */
|
||||||
private static readonly CAIDAI_ANIMATION_NAME = 'open';
|
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 节点)档位:[最小通关数, 动画名],倒序匹配。
|
* 通关赞美 spine(pose 节点)档位:[最小通关数, 动画名],倒序匹配。
|
||||||
* 1-5 关 → "1",6-10 关 → "2",11+ 关 → "3"。
|
* 1-5 关 → "1",6-10 关 → "2",11+ 关 → "3"。
|
||||||
@@ -346,6 +352,9 @@ export class PageLevel extends BaseView {
|
|||||||
/** PassNode 原始 local position(prefab 摆放位),动画结束后用来回归 */
|
/** PassNode 原始 local position(prefab 摆放位),动画结束后用来回归 */
|
||||||
private _passNodeOriginalPos: Vec3 | null = null;
|
private _passNodeOriginalPos: Vec3 | null = null;
|
||||||
|
|
||||||
|
/** PassNode Widget 初始启用状态;进出场动画期间临时关闭,避免激活首帧回写位置 */
|
||||||
|
private _passNodeWidgetOriginalEnabled: boolean | null = null;
|
||||||
|
|
||||||
/** 通关页所用「已通关数量」(业务数据,给成就体系展示用) */
|
/** 通关页所用「已通关数量」(业务数据,给成就体系展示用) */
|
||||||
private _passCompletedLevelCount: number | null = null;
|
private _passCompletedLevelCount: number | null = null;
|
||||||
|
|
||||||
@@ -358,6 +367,18 @@ export class PageLevel extends BaseView {
|
|||||||
/** pose Spine 隐藏延时定时器(setTimeout 句柄);切关 / 关页时需清理避免穿屏触发 */
|
/** pose Spine 隐藏延时定时器(setTimeout 句柄);切关 / 关页时需清理避免穿屏触发 */
|
||||||
private _poseHideTimer: ReturnType<typeof setTimeout> | null = null;
|
private _poseHideTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
/** pose 赞美延迟播放定时器;等待通关动画与成功音效结束后再触发 */
|
||||||
|
private _posePraiseDelayTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
/** pose 赞美延迟播放序号,用于取消 resources.load 异步回调里的过期播放 */
|
||||||
|
private _posePraiseSequenceId: number = 0;
|
||||||
|
|
||||||
|
/** good.mp3 加载缓存 */
|
||||||
|
private _goodAudioClip: AudioClip | null = null;
|
||||||
|
|
||||||
|
/** good.mp3 加载中的 Promise,避免重复请求 resources */
|
||||||
|
private _goodAudioLoadPromise: Promise<AudioClip | null> | null = null;
|
||||||
|
|
||||||
/** 通关页动画起点(通关前)的已通关数量;为 null 表示不播跨称号过渡 */
|
/** 通关页动画起点(通关前)的已通关数量;为 null 表示不播跨称号过渡 */
|
||||||
private _passPreviousCompletedLevelCount: number | null = null;
|
private _passPreviousCompletedLevelCount: number | null = null;
|
||||||
|
|
||||||
@@ -703,10 +724,8 @@ export class PageLevel extends BaseView {
|
|||||||
// 设置图片描述
|
// 设置图片描述
|
||||||
this.setImageDescriptions(config.image1Description, config.image2Description);
|
this.setImageDescriptions(config.image1Description, config.image2Description);
|
||||||
|
|
||||||
// 设置关卡标题
|
// 设置关卡标题与主线 / 分享模式常驻 UI 可见性
|
||||||
this.updateTitleLevelLabel();
|
this._refreshModeUI();
|
||||||
this.updatePkLevelProgressLabel();
|
|
||||||
this.updatePkNextLevelButtonText();
|
|
||||||
|
|
||||||
// 隐藏包袱答案,通关后再按 punchline 展示
|
// 隐藏包袱答案,通关后再按 punchline 展示
|
||||||
this.hidePunchline();
|
this.hidePunchline();
|
||||||
@@ -1178,10 +1197,36 @@ export class PageLevel extends BaseView {
|
|||||||
if (this._isSubmittingShareResult) {
|
if (this._isSubmittingShareResult) {
|
||||||
return;
|
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) {
|
if (this._isTransitioning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 未通关就点下一题 = 跳过当前题,弹二次确认避免误触
|
||||||
this.playClickSound();
|
this.playClickSound();
|
||||||
this._showShareNextConfirmModal(() => {
|
this._showShareNextConfirmModal(() => {
|
||||||
this._recordCurrentShareSubmission();
|
this._recordCurrentShareSubmission();
|
||||||
@@ -2135,11 +2180,17 @@ export class PageLevel extends BaseView {
|
|||||||
this._setupPassNodeContent();
|
this._setupPassNodeContent();
|
||||||
this._bindPassNodeEvents();
|
this._bindPassNodeEvents();
|
||||||
|
|
||||||
// 启动彩带 + 滑入动画 + 淡出底部UI + 通关音效 + 赞美 spine
|
// 先让 PassNode 完整入场,再启动通关动效和成功音效;pose 赞美段延后到它们结束后播放。
|
||||||
this._playCaidai();
|
this._playPassNodeShowAnimation(() => {
|
||||||
this._playPosePraise(this._sessionPassCount);
|
const caidaiDuration = this._playCaidai();
|
||||||
this._playPassNodeShowAnimation();
|
this.playSuccessSound();
|
||||||
this.playSuccessSound();
|
if (this._isShareMode) {
|
||||||
|
this._playPosePraise(this._sessionPassCount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._schedulePosePraiseAfterPassEffects(this._sessionPassCount, caidaiDuration);
|
||||||
|
});
|
||||||
|
|
||||||
console.log('[PageLevel] 显示通关页 PassNode');
|
console.log('[PageLevel] 显示通关页 PassNode');
|
||||||
}
|
}
|
||||||
@@ -2186,6 +2237,16 @@ export class PageLevel extends BaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NextLevelButton 文案
|
// 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 isFinalShareSubmit = this._isShareMode && this._isFinalShareLevel();
|
||||||
const nextLabel = this.passNextLevelButton?.getChildByName('Label')?.getComponent(Label);
|
const nextLabel = this.passNextLevelButton?.getChildByName('Label')?.getComponent(Label);
|
||||||
if (nextLabel) {
|
if (nextLabel) {
|
||||||
@@ -2193,6 +2254,10 @@ export class PageLevel extends BaseView {
|
|||||||
? PageLevel.SHARE_SUBMIT_BUTTON_TEXT
|
? PageLevel.SHARE_SUBMIT_BUTTON_TEXT
|
||||||
: '下一关';
|
: '下一关';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分享模式下 PassNode 展示后,让 pkNextLevelButton 文案与最终态保持同步
|
||||||
|
// (SHARE_SUBMIT_BUTTON_TEXT vs SHARE_NEXT_BUTTON_TEXT),由 updatePkNextLevelButtonText 统一处理
|
||||||
|
this.updatePkNextLevelButtonText();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _ensureTitleAnimator(): AchievementTitleAnimator {
|
private _ensureTitleAnimator(): AchievementTitleAnimator {
|
||||||
@@ -2213,6 +2278,8 @@ export class PageLevel extends BaseView {
|
|||||||
private _initPassNodeState(): void {
|
private _initPassNodeState(): void {
|
||||||
if (this.passNode) {
|
if (this.passNode) {
|
||||||
this._passNodeOriginalPos = this.passNode.position.clone();
|
this._passNodeOriginalPos = this.passNode.position.clone();
|
||||||
|
const widget = this.passNode.getComponent(Widget);
|
||||||
|
this._passNodeWidgetOriginalEnabled = widget?.enabled ?? null;
|
||||||
this.passNode.active = false;
|
this.passNode.active = false;
|
||||||
}
|
}
|
||||||
if (this.caidaiNode) {
|
if (this.caidaiNode) {
|
||||||
@@ -2224,6 +2291,7 @@ export class PageLevel extends BaseView {
|
|||||||
if (this.poseNode) {
|
if (this.poseNode) {
|
||||||
this.poseNode.active = false;
|
this.poseNode.active = false;
|
||||||
}
|
}
|
||||||
|
this._clearPosePraiseDelayTimer();
|
||||||
this._clearPoseHideTimer();
|
this._clearPoseHideTimer();
|
||||||
|
|
||||||
this._isPassNodeShown = false;
|
this._isPassNodeShown = false;
|
||||||
@@ -2231,7 +2299,7 @@ export class PageLevel extends BaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** PassNode 进入动画:底部两层淡出 + PassNode 从屏幕左侧滑入 */
|
/** PassNode 进入动画:底部两层淡出 + PassNode 从屏幕左侧滑入 */
|
||||||
private _playPassNodeShowAnimation(): void {
|
private _playPassNodeShowAnimation(onShown?: () => void): void {
|
||||||
if (!this.passNode) return;
|
if (!this.passNode) return;
|
||||||
|
|
||||||
this._isPassNodeAnimating = true;
|
this._isPassNodeAnimating = true;
|
||||||
@@ -2250,8 +2318,9 @@ export class PageLevel extends BaseView {
|
|||||||
const startX = originalPos.x - screenWidth;
|
const startX = originalPos.x - screenWidth;
|
||||||
|
|
||||||
Tween.stopAllByTarget(this.passNode);
|
Tween.stopAllByTarget(this.passNode);
|
||||||
this.passNode.active = true;
|
this._setPassNodeWidgetEnabledForAnimation(false);
|
||||||
this.passNode.setPosition(startX, originalPos.y, originalPos.z);
|
this.passNode.setPosition(startX, originalPos.y, originalPos.z);
|
||||||
|
this.passNode.active = true;
|
||||||
|
|
||||||
tween(this.passNode)
|
tween(this.passNode)
|
||||||
.to(
|
.to(
|
||||||
@@ -2260,7 +2329,9 @@ export class PageLevel extends BaseView {
|
|||||||
{ easing: 'cubicOut' },
|
{ easing: 'cubicOut' },
|
||||||
)
|
)
|
||||||
.call(() => {
|
.call(() => {
|
||||||
|
this._setPassNodeWidgetEnabledForAnimation(true);
|
||||||
this._isPassNodeAnimating = false;
|
this._isPassNodeAnimating = false;
|
||||||
|
onShown?.();
|
||||||
})
|
})
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
@@ -2269,7 +2340,7 @@ export class PageLevel extends BaseView {
|
|||||||
* PassNode 退出(用户点下一关时调用):滑出 PassNode + 底部两层淡入
|
* PassNode 退出(用户点下一关时调用):滑出 PassNode + 底部两层淡入
|
||||||
* 返回 Promise,外部链路通常 await 后再调 goToNextLevel
|
* 返回 Promise,外部链路通常 await 后再调 goToNextLevel
|
||||||
*/
|
*/
|
||||||
private _hidePassNode(): Promise<void> {
|
private _hidePassNode(restoreBottomLayers: boolean = true): Promise<void> {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
if (!this.passNode || !this._isPassNodeShown) {
|
if (!this.passNode || !this._isPassNodeShown) {
|
||||||
resolve();
|
resolve();
|
||||||
@@ -2279,7 +2350,9 @@ export class PageLevel extends BaseView {
|
|||||||
this._isPassNodeAnimating = true;
|
this._isPassNodeAnimating = true;
|
||||||
|
|
||||||
// 底部两层淡入恢复
|
// 底部两层淡入恢复
|
||||||
this._fadeInBottomLayers();
|
if (restoreBottomLayers) {
|
||||||
|
this._fadeInBottomLayers();
|
||||||
|
}
|
||||||
|
|
||||||
// 滑出 PassNode(向左滑出屏幕)
|
// 滑出 PassNode(向左滑出屏幕)
|
||||||
const screenWidth = view.getVisibleSize().width;
|
const screenWidth = view.getVisibleSize().width;
|
||||||
@@ -2287,6 +2360,7 @@ export class PageLevel extends BaseView {
|
|||||||
const exitX = originalPos.x - screenWidth;
|
const exitX = originalPos.x - screenWidth;
|
||||||
|
|
||||||
Tween.stopAllByTarget(this.passNode);
|
Tween.stopAllByTarget(this.passNode);
|
||||||
|
this._setPassNodeWidgetEnabledForAnimation(false);
|
||||||
tween(this.passNode)
|
tween(this.passNode)
|
||||||
.to(
|
.to(
|
||||||
PageLevel.PASS_NODE_SLIDE_DURATION,
|
PageLevel.PASS_NODE_SLIDE_DURATION,
|
||||||
@@ -2308,6 +2382,7 @@ export class PageLevel extends BaseView {
|
|||||||
if (this._passNodeOriginalPos) {
|
if (this._passNodeOriginalPos) {
|
||||||
this.passNode.setPosition(this._passNodeOriginalPos);
|
this.passNode.setPosition(this._passNodeOriginalPos);
|
||||||
}
|
}
|
||||||
|
this._setPassNodeWidgetEnabledForAnimation(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关掉彩带
|
// 关掉彩带
|
||||||
@@ -2319,6 +2394,7 @@ export class PageLevel extends BaseView {
|
|||||||
if (this.poseNode) {
|
if (this.poseNode) {
|
||||||
this.poseNode.active = false;
|
this.poseNode.active = false;
|
||||||
}
|
}
|
||||||
|
this._clearPosePraiseDelayTimer();
|
||||||
this._clearPoseHideTimer();
|
this._clearPoseHideTimer();
|
||||||
|
|
||||||
this._unbindPassNodeEvents();
|
this._unbindPassNodeEvents();
|
||||||
@@ -2339,6 +2415,7 @@ export class PageLevel extends BaseView {
|
|||||||
if (this._passNodeOriginalPos) {
|
if (this._passNodeOriginalPos) {
|
||||||
this.passNode.setPosition(this._passNodeOriginalPos);
|
this.passNode.setPosition(this._passNodeOriginalPos);
|
||||||
}
|
}
|
||||||
|
this._setPassNodeWidgetEnabledForAnimation(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.caidaiNode) {
|
if (this.caidaiNode) {
|
||||||
@@ -2349,6 +2426,7 @@ export class PageLevel extends BaseView {
|
|||||||
if (this.poseNode) {
|
if (this.poseNode) {
|
||||||
this.poseNode.active = false;
|
this.poseNode.active = false;
|
||||||
}
|
}
|
||||||
|
this._clearPosePraiseDelayTimer();
|
||||||
this._clearPoseHideTimer();
|
this._clearPoseHideTimer();
|
||||||
|
|
||||||
this._restoreBottomLayersImmediate();
|
this._restoreBottomLayersImmediate();
|
||||||
@@ -2358,6 +2436,19 @@ export class PageLevel extends BaseView {
|
|||||||
this._isPassNodeAnimating = false;
|
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) */
|
/** PassNode 事件绑定(NextLevel + Share) */
|
||||||
private _bindPassNodeEvents(): void {
|
private _bindPassNodeEvents(): void {
|
||||||
// 防御:先解绑,避免重复绑定
|
// 防御:先解绑,避免重复绑定
|
||||||
@@ -2390,11 +2481,38 @@ export class PageLevel extends BaseView {
|
|||||||
|
|
||||||
AudioManager.instance.playButtonClick();
|
AudioManager.instance.playButtonClick();
|
||||||
|
|
||||||
// 普通模式 _showShareNextConfirmModal 内部首行就 onConfirm(),所以两条路统一
|
if (this._isShareMode) {
|
||||||
this._showShareNextConfirmModal(async () => {
|
// 分享模式题目已预加载,继续保持“先换题、再退场”,避免露出上一题内容。
|
||||||
await this._hidePassNode();
|
this._showShareNextConfirmModal(() => {
|
||||||
void this.goToNextLevel();
|
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",单次播放 */
|
/** 启动彩带 spine 动画 "open",单次播放,返回动画时长(秒)用于串联后续赞美段 */
|
||||||
private _playCaidai(): void {
|
private _playCaidai(): number {
|
||||||
if (!this.caidaiNode || !this.caidaiSkeleton) return;
|
if (!this.caidaiNode || !this.caidaiSkeleton) return 0;
|
||||||
|
|
||||||
this.caidaiNode.active = true;
|
this.caidaiNode.active = true;
|
||||||
// setAnimation 三参:trackIndex, name, loop
|
// 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<void> {
|
||||||
|
const goodAudio = await this._loadGoodAudioClip();
|
||||||
|
if (sequenceId !== this._posePraiseSequenceId || !this._isPassNodeShown || !this.node.isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._playPosePraise(count);
|
||||||
|
this.playSound(goodAudio);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadGoodAudioClip(): Promise<AudioClip | null> {
|
||||||
|
if (this._goodAudioClip) {
|
||||||
|
return Promise.resolve(this._goodAudioClip);
|
||||||
|
}
|
||||||
|
if (this._goodAudioLoadPromise) {
|
||||||
|
return this._goodAudioLoadPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._goodAudioLoadPromise = new Promise<AudioClip | null>((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 */
|
/** 底部两层淡出(透明度 → 0),完成后 active = false */
|
||||||
private _fadeOutBottomLayers(): void {
|
private _fadeOutBottomLayers(): void {
|
||||||
for (const layer of [this.bottomLayoutNode, this.tipsLayout]) {
|
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]) {
|
for (const layer of [this.bottomLayoutNode, this.tipsLayout]) {
|
||||||
if (!layer) continue;
|
if (!layer) continue;
|
||||||
|
|
||||||
|
// 分享模式下 bottomLayoutNode 由 _refreshModeUI 强制隐藏(pkNextLevelButton 接管该位置),
|
||||||
|
// 不能在 _fadeInBottomLayers 里偷偷打开它,否则两个「下一题」入口又会同时出现。
|
||||||
|
// tipsLayout 在两种模式下都需要恢复,这里只跳过 bottomLayoutNode。
|
||||||
|
if (layer === this.bottomLayoutNode && this._isShareMode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const opacity = this._ensureUIOpacity(layer);
|
const opacity = this._ensureUIOpacity(layer);
|
||||||
Tween.stopAllByTarget(opacity);
|
Tween.stopAllByTarget(opacity);
|
||||||
layer.active = true;
|
layer.active = true;
|
||||||
|
|||||||
@@ -2241,6 +2241,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "90w8HRdbBPLYFqBkhPhWfM",
|
"fileId": "90w8HRdbBPLYFqBkhPhWfM",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2254,9 +2256,6 @@
|
|||||||
"_children": [
|
"_children": [
|
||||||
{
|
{
|
||||||
"__id__": 93
|
"__id__": 93
|
||||||
},
|
|
||||||
{
|
|
||||||
"__id__": 147
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_active": true,
|
"_active": true,
|
||||||
@@ -2274,7 +2273,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": -8.201,
|
"x": -8.201,
|
||||||
"y": 492.399,
|
"y": -183.147,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -2311,27 +2310,30 @@
|
|||||||
"_children": [
|
"_children": [
|
||||||
{
|
{
|
||||||
"__id__": 94
|
"__id__": 94
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__id__": 140
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_active": true,
|
"_active": true,
|
||||||
"_components": [
|
"_components": [
|
||||||
{
|
{
|
||||||
"__id__": 140
|
"__id__": 148
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__id__": 142
|
"__id__": 150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__id__": 144
|
"__id__": 152
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_prefab": {
|
"_prefab": {
|
||||||
"__id__": 146
|
"__id__": 154
|
||||||
},
|
},
|
||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": -80.522,
|
"y": 46.995,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -2382,7 +2384,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": -10,
|
"x": -10,
|
||||||
"y": 125,
|
"y": 540,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -3459,7 +3461,7 @@
|
|||||||
"_contentSize": {
|
"_contentSize": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 780,
|
"width": 780,
|
||||||
"height": 400
|
"height": 1080
|
||||||
},
|
},
|
||||||
"_anchorPoint": {
|
"_anchorPoint": {
|
||||||
"__type__": "cc.Vec2",
|
"__type__": "cc.Vec2",
|
||||||
@@ -3485,6 +3487,181 @@
|
|||||||
"targetOverrides": null,
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": 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",
|
"__type__": "cc.UITransform",
|
||||||
"_name": "",
|
"_name": "",
|
||||||
@@ -3495,12 +3672,12 @@
|
|||||||
},
|
},
|
||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": {
|
"__prefab": {
|
||||||
"__id__": 141
|
"__id__": 149
|
||||||
},
|
},
|
||||||
"_contentSize": {
|
"_contentSize": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 780,
|
"width": 780,
|
||||||
"height": 400
|
"height": 1080
|
||||||
},
|
},
|
||||||
"_anchorPoint": {
|
"_anchorPoint": {
|
||||||
"__type__": "cc.Vec2",
|
"__type__": "cc.Vec2",
|
||||||
@@ -3523,7 +3700,7 @@
|
|||||||
},
|
},
|
||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": {
|
"__prefab": {
|
||||||
"__id__": 143
|
"__id__": 151
|
||||||
},
|
},
|
||||||
"_type": 0,
|
"_type": 0,
|
||||||
"_inverted": false,
|
"_inverted": false,
|
||||||
@@ -3545,7 +3722,7 @@
|
|||||||
},
|
},
|
||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": {
|
"__prefab": {
|
||||||
"__id__": 145
|
"__id__": 153
|
||||||
},
|
},
|
||||||
"_customMaterial": null,
|
"_customMaterial": null,
|
||||||
"_srcBlendFactor": 2,
|
"_srcBlendFactor": 2,
|
||||||
@@ -3594,181 +3771,6 @@
|
|||||||
"targetOverrides": null,
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": 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",
|
"__type__": "cc.UITransform",
|
||||||
"_name": "",
|
"_name": "",
|
||||||
@@ -3784,7 +3786,7 @@
|
|||||||
"_contentSize": {
|
"_contentSize": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 400
|
"height": 1600
|
||||||
},
|
},
|
||||||
"_anchorPoint": {
|
"_anchorPoint": {
|
||||||
"__type__": "cc.Vec2",
|
"__type__": "cc.Vec2",
|
||||||
@@ -3837,6 +3839,8 @@
|
|||||||
"__id__": 0
|
"__id__": 0
|
||||||
},
|
},
|
||||||
"fileId": "bdqvu61fVFTIU1TQqs/qES",
|
"fileId": "bdqvu61fVFTIU1TQqs/qES",
|
||||||
|
"instance": null,
|
||||||
|
"targetOverrides": null,
|
||||||
"nestedPrefabInstanceRoots": null
|
"nestedPrefabInstanceRoots": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5396,6 +5396,10 @@
|
|||||||
"dataBtn": {
|
"dataBtn": {
|
||||||
"__id__": 34
|
"__id__": 34
|
||||||
},
|
},
|
||||||
|
"commonModalPrefab": {
|
||||||
|
"__uuid__": "5379669e-7cd7-45b6-9dd3-4a021730b23e",
|
||||||
|
"__expectedType__": "cc.Prefab"
|
||||||
|
},
|
||||||
"roundedSpriteEffect": {
|
"roundedSpriteEffect": {
|
||||||
"__uuid__": "f0080a34-1786-4547-8d81-d89cc517b63e",
|
"__uuid__": "f0080a34-1786-4547-8d81-d89cc517b63e",
|
||||||
"__expectedType__": "cc.EffectAsset"
|
"__expectedType__": "cc.EffectAsset"
|
||||||
@@ -5483,4 +5487,4 @@
|
|||||||
"instance": null,
|
"instance": null,
|
||||||
"targetOverrides": null
|
"targetOverrides": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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 { BaseView } from 'db://assets/scripts/core/BaseView';
|
||||||
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
||||||
|
import { CommonModal } from 'db://assets/prefabs/CommonModal';
|
||||||
import { CompletedLevelsManager } from 'db://assets/scripts/utils/CompletedLevelsManager';
|
import { CompletedLevelsManager } from 'db://assets/scripts/utils/CompletedLevelsManager';
|
||||||
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
|
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
|
||||||
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
|
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
|
||||||
@@ -30,6 +31,7 @@ const LAYOUT_CONFIG = {
|
|||||||
CENTER_ROWS: 2,
|
CENTER_ROWS: 2,
|
||||||
VIEW_WIDTH: 900,
|
VIEW_WIDTH: 900,
|
||||||
VIEW_HEIGHT: 1300,
|
VIEW_HEIGHT: 1300,
|
||||||
|
LIST_BOTTOM_GAP_TO_TITLE: 54,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 必须选择的关卡数量 */
|
/** 必须选择的关卡数量 */
|
||||||
@@ -67,6 +69,9 @@ export class PageWriteLevels extends BaseView {
|
|||||||
@property({ type: EffectAsset, tooltip: '关卡封面圆角材质 EffectAsset' })
|
@property({ type: EffectAsset, tooltip: '关卡封面圆角材质 EffectAsset' })
|
||||||
roundedSpriteEffect: EffectAsset | null = null;
|
roundedSpriteEffect: EffectAsset | null = null;
|
||||||
|
|
||||||
|
@property({ type: Prefab, tooltip: '通用弹窗预制体' })
|
||||||
|
commonModalPrefab: Prefab | null = null;
|
||||||
|
|
||||||
@property({ tooltip: '关卡封面圆角半径比例(相对于短边,0-0.5)' })
|
@property({ tooltip: '关卡封面圆角半径比例(相对于短边,0-0.5)' })
|
||||||
coverCornerRadius: number = 0.1;
|
coverCornerRadius: number = 0.1;
|
||||||
|
|
||||||
@@ -86,6 +91,7 @@ export class PageWriteLevels extends BaseView {
|
|||||||
console.log('[PageWriteLevels] onViewLoad');
|
console.log('[PageWriteLevels] onViewLoad');
|
||||||
this._initButtons();
|
this._initButtons();
|
||||||
this._initScrollView();
|
this._initScrollView();
|
||||||
|
this._resizeScrollViewport();
|
||||||
this._updateSelectionUI();
|
this._updateSelectionUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,12 +138,51 @@ export class PageWriteLevels extends BaseView {
|
|||||||
|
|
||||||
onViewShow(): void {
|
onViewShow(): void {
|
||||||
console.log('[PageWriteLevels] onViewShow');
|
console.log('[PageWriteLevels] onViewShow');
|
||||||
|
this._resizeScrollViewport();
|
||||||
|
this._updateContentSize();
|
||||||
|
|
||||||
// 仅首次初始化列表,从预览页返回时保留选中状态
|
// 仅首次初始化列表,从预览页返回时保留选中状态
|
||||||
if (this._itemNodes.length === 0) {
|
if (this._itemNodes.length === 0) {
|
||||||
void this._initLevelList();
|
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<void> {
|
private async _initLevelList(): Promise<void> {
|
||||||
this._clearList();
|
this._clearList();
|
||||||
|
|
||||||
@@ -474,11 +519,11 @@ export class PageWriteLevels extends BaseView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 CompleteButton 和 PreviewButton 的可用状态
|
// 预览与分享数量不足时也要允许点击,统一弹出提示弹窗。
|
||||||
if (this.completeBtn) {
|
if (this.completeBtn) {
|
||||||
const btn = this.completeBtn.getComponent(Button);
|
const btn = this.completeBtn.getComponent(Button);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.interactable = isFull;
|
btn.interactable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.previewBtn) {
|
if (this.previewBtn) {
|
||||||
@@ -501,18 +546,31 @@ export class PageWriteLevels extends BaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验是否已选满关卡,未满则 Toast 提示
|
* 校验是否已选满关卡,未满则弹出统一提示弹窗
|
||||||
* @returns true 表示校验通过
|
* @returns true 表示校验通过
|
||||||
*/
|
*/
|
||||||
private _validateSelection(): boolean {
|
private _validateSelection(): boolean {
|
||||||
if (this._selectedIndices.size < MAX_SELECTION) {
|
if (this._selectedIndices.size < MAX_SELECTION) {
|
||||||
const remaining = MAX_SELECTION - this._selectedIndices.size;
|
this._showSelectionRequiredModal();
|
||||||
ToastManager.instance.show(`还需选择${remaining}个关卡`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
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 {
|
private _onPreviewClick(): void {
|
||||||
AudioManager.instance.playButtonClick();
|
AudioManager.instance.playButtonClick();
|
||||||
if (!this._validateSelection()) return;
|
if (!this._validateSelection()) return;
|
||||||
|
|||||||
BIN
assets/resources/audios/good.mp3
Normal file
BIN
assets/resources/audios/good.mp3
Normal file
Binary file not shown.
14
assets/resources/audios/good.mp3.meta
Normal file
14
assets/resources/audios/good.mp3.meta
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user