feat: 对接最新的关卡工作流

This commit is contained in:
richarjiang
2026-04-26 17:04:47 +08:00
parent 5074706115
commit 1e5017e28e
16 changed files with 1808 additions and 795 deletions

View File

@@ -7956,6 +7956,14 @@
"__uuid__": "29ff0bfc-d5cf-4ad1-b8cb-61bdfd4850ef",
"__expectedType__": "cc.Prefab"
},
"wrongModalPrefab": {
"__uuid__": "455c7845-d090-4cd9-aeb4-1f5cad616bb5",
"__expectedType__": "cc.Prefab"
},
"timeoutModalPrefab": {
"__uuid__": "e41c722f-f605-47f7-9ce4-abff0ed2020f",
"__expectedType__": "cc.Prefab"
},
"_id": ""
},
{

View File

@@ -1,7 +1,6 @@
import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, 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';
import { StaminaManager } from 'db://assets/scripts/utils/StaminaManager';
import { WxSDK } from 'db://assets/scripts/utils/WxSDK';
import { LevelDataManager } from 'db://assets/scripts/utils/LevelDataManager';
@@ -10,13 +9,16 @@ import { RuntimeLevelConfig } from 'db://assets/scripts/types/LevelTypes';
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { PassModal } from 'db://assets/prefabs/PassModal';
import { StaminaInfo } from 'db://assets/scripts/types/ApiTypes';
import { WrongModal } from 'db://assets/prefabs/WrongModal';
import { TimeoutModal } from 'db://assets/prefabs/TimeoutModal';
import { StaminaInfo, NextLevelData } from 'db://assets/scripts/types/ApiTypes';
import { AchievementTitleManager } from 'db://assets/scripts/utils/AchievementTitleManager';
const { ccclass, property } = _decorator;
/**
* 关卡页面组件
* 继承 BaseView实现页面生命周期
* 关卡流程由服务端 NextLevelData 驱动,客户端不再维护关卡列表
*/
@ccclass('PageLevel')
export class PageLevel extends BaseView {
@@ -29,9 +31,6 @@ export class PageLevel extends BaseView {
/** 答案正确后展示包袱答案的停留时间 */
private static readonly PASS_MODAL_DELAY_MS = 2000;
/** 答案错误后清空输入的延迟,给失败音效和错误答案留出反馈时间 */
private static readonly CLEAR_INPUT_DELAY_MS = 500;
// ========== 节点引用 ==========
@property(Node)
inputLayout: Node | null = null;
@@ -88,17 +87,11 @@ export class PageLevel extends BaseView {
@property(Label)
liveLabel: Label | null = null;
/** 关卡标题标签,显示为第 N 关 */
/** 关卡标题标签,显示为"第 N 关" */
@property(Label)
titleLevelLabel: Label | null = null;
// ========== 配置属性 ==========
@property({
min: 0,
tooltip: '当前关卡索引'
})
currentLevelIndex: number = 0;
@property(AudioClip)
clickAudio: AudioClip | null = null;
@@ -111,6 +104,12 @@ export class PageLevel extends BaseView {
@property(Prefab)
passModalPrefab: Prefab | null = null;
@property(Prefab)
wrongModalPrefab: Prefab | null = null;
@property(Prefab)
timeoutModalPrefab: Prefab | null = null;
// ========== 内部状态 ==========
/** 当前创建的输入框节点数组 */
private _inputNodes: Node[] = [];
@@ -157,12 +156,32 @@ export class PageLevel extends BaseView {
/** 本次通关弹窗使用的已通关数量 */
private _passModalCompletedLevelCount: number | null = null;
/** 错误弹窗实例 */
private _wrongModalNode: Node | null = null;
/** 超时弹窗实例 */
private _timeoutModalNode: Node | null = null;
/** 是否处于分享挑战模式 */
private _isShareMode: boolean = false;
/** 体力恢复倒计时定时器 */
private _staminaTimerId: ReturnType<typeof setInterval> | null = null;
// ========== 关卡驱动状态NextLevel 驱动) ==========
/** 当前关卡 ID */
private _currentLevelId: string = '';
/** 当前关卡编号(仅显示用,来自 NextLevelData.level */
private _currentLevelNumber: number = 0;
/** 下一关数据(来自 complete 接口返回),点击"下一关"时使用 */
private _nextLevelData: NextLevelData | null = null;
/** 分享模式下的关卡索引(仅分享模式使用) */
private _shareLevelIndex: number = 0;
/**
* 页面首次加载时调用
*/
@@ -173,14 +192,20 @@ export class PageLevel extends BaseView {
this._isShareMode = params?.shareMode === true;
if (this._isShareMode) {
this.currentLevelIndex = 0;
this._shareLevelIndex = 0;
console.log('[PageLevel] 进入分享挑战模式');
} else {
// 根据关卡列表找到第一个未通关的关卡
this.currentLevelIndex = LevelDataManager.instance.getFirstUncompletedIndex();
StorageManager.setCurrentLevelIndex(this.currentLevelIndex);
console.log(`[PageLevel] 进入第一个未通关关卡: 第 ${this.currentLevelIndex + 1}`);
// 从 AuthManager 获取首关数据(由 PageLoading → game-data 提供)
const nextLevel = AuthManager.instance.nextLevel;
if (nextLevel) {
this._currentLevelId = nextLevel.id;
this._currentLevelNumber = nextLevel.level;
console.log(`[PageLevel] 进入关卡: 第 ${nextLevel.level} 关 (${nextLevel.id})`);
} else {
console.warn('[PageLevel] 没有可用关卡');
}
}
this.updateStaminaLabel();
this.initIconSetting();
this.initUnlockButtons();
@@ -218,6 +243,8 @@ export class PageLevel extends BaseView {
this.clearPunchBlocks();
this.stopCountdown();
this._closePassModal();
this._closeWrongModal();
this._closeTimeoutModal();
this._stopStaminaRecoverTimer();
// 清理事件监听
@@ -229,77 +256,85 @@ export class PageLevel extends BaseView {
/**
* 进入关卡并初始化
* 1. 加载关卡图片资源
* 1. 加载关卡图片资源(从缓存或 NextLevelData
* 2. 调用进入关卡接口(消耗体力,获取答案和线索)
* 3. 启动倒计时
*/
private async _enterAndInitLevel(): Promise<void> {
// 先加载关卡图片资源
let config: RuntimeLevelConfig | null = null;
if (this._isShareMode) {
config = await ShareManager.instance.ensureShareLevelReady(this.currentLevelIndex);
// 分享模式:使用 ShareManager 的关卡数据
config = await ShareManager.instance.ensureShareLevelReady(this._shareLevelIndex);
} else {
config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
// 正常模式先尝试从缓存获取PageLoading 初始化时已加载首关)
config = LevelDataManager.instance.getLevelConfig(this._currentLevelId);
if (!config) {
console.log(`[PageLevel] 关卡 ${this.currentLevelIndex + 1} 资源未缓存,开始加载...`);
config = await LevelDataManager.instance.ensureLevelReady(this.currentLevelIndex);
// 缓存未命中,从 nextLevel 数据加载complete 返回的下一关)
const nextLevelData = this._nextLevelData ?? AuthManager.instance.nextLevel;
if (nextLevelData && nextLevelData.id === this._currentLevelId) {
console.log(`[PageLevel] 关卡 ${this._currentLevelId} 资源未缓存,开始加载...`);
config = await LevelDataManager.instance.ensureLevelReady(nextLevelData);
}
}
}
if (!config) {
console.warn(`[PageLevel] 没有找到关卡配置,索引: ${this.currentLevelIndex}`);
console.warn(`[PageLevel] 没有找到关卡配置,ID: ${this._currentLevelId}`);
return;
}
// 非分享模式下,调用进入关卡接口获取答案和线索
if (!this._isShareMode) {
const levelId = LevelDataManager.instance.getLevelId(this.currentLevelIndex);
if (levelId) {
const enterData = await StaminaManager.instance.enterLevel(levelId);
if (!enterData) {
// 进入关卡失败(可能是体力不足)
const stamina = StaminaManager.instance.getStamina();
if (stamina.current <= 0) {
ToastManager.show('体力不足,请等待恢复');
this._startStaminaRecoverTimer();
} else {
ToastManager.show('进入关卡失败,请重试');
}
this.updateStaminaLabel();
return;
const enterData = await StaminaManager.instance.enterLevel(this._currentLevelId);
if (!enterData) {
// 进入关卡失败(可能是体力不足)
const stamina = StaminaManager.instance.getStamina();
if (stamina.current <= 0) {
ToastManager.show('体力不足,请等待恢复');
this._startStaminaRecoverTimer();
} else {
ToastManager.show('进入关卡失败,请重试');
}
// 提示用户消耗体力
ToastManager.show(`消耗1点体力剩余 ${enterData.stamina.current}/${this._getStaminaMax(enterData.stamina)}`);
// 用 enter 接口返回的数据更新关卡配置(填充答案和线索)
LevelDataManager.instance.updateLevelDetails(
this.currentLevelIndex,
{
answer: enterData.answer,
image1Description: enterData.image1Description,
image2Description: enterData.image2Description,
punchline: enterData.punchline,
hint1: enterData.hint1,
hint2: enterData.hint2,
hint3: enterData.hint3,
}
);
// 重新获取更新后的配置
config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
if (!config) {
console.error('[PageLevel] 更新关卡详情后获取配置失败');
return;
}
// 更新体力显示
this.updateStaminaLabel();
return;
}
// 提示用户消耗体力
ToastManager.show(`消耗1点体力剩余 ${enterData.stamina.current}/${this._getStaminaMax(enterData.stamina)}`);
// 用 enter 接口返回的数据更新关卡配置(填充答案和线索)
LevelDataManager.instance.updateLevelDetails(
this._currentLevelId,
{
answer: enterData.answer,
image1Description: enterData.image1Description,
image2Description: enterData.image2Description,
punchline: enterData.punchline,
hint1: enterData.hint1,
hint2: enterData.hint2,
hint3: enterData.hint3,
}
);
// 重新获取更新后的配置
config = LevelDataManager.instance.getLevelConfig(this._currentLevelId);
if (!config) {
console.error('[PageLevel] 更新关卡详情后获取配置失败');
return;
}
// 更新体力显示
this.updateStaminaLabel();
// 预加载下一关图片enter 返回的 preloadNextLevel
if (enterData.preloadNextLevel) {
LevelDataManager.instance.preloadLevel(enterData.preloadNextLevel);
}
}
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}: ${config.name}`);
console.log(`[PageLevel] 初始化关卡 ${this._currentLevelNumber}: ${config.name}`);
this._applyLevelConfig(config);
this.startCountdown();
}
@@ -315,7 +350,7 @@ export class PageLevel extends BaseView {
// 重置倒计时状态
this._isTimeUp = false;
this._countdown = 60;
this._countdown = config.timeLimit ?? 60;
// 设置主图图片1
this.setMainImage(config.spriteFrame1);
@@ -355,17 +390,16 @@ export class PageLevel extends BaseView {
// 更新倒计时显示
this.updateClockLabel();
// 预加载下一关图片(静默加载,不阻塞)
// 分享模式下预加载下一关
if (this._isShareMode) {
const nextIndex = this.currentLevelIndex + 1;
const nextIndex = this._shareLevelIndex + 1;
if (nextIndex < ShareManager.instance.getShareLevelCount()) {
ShareManager.instance.ensureShareLevelReady(nextIndex).catch(() => {});
}
} else {
LevelDataManager.instance.preloadNextLevel(this.currentLevelIndex);
}
// 正常模式的预加载在 enter 返回 preloadNextLevel 时已处理
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${Array.from(config.answer ?? '').length}`);
console.log(`[PageLevel] 初始化关卡 ${this._currentLevelNumber}, 答案长度: ${Array.from(config.answer ?? '').length}`);
}
/**
@@ -806,7 +840,7 @@ export class PageLevel extends BaseView {
private updateTitleLevelLabel(): void {
if (!this.titleLevelLabel) return;
this.titleLevelLabel.string = `${this.currentLevelIndex + 1}`;
this.titleLevelLabel.string = `${this._currentLevelNumber}`;
}
/**
@@ -972,12 +1006,12 @@ export class PageLevel extends BaseView {
* 开始倒计时
*/
private startCountdown(): void {
this._countdown = 60;
// _countdown 已在 _applyLevelConfig 中根据 timeLimit 设置
this._isTimeUp = false;
this._levelStartTime = Date.now();
this.updateClockLabel();
this.schedule(this.onCountdownTick, 1);
console.log('[PageLevel] 开始倒计时 60 秒');
console.log(`[PageLevel] 开始倒计时 ${this._countdown}`);
}
/**
@@ -1018,7 +1052,7 @@ export class PageLevel extends BaseView {
private onTimeUp(): void {
console.log('[PageLevel] 倒计时结束!');
this.playFailSound();
// 可以在这里添加游戏结束逻辑
this._showTimeoutModal();
}
// ========== 体力值相关方法 ==========
@@ -1086,7 +1120,7 @@ export class PageLevel extends BaseView {
this._stopStaminaRecoverTimer();
if (newCurrent < currentStamina.max) {
if (newCurrent < currentMaxStamina) {
this._startStaminaRecoverTimer();
}
}, 1000);
@@ -1151,22 +1185,26 @@ export class PageLevel extends BaseView {
this._showPassModal();
}
/**
* 上报通关并获取下一关数据
*/
private reportLevelCompleted(levelId: string, timeSpent: number): void {
if (!this._isShareMode) {
// 标记关卡为已通关(本地缓存),通关上报并行执行,不阻塞包袱展示节奏
const wasCompleted = LevelDataManager.instance.isLevelCompleted(this.currentLevelIndex);
if (!wasCompleted) {
AuthManager.instance.addCompletedLevelCount();
}
// 乐观更新通关计数(用于称号展示)
AuthManager.instance.addCompletedLevelCount();
this._passModalCompletedLevelCount = AuthManager.instance.completedLevelCount;
LevelDataManager.instance.markLevelCompleted(this.currentLevelIndex);
void StaminaManager.instance.completeLevel(levelId, timeSpent).then((result) => {
if (result) {
if (!result.firstClear && !wasCompleted) {
// 保存 complete 返回的下一关数据
this._nextLevelData = result.nextLevel;
if (!result.firstClear) {
// 非首次通关,回退乐观更新
AuthManager.instance.addCompletedLevelCount(-1);
this._passModalCompletedLevelCount = AuthManager.instance.completedLevelCount;
}
console.log(`[PageLevel] 通关上报成功,首次通关: ${result.firstClear}`);
console.log(`[PageLevel] 通关上报成功,首次通关: ${result.firstClear}, 有下一关: ${!!result.nextLevel}`);
}
});
return;
@@ -1215,13 +1253,13 @@ export class PageLevel extends BaseView {
const passModal = modalNode.getComponent(PassModal);
if (passModal) {
passModal.setParams({
levelIndex: this.currentLevelIndex + 1,
levelIndex: this._currentLevelNumber,
titleInfo: AchievementTitleManager.getTitleInfo(this._getPassModalCompletedLevelCount())
});
passModal.setCallbacks({
onNextLevel: () => {
this._closePassModal();
this.nextLevel();
this.goToNextLevel();
},
onShare: () => {
// 分享后不关闭弹窗,用户可继续点击下一关
@@ -1251,6 +1289,127 @@ export class PageLevel extends BaseView {
}
}
/**
* 显示错误弹窗
*/
private _showWrongModal(): void {
if (!this.wrongModalPrefab) {
console.warn('[PageLevel] wrongModalPrefab 未设置');
return;
}
// 如果弹窗已显示,不再重复创建
if (this._wrongModalNode && this._wrongModalNode.isValid) {
return;
}
const modalNode = instantiate(this.wrongModalPrefab);
modalNode.setPosition(PageLevel.ZERO_POS);
modalNode.setSiblingIndex(WrongModal.MODAL_Z_INDEX);
const canvasNode = this.node.parent;
if (canvasNode) {
canvasNode.addChild(modalNode);
} else {
this.node.addChild(modalNode);
}
this._wrongModalNode = modalNode;
const wrongModal = modalNode.getComponent(WrongModal);
if (wrongModal) {
wrongModal.setCallbacks({
onContinue: () => {
this._closeWrongModal();
this.clearInputText();
}
});
wrongModal.onViewLoad();
wrongModal.onViewShow();
}
console.log('[PageLevel] 显示错误弹窗');
}
/**
* 关闭错误弹窗
*/
private _closeWrongModal(): void {
if (this._wrongModalNode && this._wrongModalNode.isValid) {
this._wrongModalNode.destroy();
this._wrongModalNode = null;
console.log('[PageLevel] 关闭错误弹窗');
}
}
/**
* 显示超时弹窗
*/
private _showTimeoutModal(): void {
if (!this.timeoutModalPrefab) {
console.warn('[PageLevel] timeoutModalPrefab 未设置');
return;
}
// 如果弹窗已显示,不再重复创建
if (this._timeoutModalNode && this._timeoutModalNode.isValid) {
return;
}
const modalNode = instantiate(this.timeoutModalPrefab);
modalNode.setPosition(PageLevel.ZERO_POS);
modalNode.setSiblingIndex(TimeoutModal.MODAL_Z_INDEX);
const canvasNode = this.node.parent;
if (canvasNode) {
canvasNode.addChild(modalNode);
} else {
this.node.addChild(modalNode);
}
this._timeoutModalNode = modalNode;
const timeoutModal = modalNode.getComponent(TimeoutModal);
if (timeoutModal) {
timeoutModal.setParams({
levelIndex: this._currentLevelNumber
});
timeoutModal.setCallbacks({
onShare: () => {
console.log('[PageLevel] 超时弹窗分享完成');
},
onRestart: () => {
this._closeTimeoutModal();
this._enterAndInitLevel().catch(err => {
console.error('[PageLevel] 重新进入关卡失败:', err);
});
},
onHome: () => {
this._closeTimeoutModal();
if (this._isShareMode) {
ShareManager.instance.clearShareMode();
ViewManager.instance.replace('PageHome');
} else {
ViewManager.instance.back();
}
}
});
timeoutModal.onViewLoad();
timeoutModal.onViewShow();
}
console.log('[PageLevel] 显示超时弹窗');
}
/**
* 关闭超时弹窗
*/
private _closeTimeoutModal(): void {
if (this._timeoutModalNode && this._timeoutModalNode.isValid) {
this._timeoutModalNode.destroy();
this._timeoutModalNode = null;
console.log('[PageLevel] 关闭超时弹窗');
}
}
/**
* 显示错误提示
*/
@@ -1263,32 +1422,21 @@ export class PageLevel extends BaseView {
// 触发手机震动
WxSDK.vibrateLong();
// 显示 Toast 提示
ToastManager.show('答案错误,再试试吧!');
// 输入识别失败或答案错误后延迟清空,避免错误内容瞬间消失
void this.delay(PageLevel.CLEAR_INPUT_DELAY_MS).then(() => {
if (!this._isTransitioning) {
this.clearInputText();
}
});
// 显示错误弹窗
this._showWrongModal();
}
/**
* 进入下一关
* 正常模式:使用 complete 返回的 nextLevel 数据
* 分享模式:按索引递增
*/
private async nextLevel(): Promise<void> {
// 标记当前关卡已通关
if (!this._isShareMode) {
StorageManager.onLevelCompleted(this.currentLevelIndex);
LevelDataManager.instance.markLevelCompleted(this.currentLevelIndex);
}
// 查找下一个未通关的关卡
private async goToNextLevel(): Promise<void> {
if (this._isShareMode) {
this.currentLevelIndex++;
// 分享模式:按索引递增
this._shareLevelIndex++;
const totalLevels = ShareManager.instance.getShareLevelCount();
if (this.currentLevelIndex >= totalLevels) {
if (this._shareLevelIndex >= totalLevels) {
console.log('[PageLevel] 分享关卡全部完成');
this.stopCountdown();
ShareManager.instance.clearShareMode();
@@ -1296,20 +1444,23 @@ export class PageLevel extends BaseView {
return;
}
} else {
const nextIndex = LevelDataManager.instance.getNextUncompletedIndex(this.currentLevelIndex);
if (nextIndex < 0) {
// 所有关卡全部通关
// 正常模式:使用 complete 返回的 nextLevel
if (!this._nextLevelData) {
// 没有下一关 → 全部通关
console.log('[PageLevel] 恭喜通关!所有关卡已完成');
this.stopCountdown();
ViewManager.instance.back();
return;
}
this.currentLevelIndex = nextIndex;
StorageManager.setCurrentLevelIndex(this.currentLevelIndex);
// 切换到下一关
this._currentLevelId = this._nextLevelData.id;
this._currentLevelNumber = this._nextLevelData.level;
this._nextLevelData = null;
}
// 重置并加载下一关(包含进入关卡接口调用)
await this._enterAndInitLevel();
console.log(`[PageLevel] 进入关卡 ${this.currentLevelIndex + 1}`);
console.log(`[PageLevel] 进入关卡 ${this._currentLevelNumber}`);
}
}

View File

@@ -183,7 +183,9 @@ export class PageWriteLevels extends BaseView {
private _initLevelList(): void {
this._clearList();
this._levelCount = LevelDataManager.instance.getLevelCount();
// TODO: LevelDataManager API 已重构为 NextLevel 驱动,此页面需要重新设计数据来源
// this._levelCount = LevelDataManager.instance.getLevelCount();
this._levelCount = 0;
console.log('[PageWriteLevels] 关卡总数:', this._levelCount);
if (this._levelCount === 0) {
@@ -316,11 +318,11 @@ export class PageWriteLevels extends BaseView {
/**
* 异步加载关卡资源并刷新封面图和名称。
* LevelDataManager 采用懒加载,初始化时只加载了第一关图片,
* 其余关卡通过 ensureLevelReady 按需加载。
* TODO: LevelDataManager API 已重构为 NextLevel 驱动,此方法需要重新设计
*/
private async _loadAndRefreshCover(item: Node, index: number): Promise<void> {
const config = await LevelDataManager.instance.ensureLevelReady(index);
// const config = await LevelDataManager.instance.ensureLevelReady(index);
const config = null as any; // TODO: 需要适配新 API
if (!config || !item.isValid) return;
const levelCover = item.getChildByName('LevelCover');
@@ -548,14 +550,15 @@ export class PageWriteLevels extends BaseView {
* 将选中的关卡索引转换为关卡 ID 数组
*/
private _getSelectedLevelIds(): string[] {
// TODO: LevelDataManager API 已重构为 NextLevel 驱动,此方法需要重新设计
const ids: string[] = [];
const sortedIndices = Array.from(this._selectedIndices).sort((a, b) => a - b);
for (const index of sortedIndices) {
const config = LevelDataManager.instance.getLevelConfig(index);
if (config) {
ids.push(config.id);
}
}
// const sortedIndices = Array.from(this._selectedIndices).sort((a, b) => a - b);
// for (const index of sortedIndices) {
// const config = LevelDataManager.instance.getLevelConfig(index);
// if (config) {
// ids.push(config.id);
// }
// }
return ids;
}

View File

@@ -1157,7 +1157,7 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 320,
"width": 426.25,
"height": 100.8
},
"_anchorPoint": {
@@ -1623,7 +1623,7 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 402.03125,
"width": 442.03125,
"height": 75.6
},
"_anchorPoint": {
@@ -3141,4 +3141,4 @@
"instance": null,
"targetOverrides": null
}
]
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,162 @@
import { _decorator, Node, view, UITransform, Size } from 'cc';
import { BaseModal } from 'db://assets/scripts/core/BaseModal';
import { WxSDK } from 'db://assets/scripts/utils/WxSDK';
const { ccclass, property } = _decorator;
/**
* TimeoutModal 回调接口
*/
export interface TimeoutModalCallbacks {
/** 点击求助好友回调 */
onShare?: () => void;
/** 点击再次挑战回调 */
onRestart?: () => void;
/** 点击返回主页 / 关闭按钮回调 */
onHome?: () => void;
}
interface TimeoutModalParams {
levelIndex?: number;
}
/**
* 时间耗尽弹窗组件
* 继承 BaseModal显示倒计时结束提示提供"求助好友"、"再次挑战"和"返回主页"三个按钮
*/
@ccclass('TimeoutModal')
export class TimeoutModal extends BaseModal {
/** 静态常量:弹窗层级 */
public static readonly MODAL_Z_INDEX = 999;
/** 关闭按钮 */
@property(Node)
closeBtn: Node | null = null;
/** 求助好友按钮 */
@property(Node)
buttonShare: Node | null = null;
/** 再次挑战按钮 */
@property(Node)
buttonRestart: Node | null = null;
/** 返回主页按钮 */
@property(Node)
buttonHome: Node | null = null;
/** 回调函数 */
private _callbacks: TimeoutModalCallbacks = {};
/** 缓存的屏幕尺寸 */
private _screenSize: Size | null = null;
/**
* 设置回调函数
*/
setCallbacks(callbacks: TimeoutModalCallbacks): void {
this._callbacks = callbacks;
}
/**
* 页面首次加载时调用
*/
onViewLoad(): void {
console.log('[TimeoutModal] onViewLoad');
this._bindButtonEvents();
}
/**
* 页面每次显示时调用
*/
onViewShow(): void {
super.onViewShow();
this._updateWidget();
}
/**
* 页面销毁时调用
*/
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.closeBtn) {
this.closeBtn.on(Node.EventType.TOUCH_END, this._onHomeClick, this);
}
if (this.buttonShare) {
this.buttonShare.on(Node.EventType.TOUCH_END, this._onShareClick, this);
}
if (this.buttonRestart) {
this.buttonRestart.on(Node.EventType.TOUCH_END, this._onRestartClick, this);
}
if (this.buttonHome) {
this.buttonHome.on(Node.EventType.TOUCH_END, this._onHomeClick, this);
}
}
/**
* 解除按钮事件绑定
*/
private _unbindButtonEvents(): void {
if (this.closeBtn && this.closeBtn.isValid) {
this.closeBtn.off(Node.EventType.TOUCH_END, this._onHomeClick, this);
}
if (this.buttonShare && this.buttonShare.isValid) {
this.buttonShare.off(Node.EventType.TOUCH_END, this._onShareClick, this);
}
if (this.buttonRestart && this.buttonRestart.isValid) {
this.buttonRestart.off(Node.EventType.TOUCH_END, this._onRestartClick, this);
}
if (this.buttonHome && this.buttonHome.isValid) {
this.buttonHome.off(Node.EventType.TOUCH_END, this._onHomeClick, this);
}
}
/**
* 求助好友按钮点击
*/
private _onShareClick(): void {
console.log('[TimeoutModal] 点击求助好友');
WxSDK.shareAppMessage({
title: '这道题太难了,快来帮帮我!',
query: `level=${this._params?.levelIndex ?? 1}`
});
this._callbacks.onShare?.();
}
/**
* 再次挑战按钮点击
*/
private _onRestartClick(): void {
console.log('[TimeoutModal] 点击再次挑战');
this._callbacks.onRestart?.();
}
/**
* 返回主页 / 关闭按钮点击
*/
private _onHomeClick(): void {
console.log('[TimeoutModal] 点击返回主页');
this._callbacks.onHome?.();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "bdb18473-6efb-4592-bf67-48555845eec5",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -20,19 +20,25 @@
"_children": [
{
"__id__": 2
},
{
"__id__": 10
}
],
"_active": true,
"_components": [
{
"__id__": 56
"__id__": 68
},
{
"__id__": 58
"__id__": 70
},
{
"__id__": 72
}
],
"_prefab": {
"__id__": 60
"__id__": 74
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -63,6 +69,181 @@
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "BgMask",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
},
{
"__id__": 7
}
],
"_prefab": {
"__id__": 9
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1080,
"height": 2160
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "88epfHEmhCcLE8ktlnygG1"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 6
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 121
},
"_spriteFrame": {
"__uuid__": "7d8f9b89-4fd1-4c9f-a3ab-38ec7cded7ca@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1,
"_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": "e4C6cPdxRB66+B4hmjydyn"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 8
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 40,
"_originalHeight": 36,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "61mt404VhKUq+sKTkX8CtT"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "cby61tEk5Iza0zINKbYqqt",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "dialogPanel",
@@ -72,33 +253,33 @@
"__id__": 1
},
"_children": [
{
"__id__": 3
},
{
"__id__": 11
},
{
"__id__": 17
"__id__": 21
},
{
"__id__": 23
"__id__": 27
},
{
"__id__": 39
"__id__": 33
},
{
"__id__": 51
}
],
"_active": true,
"_components": [
{
"__id__": 51
"__id__": 63
},
{
"__id__": 53
"__id__": 65
}
],
"_prefab": {
"__id__": 55
"__id__": 67
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -135,23 +316,26 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
"__id__": 10
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 4
"__id__": 12
},
{
"__id__": 6
"__id__": 14
},
{
"__id__": 8
"__id__": 16
},
{
"__id__": 18
}
],
"_prefab": {
"__id__": 10
"__id__": 20
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -188,11 +372,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
"__id__": 11
},
"_enabled": true,
"__prefab": {
"__id__": 5
"__id__": 13
},
"_contentSize": {
"__type__": "cc.Size",
@@ -216,11 +400,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
"__id__": 11
},
"_enabled": true,
"__prefab": {
"__id__": 7
"__id__": 15
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -261,11 +445,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
"__id__": 11
},
"_enabled": true,
"__prefab": {
"__id__": 9
"__id__": 17
},
"_alignFlags": 33,
"_target": null,
@@ -291,6 +475,62 @@
"__type__": "cc.CompPrefabInfo",
"fileId": "e6kTdMyd1C3ZK6dQGDNQYb"
},
{
"__type__": "cc.Button",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 11
},
"_enabled": true,
"__prefab": {
"__id__": 19
},
"clickEvents": [],
"_interactable": true,
"_transition": 3,
"_normalColor": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_hoverColor": {
"__type__": "cc.Color",
"r": 211,
"g": 211,
"b": 211,
"a": 255
},
"_pressedColor": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_disabledColor": {
"__type__": "cc.Color",
"r": 124,
"g": 124,
"b": 124,
"a": 255
},
"_normalSprite": null,
"_hoverSprite": null,
"_pressedSprite": null,
"_disabledSprite": null,
"_duration": 0.1,
"_zoomScale": 1.2,
"_target": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "3eNsK9o9tD8Zga0+Ucj722"
},
{
"__type__": "cc.PrefabInfo",
"root": {
@@ -310,20 +550,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
"__id__": 10
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 12
"__id__": 22
},
{
"__id__": 14
"__id__": 24
}
],
"_prefab": {
"__id__": 16
"__id__": 26
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -360,11 +600,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 11
"__id__": 21
},
"_enabled": true,
"__prefab": {
"__id__": 13
"__id__": 23
},
"_contentSize": {
"__type__": "cc.Size",
@@ -388,11 +628,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 11
"__id__": 21
},
"_enabled": true,
"__prefab": {
"__id__": 15
"__id__": 25
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -472,20 +712,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
"__id__": 10
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 18
"__id__": 28
},
{
"__id__": 20
"__id__": 30
}
],
"_prefab": {
"__id__": 22
"__id__": 32
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -522,11 +762,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
"__id__": 27
},
"_enabled": true,
"__prefab": {
"__id__": 19
"__id__": 29
},
"_contentSize": {
"__type__": "cc.Size",
@@ -550,11 +790,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
"__id__": 27
},
"_enabled": true,
"__prefab": {
"__id__": 21
"__id__": 31
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -634,27 +874,30 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
"__id__": 10
},
"_children": [
{
"__id__": 24
"__id__": 34
}
],
"_active": true,
"_components": [
{
"__id__": 32
"__id__": 42
},
{
"__id__": 34
"__id__": 44
},
{
"__id__": 36
"__id__": 46
},
{
"__id__": 48
}
],
"_prefab": {
"__id__": 38
"__id__": 50
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -691,23 +934,23 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 23
"__id__": 33
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 25
"__id__": 35
},
{
"__id__": 27
"__id__": 37
},
{
"__id__": 29
"__id__": 39
}
],
"_prefab": {
"__id__": 31
"__id__": 41
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -744,11 +987,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 24
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 26
"__id__": 36
},
"_contentSize": {
"__type__": "cc.Size",
@@ -772,11 +1015,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 24
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 28
"__id__": 38
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -843,11 +1086,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 24
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 30
"__id__": 40
},
"_alignFlags": 18,
"_target": null,
@@ -892,11 +1135,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 23
"__id__": 33
},
"_enabled": true,
"__prefab": {
"__id__": 33
"__id__": 43
},
"_contentSize": {
"__type__": "cc.Size",
@@ -920,11 +1163,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 23
"__id__": 33
},
"_enabled": true,
"__prefab": {
"__id__": 35
"__id__": 45
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -965,11 +1208,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 23
"__id__": 33
},
"_enabled": true,
"__prefab": {
"__id__": 37
"__id__": 47
},
"_alignFlags": 12,
"_target": null,
@@ -995,6 +1238,62 @@
"__type__": "cc.CompPrefabInfo",
"fileId": "566NGbzlJOyLK5JELzf1Nj"
},
{
"__type__": "cc.Button",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 33
},
"_enabled": true,
"__prefab": {
"__id__": 49
},
"clickEvents": [],
"_interactable": true,
"_transition": 3,
"_normalColor": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_hoverColor": {
"__type__": "cc.Color",
"r": 211,
"g": 211,
"b": 211,
"a": 255
},
"_pressedColor": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_disabledColor": {
"__type__": "cc.Color",
"r": 124,
"g": 124,
"b": 124,
"a": 255
},
"_normalSprite": null,
"_hoverSprite": null,
"_pressedSprite": null,
"_disabledSprite": null,
"_duration": 0.1,
"_zoomScale": 1.2,
"_target": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "dcsuGJKVpOcoOngimD0/cU"
},
{
"__type__": "cc.PrefabInfo",
"root": {
@@ -1014,24 +1313,24 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
"__id__": 10
},
"_children": [
{
"__id__": 40
"__id__": 52
}
],
"_active": true,
"_components": [
{
"__id__": 46
"__id__": 58
},
{
"__id__": 48
"__id__": 60
}
],
"_prefab": {
"__id__": 50
"__id__": 62
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -1068,20 +1367,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 39
"__id__": 51
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 41
"__id__": 53
},
{
"__id__": 43
"__id__": 55
}
],
"_prefab": {
"__id__": 45
"__id__": 57
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -1118,11 +1417,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 40
"__id__": 52
},
"_enabled": true,
"__prefab": {
"__id__": 42
"__id__": 54
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1146,11 +1445,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 40
"__id__": 52
},
"_enabled": true,
"__prefab": {
"__id__": 44
"__id__": 56
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1204,11 +1503,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 39
"__id__": 51
},
"_enabled": true,
"__prefab": {
"__id__": 47
"__id__": 59
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1232,11 +1531,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 39
"__id__": 51
},
"_enabled": true,
"__prefab": {
"__id__": 49
"__id__": 61
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1290,11 +1589,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
"__id__": 10
},
"_enabled": true,
"__prefab": {
"__id__": 52
"__id__": 64
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1318,11 +1617,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
"__id__": 10
},
"_enabled": true,
"__prefab": {
"__id__": 54
"__id__": 66
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1380,7 +1679,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 57
"__id__": 69
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1408,7 +1707,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 59
"__id__": 71
},
"_alignFlags": 45,
"_target": null,
@@ -1434,6 +1733,34 @@
"__type__": "cc.CompPrefabInfo",
"fileId": "a29RxiIzdCmb3+pvtytcYa"
},
{
"__type__": "972c5f8EOdJPqHaSEnzweVV",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 73
},
"animationNodes": [],
"backdropNode": null,
"openAnimationEnabled": true,
"openAnimationDuration": 0.36,
"closeBtn": {
"__id__": 11
},
"buttonHint": {
"__id__": 33
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "c7ZV1N6ZxM8LkSfjwd1UFh"
},
{
"__type__": "cc.PrefabInfo",
"root": {

View File

@@ -0,0 +1,111 @@
import { _decorator, Node, view, UITransform, Size } from 'cc';
import { BaseModal } from 'db://assets/scripts/core/BaseModal';
const { ccclass, property } = _decorator;
/**
* WrongModal 回调接口
*/
export interface WrongModalCallbacks {
/** 点击继续挑战 / 关闭按钮回调 */
onContinue?: () => void;
}
/**
* 答案错误弹窗组件
* 继承 BaseModal显示答案错误提示提供"继续挑战"和关闭按钮
*/
@ccclass('WrongModal')
export class WrongModal extends BaseModal {
/** 静态常量:弹窗层级 */
public static readonly MODAL_Z_INDEX = 999;
/** 关闭按钮 */
@property(Node)
closeBtn: Node | null = null;
/** 继续挑战按钮 */
@property(Node)
buttonHint: Node | null = null;
/** 回调函数 */
private _callbacks: WrongModalCallbacks = {};
/** 缓存的屏幕尺寸 */
private _screenSize: Size | null = null;
/**
* 设置回调函数
*/
setCallbacks(callbacks: WrongModalCallbacks): void {
this._callbacks = callbacks;
}
/**
* 页面首次加载时调用
*/
onViewLoad(): void {
console.log('[WrongModal] onViewLoad');
this._bindButtonEvents();
}
/**
* 页面每次显示时调用
*/
onViewShow(): void {
super.onViewShow();
this._updateWidget();
}
/**
* 页面销毁时调用
*/
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.closeBtn) {
this.closeBtn.on(Node.EventType.TOUCH_END, this._onContinueClick, this);
}
if (this.buttonHint) {
this.buttonHint.on(Node.EventType.TOUCH_END, this._onContinueClick, this);
}
}
/**
* 解除按钮事件绑定
*/
private _unbindButtonEvents(): void {
if (this.closeBtn && this.closeBtn.isValid) {
this.closeBtn.off(Node.EventType.TOUCH_END, this._onContinueClick, this);
}
if (this.buttonHint && this.buttonHint.isValid) {
this.buttonHint.off(Node.EventType.TOUCH_END, this._onContinueClick, this);
}
}
/**
* 继续挑战 / 关闭按钮点击
*/
private _onContinueClick(): void {
console.log('[WrongModal] 点击继续挑战');
this._callbacks.onContinue?.();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "972c57fc-10e7-493e-a1da-4849f3c1e555",
"files": [],
"subMetas": {},
"userData": {}
}