Files
mp-xieyingeng/assets/prefabs/PageLevel.ts
2026-04-19 14:19:13 +08:00

1026 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, UITransform, Prefab } from 'cc';
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { StorageManager } from 'db://assets/scripts/utils/StorageManager';
import { StaminaManager } from 'db://assets/scripts/utils/StaminaManager';
import { WxSDK } from 'db://assets/scripts/utils/WxSDK';
import { LevelDataManager } from 'db://assets/scripts/utils/LevelDataManager';
import { RuntimeLevelConfig } from 'db://assets/scripts/types/LevelTypes';
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { PassModal } from 'db://assets/prefabs/PassModal';
import { StaminaInfo } from 'db://assets/scripts/types/ApiTypes';
const { ccclass, property } = _decorator;
/**
* 关卡页面组件
* 继承 BaseView实现页面生命周期
*/
@ccclass('PageLevel')
export class PageLevel extends BaseView {
/** 静态常量:零位置 */
private static readonly ZERO_POS = new Vec3(0, 0, 0);
// ========== 节点引用 ==========
@property(Node)
inputLayout: Node | null = null;
@property(Node)
submitButton: Node | null = null;
@property(Node)
inputTemplate: Node | null = null;
@property(Node)
actionNode: Node | null = null;
@property(Node)
iconSetting: Node | null = null;
@property(Node)
tipsLayout: Node | null = null;
@property(Node)
mainImage: Node | null = null;
@property(Node)
mainImage2: Node | null = null;
@property(Label)
image1DescLabel: Label | null = null;
@property(Label)
image2DescLabel: Label | null = null;
@property(Label)
punchlineLabel: Label | null = null;
@property(Node)
tipsItem1: Node | null = null;
@property(Node)
tipsItem2: Node | null = null;
@property(Node)
tipsItem3: Node | null = null;
@property(Node)
unLockTipsBtn: Node | null = null;
@property(Node)
addTimeBtn: Node | null = null;
@property(Label)
clockLabel: Label | null = null;
/** 体力值显示标签prefab 中序列化名为 liveLabel保持兼容 */
@property(Label)
liveLabel: Label | null = null;
// ========== 配置属性 ==========
@property({
min: 0,
tooltip: '当前关卡索引'
})
currentLevelIndex: number = 0;
@property(AudioClip)
clickAudio: AudioClip | null = null;
@property(AudioClip)
successAudio: AudioClip | null = null;
@property(AudioClip)
failAudio: AudioClip | null = null;
@property(Prefab)
passModalPrefab: Prefab | null = null;
// ========== 内部状态 ==========
/** 当前创建的输入框节点数组 */
private _inputNodes: Node[] = [];
/** 倒计时剩余秒数 */
private _countdown: number = 60;
/** 关卡开始时间戳ms用于准确计算耗时 */
private _levelStartTime: number = 0;
/** 倒计时是否结束 */
private _isTimeUp: boolean = false;
/** 当前关卡配置 */
private _currentConfig: RuntimeLevelConfig | null = null;
/** 是否正在切换关卡(防止重复提交) */
private _isTransitioning: boolean = false;
/** 是否正在解锁提示(防止双击重复触发) */
private _isUnlocking: boolean = false;
/** 下一个待解锁的线索序号2 或 3超过 3 表示全部已解锁 */
private _nextClueIndex: number = 2;
/** 通关弹窗实例 */
private _passModalNode: Node | null = null;
/** 是否处于分享挑战模式 */
private _isShareMode: boolean = false;
/** 体力恢复倒计时定时器 */
private _staminaTimerId: ReturnType<typeof setInterval> | null = null;
/**
* 页面首次加载时调用
*/
onViewLoad(): void {
console.log('[PageLevel] onViewLoad');
const params = this.getParams();
this._isShareMode = params?.shareMode === true;
if (this._isShareMode) {
this.currentLevelIndex = 0;
console.log('[PageLevel] 进入分享挑战模式');
} else {
// 根据关卡列表找到第一个未通关的关卡
this.currentLevelIndex = LevelDataManager.instance.getFirstUncompletedIndex();
StorageManager.setCurrentLevelIndex(this.currentLevelIndex);
console.log(`[PageLevel] 进入第一个未通关关卡: 第 ${this.currentLevelIndex + 1}`);
}
this.updateStaminaLabel();
this.initIconSetting();
this.initUnlockButtons();
this.initSubmitButton();
// 异步加载关卡资源并调用进入关卡接口,完成后启动倒计时
this._enterAndInitLevel().catch(err => {
console.error('[PageLevel] 进入关卡失败:', err);
});
}
/**
* 页面每次显示时调用
*/
onViewShow(): void {
console.log('[PageLevel] onViewShow');
this.updateStaminaLabel();
this._startStaminaRecoverTimer();
}
/**
* 页面隐藏时调用
*/
onViewHide(): void {
console.log('[PageLevel] onViewHide');
this._stopStaminaRecoverTimer();
}
/**
* 页面销毁时调用
*/
onViewDestroy(): void {
console.log('[PageLevel] onViewDestroy');
this.clearInputNodes();
this.stopCountdown();
this._closePassModal();
this._stopStaminaRecoverTimer();
// 清理事件监听
this.iconSetting?.off(Node.EventType.TOUCH_END, this.onIconSettingClick, this);
this.unLockTipsBtn?.off(Node.EventType.TOUCH_END);
this.addTimeBtn?.off(Node.EventType.TOUCH_END);
this.submitButton?.off(Node.EventType.TOUCH_END, this.onSubmitAnswer, this);
}
/**
* 进入关卡并初始化
* 1. 加载关卡图片资源
* 2. 调用进入关卡接口(消耗体力,获取答案和线索)
* 3. 启动倒计时
*/
private async _enterAndInitLevel(): Promise<void> {
// 先加载关卡图片资源
let config: RuntimeLevelConfig | null = null;
if (this._isShareMode) {
config = await ShareManager.instance.ensureShareLevelReady(this.currentLevelIndex);
} else {
config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
if (!config) {
console.log(`[PageLevel] 关卡 ${this.currentLevelIndex + 1} 资源未缓存,开始加载...`);
config = await LevelDataManager.instance.ensureLevelReady(this.currentLevelIndex);
}
}
if (!config) {
console.warn(`[PageLevel] 没有找到关卡配置,索引: ${this.currentLevelIndex}`);
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;
}
// 提示用户消耗体力
ToastManager.show(`消耗1点体力剩余 ${enterData.stamina.current}/${enterData.stamina.max}`);
// 用 enter 接口返回的数据更新关卡配置(填充答案和线索)
LevelDataManager.instance.updateLevelDetails(
this.currentLevelIndex,
{
answer: enterData.answer,
hint1: enterData.hint1,
hint2: enterData.hint2,
hint3: enterData.hint3,
}
);
// 重新获取更新后的配置
config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
if (!config) {
console.error('[PageLevel] 更新关卡详情后获取配置失败');
return;
}
// 更新体力显示
this.updateStaminaLabel();
}
}
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}: ${config.name}`);
this._applyLevelConfig(config);
this.startCountdown();
}
/**
* 应用关卡配置(通用初始化逻辑)
*/
private _applyLevelConfig(config: RuntimeLevelConfig): void {
this._currentConfig = config;
// 重置关卡切换状态,允许再次提交
this._isTransitioning = false;
// 重置倒计时状态
this._isTimeUp = false;
this._countdown = 60;
// 设置主图图片1
this.setMainImage(config.spriteFrame1);
// 设置图片2
this.setMainImage2(config.spriteFrame2);
// 设置图片描述
this.setImageDescriptions(config.image1Description, config.image2Description);
// 隐藏谐音梗说明(通关后才显示)
this.setPunchline(null);
// 设置线索1默认解锁如果有的话
if (config.clue1) {
this.setClue(1, config.clue1);
}
// 重置线索解锁进度
this._nextClueIndex = 2;
// 线索2、3 保持显示,写入"待解锁"占位文案
this.setClue(2, '待解锁');
this.setClue(3, '待解锁');
// 显示解锁按钮(单个统一按钮)
this.showUnlockButton();
// 根据答案长度创建单个输入框
if (config.answer) {
this.createSingleInput(config.answer.length);
}
// 更新倒计时显示
this.updateClockLabel();
// 预加载下一关图片(静默加载,不阻塞)
if (this._isShareMode) {
const nextIndex = this.currentLevelIndex + 1;
if (nextIndex < ShareManager.instance.getShareLevelCount()) {
ShareManager.instance.ensureShareLevelReady(nextIndex).catch(() => {});
}
} else {
LevelDataManager.instance.preloadNextLevel(this.currentLevelIndex);
}
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${config.answer?.length ?? 0}`);
}
/**
* 创建单个输入框
* @param answerLength 答案长度,用于设置 placeholder 和宽度
*/
private createSingleInput(answerLength: number): void {
if (!this.inputLayout || !this.inputTemplate) {
console.error('[PageLevel] inputLayout 或 inputTemplate 未设置');
return;
}
// 清理现有输入框
this.clearInputNodes();
// 隐藏模板节点
this.inputTemplate.active = false;
// 创建单个输入框
const inputNode = instantiate(this.inputTemplate);
inputNode.active = true;
inputNode.name = 'singleInput';
// 设置位置
inputNode.setPosition(PageLevel.ZERO_POS);
// 获取 EditBox 组件
const editBox = inputNode.getComponent(EditBox);
if (editBox) {
// 设置 placeholder 提示
editBox.placeholder = `(${answerLength}个字)`;
// 设置最大长度为答案长度
editBox.maxLength = answerLength;
// 清空输入内容
editBox.string = '';
// 监听事件
editBox.node.on(EditBox.EventType.TEXT_CHANGED, this.onInputTextChanged, this);
editBox.node.on(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this);
}
// 动态调整输入框宽度
const uitransform = inputNode.getComponent(UITransform);
let inputWidth = 200;
if (uitransform) {
// 每个字符约 60px加上 padding
inputWidth = Math.min(600, Math.max(200, answerLength * 60 + 40));
uitransform.setContentSize(inputWidth, 100);
}
// 调整下划线宽度与输入框一致
const underLine = inputNode.getChildByName('UnderLine');
if (underLine) {
const underLineTransform = underLine.getComponent(UITransform);
if (underLineTransform) {
underLineTransform.setContentSize(inputWidth, underLineTransform.height);
}
}
this.inputLayout.addChild(inputNode);
this._inputNodes.push(inputNode);
console.log(`[PageLevel] 创建单个输入框,答案长度: ${answerLength}`);
}
/**
* 清理所有输入框节点
*/
private clearInputNodes(): void {
for (const node of this._inputNodes) {
if (node.isValid) {
const editBox = node.getComponent(EditBox);
if (editBox) {
editBox.node.off(EditBox.EventType.TEXT_CHANGED, this.onInputTextChanged, this);
editBox.node.off(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this);
}
node.destroy();
}
}
this._inputNodes = [];
}
/**
* 获取所有输入框的值
*/
getInputValues(): string[] {
if (this._inputNodes.length === 0) return [];
const editBox = this._inputNodes[0].getComponent(EditBox);
const str = (editBox?.string ?? '').trim();
return [str];
}
/**
* 获取拼接后的答案字符串
*/
getAnswer(): string {
if (this._inputNodes.length === 0) return '';
const editBox = this._inputNodes[0].getComponent(EditBox);
return (editBox?.string ?? '').trim();
}
// ========== EditBox 事件回调 ==========
/**
* 输入框文本变化回调
*/
private onInputTextChanged(_editBox: EditBox): void {
console.log('[PageLevel] 输入内容变化');
}
/**
* 输入框编辑结束回调
*/
private onInputEditingEnded(_editBox: EditBox): void {
console.log('[PageLevel] 输入编辑结束');
}
// ========== IconSetting 按钮相关 ==========
/**
* 初始化 IconSetting 按钮事件
*/
private initIconSetting(): void {
if (!this.iconSetting) {
console.warn('[PageLevel] iconSetting 节点未设置');
return;
}
const button = this.iconSetting.getComponent(Button);
if (!button) {
console.warn('[PageLevel] iconSetting 节点缺少 Button 组件');
return;
}
this.iconSetting.on(Node.EventType.TOUCH_END, this.onIconSettingClick, this);
console.log('[PageLevel] IconSetting 按钮事件已绑定');
}
/**
* IconSetting 按钮点击回调
*/
private onIconSettingClick(): void {
console.log('[PageLevel] IconSetting 点击,返回主页');
this.playClickSound();
// 分享模式下栈中没有 PageHome需要清除分享状态并直接打开首页
if (this._isShareMode) {
ShareManager.instance.clearShareMode();
ViewManager.instance.replace('PageHome');
} else {
ViewManager.instance.back();
}
}
// ========== 线索相关方法 ==========
/**
* 获取线索节点
*/
private getTipsItem(index: number): Node | null {
switch (index) {
case 1: return this.tipsItem1;
case 2: return this.tipsItem2;
case 3: return this.tipsItem3;
default: return null;
}
}
/**
* 设置线索内容
*/
private setClue(index: number, content: string): void {
const tipsItem = this.getTipsItem(index);
if (!tipsItem) return;
// 查找 TipsLabel 节点Content -> TipsLabel
const contentNode = tipsItem.getChildByName('Content');
if (!contentNode) return;
const tipsLabelNode = contentNode.getChildByName('TipsLabel');
if (!tipsLabelNode) return;
const label = tipsLabelNode.getComponent(Label);
if (label) {
label.string = `提示 ${index}: ${content}`;
console.log(`[PageLevel] 设置线索${index}: ${content}`);
}
}
/**
* 显示线索
*/
private showClue(index: number): void {
const tipsItem = this.getTipsItem(index);
if (tipsItem) {
tipsItem.active = true;
console.log(`[PageLevel] 显示线索${index}`);
}
}
/**
* 隐藏线索
*/
private hideClue(index: number): void {
const tipsItem = this.getTipsItem(index);
if (tipsItem) {
tipsItem.active = false;
console.log(`[PageLevel] 隐藏线索${index}`);
}
}
/**
* 显示解锁按钮
*/
private showUnlockButton(_index?: number): void {
if (this.unLockTipsBtn) {
this.unLockTipsBtn.active = true;
console.log('[PageLevel] 显示解锁按钮');
}
}
/**
* 隐藏解锁按钮
*/
private hideUnlockButton(_index?: number): void {
if (this.unLockTipsBtn) {
this.unLockTipsBtn.active = false;
console.log('[PageLevel] 隐藏解锁按钮');
}
}
/**
* 初始化解锁按钮事件
*/
private initUnlockButtons(): void {
if (this.unLockTipsBtn) {
this.unLockTipsBtn.on(Node.EventType.TOUCH_END, this.onUnlockClue, this);
}
if (this.addTimeBtn) {
this.addTimeBtn.on(Node.EventType.TOUCH_END, this.onAddTime, this);
}
console.log('[PageLevel] 解锁按钮事件已绑定');
}
/**
* 初始化提交按钮事件
*/
private initSubmitButton(): void {
if (!this.submitButton) {
console.warn('[PageLevel] submitButton 节点未设置');
return;
}
this.submitButton.on(Node.EventType.TOUCH_END, this.onSubmitAnswer, this);
console.log('[PageLevel] 提交按钮事件已绑定');
}
/**
* 点击解锁线索顺序解锁先线索2再线索3全部解锁后提示已解锁完毕
*/
private onUnlockClue(): void {
// 全部已解锁,提示用户
if (this._nextClueIndex > 3) {
ToastManager.show('已解锁完毕');
return;
}
if (!this._currentConfig) return;
const index = this._nextClueIndex;
const clueContent = index === 2 ? this._currentConfig.clue2 : this._currentConfig.clue3;
if (!clueContent) {
ToastManager.show('该提示暂未配置');
return;
}
this.playClickSound();
this.setClue(index, clueContent);
// 推进到下一条待解锁线索
this._nextClueIndex++;
// 全部解锁完毕则隐藏按钮
if (this._nextClueIndex > 3) {
this.hideUnlockButton();
}
console.log(`[PageLevel] 解锁线索${index}`);
}
/**
* 点击增加时间按钮(倒计时增加 60 秒)
*/
private onAddTime(): void {
if (this._isTimeUp) {
ToastManager.show('时间已结束,无法增加');
return;
}
this._countdown += 60;
this.updateClockLabel();
this.playClickSound();
ToastManager.show('已成功增加60秒');
console.log(`[PageLevel] 增加60秒倒计时当前剩余: ${this._countdown}s`);
}
// ========== 主图相关方法 ==========
/**
* 设置主图图片1
*/
private setMainImage(spriteFrame: SpriteFrame | null): void {
if (!this.mainImage) return;
const sprite = this.mainImage.getComponent(Sprite);
if (sprite && spriteFrame) {
sprite.spriteFrame = spriteFrame;
console.log('[PageLevel] 设置主图1');
}
}
/**
* 设置图片2
*/
private setMainImage2(spriteFrame: SpriteFrame | null): void {
if (!this.mainImage2) return;
const sprite = this.mainImage2.getComponent(Sprite);
if (sprite && spriteFrame) {
sprite.spriteFrame = spriteFrame;
console.log('[PageLevel] 设置主图2');
}
}
/**
* 设置图片描述文本
*/
private setImageDescriptions(desc1: string | null, desc2: string | null): void {
if (this.image1DescLabel) {
this.image1DescLabel.string = desc1 ?? '';
}
if (this.image2DescLabel) {
this.image2DescLabel.string = desc2 ?? '';
}
}
/**
* 设置谐音梗说明(通关后展示,未通关时传 null 隐藏)
*/
private setPunchline(punchline: string | null): void {
if (!this.punchlineLabel) return;
if (punchline) {
this.punchlineLabel.node.active = true;
this.punchlineLabel.string = punchline;
} else {
this.punchlineLabel.node.active = false;
this.punchlineLabel.string = '';
}
}
// ========== 音效相关方法 ==========
/**
* 播放音效(通用方法)
*/
private playSound(clip: AudioClip | null): void {
if (!clip) return;
const audioSource = this.node.getComponent(AudioSource);
audioSource?.playOneShot(clip);
}
/**
* 播放点击音效
*/
private playClickSound(): void {
this.playSound(this.clickAudio);
}
/**
* 播放成功音效
*/
private playSuccessSound(): void {
this.playSound(this.successAudio);
}
/**
* 播放失败音效
*/
private playFailSound(): void {
this.playSound(this.failAudio);
}
// ========== 倒计时相关方法 ==========
/**
* 开始倒计时
*/
private startCountdown(): void {
this._countdown = 60;
this._isTimeUp = false;
this._levelStartTime = Date.now();
this.updateClockLabel();
this.schedule(this.onCountdownTick, 1);
console.log('[PageLevel] 开始倒计时 60 秒');
}
/**
* 停止倒计时
*/
private stopCountdown(): void {
this.unschedule(this.onCountdownTick);
}
/**
* 倒计时每秒回调
*/
private onCountdownTick(): void {
if (this._isTimeUp) return;
this._countdown--;
this.updateClockLabel();
if (this._countdown <= 0) {
this._isTimeUp = true;
this.stopCountdown();
this.onTimeUp();
}
}
/**
* 更新倒计时显示
*/
private updateClockLabel(): void {
if (this.clockLabel) {
this.clockLabel.string = `${this._countdown}s`;
}
}
/**
* 倒计时结束
*/
private onTimeUp(): void {
console.log('[PageLevel] 倒计时结束!');
this.playFailSound();
// 可以在这里添加游戏结束逻辑
}
// ========== 体力值相关方法 ==========
/** 上次显示的体力值,用于变更检测 */
private _lastDisplayedStamina: number = -1;
/**
* 更新体力值显示(仅值变化时更新 UI
*/
private updateStaminaLabel(): void {
if (this.liveLabel) {
const stamina = StaminaManager.instance.getStamina();
if (stamina.current !== this._lastDisplayedStamina) {
this.liveLabel.string = `x ${stamina.current}`;
this._lastDisplayedStamina = stamina.current;
}
}
}
/**
* 启动体力恢复倒计时 UI
*/
private _startStaminaRecoverTimer(): void {
this._stopStaminaRecoverTimer();
const stamina = StaminaManager.instance.getStamina();
if (!stamina.nextRecoverAt || stamina.current >= stamina.max) {
return;
}
const targetTime = new Date(stamina.nextRecoverAt).getTime();
if (isNaN(targetTime)) return;
this._staminaTimerId = setInterval(() => {
if (targetTime - Date.now() > 0) return;
// 恢复一点体力
const currentStamina = StaminaManager.instance.getStamina();
const newCurrent = Math.min(currentStamina.current + 1, currentStamina.max);
const newStamina: StaminaInfo = {
...currentStamina,
current: newCurrent,
nextRecoverAt: newCurrent < currentStamina.max
? new Date(Date.now() + 10 * 60 * 1000).toISOString()
: null,
};
StaminaManager.instance.updateStamina(newStamina);
this.updateStaminaLabel();
this._stopStaminaRecoverTimer();
if (newCurrent < currentStamina.max) {
this._startStaminaRecoverTimer();
}
}, 1000);
}
/**
* 停止体力恢复倒计时
*/
private _stopStaminaRecoverTimer(): void {
if (this._staminaTimerId !== null) {
clearInterval(this._staminaTimerId);
this._staminaTimerId = null;
}
}
// ========== 答案提交与关卡切换 ==========
/**
* 提交答案
*/
onSubmitAnswer(): void {
if (!this._currentConfig) return;
if (this._isTransitioning) return;
const userAnswer = this.getAnswer();
console.log(`[PageLevel] 提交答案: ${userAnswer}, 正确答案: ${this._currentConfig.answer}`);
if (userAnswer === this._currentConfig.answer) {
// 答案正确,只播放成功音效(不播放点击音效,避免重合)
this.showSuccess();
} else {
// 答案错误
this.showError();
}
}
/**
* 显示成功提示并上报通关
*/
private async showSuccess(): Promise<void> {
console.log('[PageLevel] 答案正确!');
// 标记正在切换关卡,防止重复提交
this._isTransitioning = true;
// 停止倒计时
this.stopCountdown();
// 播放成功音效
this.playSuccessSound();
// 通关后展示谐音梗说明
if (this._currentConfig?.punchline) {
this.setPunchline(this._currentConfig.punchline);
}
const levelId = this._currentConfig?.id ?? '';
const timeSpent = Math.max(0, Math.round((Date.now() - this._levelStartTime) / 1000));
if (!this._isShareMode) {
// 上报通关耗时
const result = await StaminaManager.instance.completeLevel(levelId, timeSpent);
if (result) {
console.log(`[PageLevel] 通关上报成功,首次通关: ${result.firstClear}`);
}
// 标记关卡为已通关(本地缓存)
LevelDataManager.instance.markLevelCompleted(this.currentLevelIndex);
} else {
// fire-and-forget: errors are logged inside reportLevelProgress
void ShareManager.instance.reportLevelProgress(levelId, true, timeSpent);
}
// 显示通关弹窗
this._showPassModal();
}
/**
* 显示通关弹窗
* 将弹窗添加到 Canvas 根节点下(而非 PageLevel 子节点)
* 这样 Widget 可以正确对齐到全屏
*/
private _showPassModal(): void {
if (!this.passModalPrefab) {
console.warn('[PageLevel] passModalPrefab 未设置');
return;
}
// 如果弹窗已显示,不再重复创建
if (this._passModalNode && this._passModalNode.isValid) {
return;
}
// 实例化弹窗
const modalNode = instantiate(this.passModalPrefab);
modalNode.setPosition(PageLevel.ZERO_POS);
modalNode.setSiblingIndex(PassModal.MODAL_Z_INDEX);
// 找到 Canvas 根节点并添加弹窗
const canvasNode = this.node.parent;
if (canvasNode) {
canvasNode.addChild(modalNode);
} else {
this.node.addChild(modalNode);
}
this._passModalNode = modalNode;
// 获取 PassModal 组件并设置回调
const passModal = modalNode.getComponent(PassModal);
if (passModal) {
passModal.setParams({ levelIndex: this.currentLevelIndex + 1 });
passModal.setCallbacks({
onNextLevel: () => {
this._closePassModal();
this.nextLevel();
},
onShare: () => {
// 分享后不关闭弹窗,用户可继续点击下一关
console.log('[PageLevel] 分享完成');
}
});
// 手动调用 onViewLoad 和 onViewShow
passModal.onViewLoad();
passModal.onViewShow();
}
console.log('[PageLevel] 显示通关弹窗');
}
/**
* 关闭通关弹窗
*/
private _closePassModal(): void {
if (this._passModalNode && this._passModalNode.isValid) {
this._passModalNode.destroy();
this._passModalNode = null;
console.log('[PageLevel] 关闭通关弹窗');
}
}
/**
* 显示错误提示
*/
private showError(): void {
console.log('[PageLevel] 答案错误!');
// 播放失败音效
this.playFailSound();
// 触发手机震动
WxSDK.vibrateLong();
// 显示 Toast 提示
ToastManager.show('答案错误,再试试吧!');
}
/**
* 进入下一关
*/
private async nextLevel(): Promise<void> {
// 标记当前关卡已通关
if (!this._isShareMode) {
StorageManager.onLevelCompleted(this.currentLevelIndex);
LevelDataManager.instance.markLevelCompleted(this.currentLevelIndex);
}
// 查找下一个未通关的关卡
if (this._isShareMode) {
this.currentLevelIndex++;
const totalLevels = ShareManager.instance.getShareLevelCount();
if (this.currentLevelIndex >= totalLevels) {
console.log('[PageLevel] 分享关卡全部完成');
this.stopCountdown();
ShareManager.instance.clearShareMode();
ViewManager.instance.replace('PageHome');
return;
}
} else {
const nextIndex = LevelDataManager.instance.getNextUncompletedIndex(this.currentLevelIndex);
if (nextIndex < 0) {
// 所有关卡全部通关
console.log('[PageLevel] 恭喜通关!所有关卡已完成');
this.stopCountdown();
ViewManager.instance.back();
return;
}
this.currentLevelIndex = nextIndex;
StorageManager.setCurrentLevelIndex(this.currentLevelIndex);
}
// 重置并加载下一关(包含进入关卡接口调用)
await this._enterAndInitLevel();
console.log(`[PageLevel] 进入关卡 ${this.currentLevelIndex + 1}`);
}
}