import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource } from 'cc'; import { BaseView } from 'db://assets/scripts/core/BaseView'; import { ViewManager } from 'db://assets/scripts/core/ViewManager'; const { ccclass, property } = _decorator; /** * 关卡配置类 */ @ccclass('LevelConfig') export class LevelConfig { @property(SpriteFrame) mainImage: SpriteFrame | null = null; @property({ tooltip: '线索1内容(默认解锁)' }) clue1: string = ''; @property({ tooltip: '线索2内容' }) clue2: string = ''; @property({ tooltip: '线索3内容' }) clue3: string = ''; @property({ tooltip: '答案(用于确定输入框数量和验证)' }) answer: string = ''; } /** * 关卡页面组件 * 继承 BaseView,实现页面生命周期 */ @ccclass('PageLevel') export class PageLevel extends BaseView { // ========== 节点引用 ========== @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) tipsItem1: Node | null = null; @property(Node) tipsItem2: Node | null = null; @property(Node) tipsItem3: Node | null = null; @property(Node) unLockItem2: Node | null = null; @property(Node) unLockItem3: Node | null = null; @property(Label) clockLabel: Label | null = null; // ========== 配置属性 ========== @property([LevelConfig]) levelConfigs: LevelConfig[] = []; @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; // ========== 内部状态 ========== /** 当前创建的输入框节点数组 */ private _inputNodes: Node[] = []; /** 倒计时剩余秒数 */ private _countdown: number = 60; /** 倒计时是否结束 */ private _isTimeUp: boolean = false; /** * 页面首次加载时调用 */ onViewLoad(): void { console.log('[PageLevel] onViewLoad'); this.initLevel(); this.initIconSetting(); this.initUnlockButtons(); this.initSubmitButton(); this.startCountdown(); } /** * 页面每次显示时调用 */ onViewShow(): void { console.log('[PageLevel] onViewShow'); } /** * 页面隐藏时调用 */ onViewHide(): void { console.log('[PageLevel] onViewHide'); } /** * 页面销毁时调用 */ onViewDestroy(): void { console.log('[PageLevel] onViewDestroy'); this.clearInputNodes(); this.stopCountdown(); } /** * 初始化关卡 */ private initLevel(): void { const config = this.levelConfigs[this.currentLevelIndex]; if (!config) { console.warn('[PageLevel] 没有找到关卡配置'); return; } // 重置倒计时状态 this._isTimeUp = false; this._countdown = 60; // 设置主图 this.setMainImage(config.mainImage); // 设置线索1(默认解锁) this.setClue(1, config.clue1); // 隐藏线索2、3 this.hideClue(2); this.hideClue(3); // 显示解锁按钮2、3 this.showUnlockButton(2); this.showUnlockButton(3); // 根据答案长度创建输入框 const inputCount = config.answer.length; this.createInputs(inputCount); // 隐藏提交按钮 if (this.submitButton) { this.submitButton.active = false; } // 更新倒计时显示 this.updateClockLabel(); console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${inputCount}`); } /** * 动态创建输入框 */ private createInputs(count: number): void { if (!this.inputLayout || !this.inputTemplate) { console.error('[PageLevel] inputLayout 或 inputTemplate 未设置'); return; } // 清理现有输入框 this.clearInputNodes(); // 隐藏模板节点 this.inputTemplate.active = false; // 创建指定数量的输入框 for (let i = 0; i < count; i++) { const inputNode = instantiate(this.inputTemplate); inputNode.active = true; inputNode.name = `input${i}`; // 设置位置(Layout 会自动排列) inputNode.setPosition(new Vec3(0, 0, 0)); // 获取 EditBox 组件并监听事件 const editBox = inputNode.getComponent(EditBox); if (editBox) { // 清空输入内容 editBox.string = ''; // 监听文本变化事件 editBox.node.on(EditBox.EventType.TEXT_CHANGED, this.onInputTextChanged, this); // 监听编辑结束事件 editBox.node.on(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this); } this.inputLayout.addChild(inputNode); this._inputNodes.push(inputNode); } console.log(`[PageLevel] 创建了 ${count} 个输入框`); } /** * 清理所有输入框节点 */ private clearInputNodes(): void { for (const node of this._inputNodes) { if (node.isValid) { node.destroy(); } } this._inputNodes = []; } /** * 检查所有输入框是否都已填写 */ private checkAllInputsFilled(): void { let allFilled = true; for (const node of this._inputNodes) { const editBox = node.getComponent(EditBox); if (!editBox || editBox.string.trim() === '') { allFilled = false; break; } } // 根据填写状态显示/隐藏提交按钮 if (this.submitButton) { this.submitButton.active = allFilled; } console.log(`[PageLevel] 检查输入状态: ${allFilled ? '全部已填写' : '未全部填写'}`); } /** * 获取所有输入框的值 */ getInputValues(): string[] { const values: string[] = []; for (const node of this._inputNodes) { const editBox = node.getComponent(EditBox); // 只取第一个字符,去除空格 const str = (editBox?.string ?? '').trim(); values.push(str.charAt(0)); } return values; } /** * 获取拼接后的答案字符串 */ getAnswer(): string { return this.getInputValues().join(''); } // ========== EditBox 事件回调 ========== /** 是否正在处理输入(防止递归) */ private _isHandlingInput: boolean = false; /** * 输入框文本变化回调 */ private onInputTextChanged(editBox: EditBox): void { // 防止递归调用 if (this._isHandlingInput) return; // 处理多字符输入,自动分配到后续输入框 this.handleMultiCharInput(editBox); this.checkAllInputsFilled(); } /** * 处理多字符输入,自动分配到后续输入框 */ private handleMultiCharInput(editBox: EditBox): void { const text = editBox.string; if (text.length <= 1) return; // 找到当前输入框的索引 const currentIndex = this._inputNodes.findIndex(node => node.getComponent(EditBox) === editBox); if (currentIndex === -1) return; // 标记正在处理输入 this._isHandlingInput = true; // 保留第一个字符在当前输入框 const firstChar = text[0]; const remainingChars = text.slice(1); // 设置当前输入框只保留第一个字符 editBox.string = firstChar; // 将剩余字符分配到后续输入框 for (let i = 0; i < remainingChars.length; i++) { const nextIndex = currentIndex + 1 + i; if (nextIndex < this._inputNodes.length) { const nextEditBox = this._inputNodes[nextIndex].getComponent(EditBox); if (nextEditBox) { // 只在目标输入框为空时填入 if (nextEditBox.string === '') { nextEditBox.string = remainingChars[i]; } } } } // 处理完成 this._isHandlingInput = false; } /** * 输入框编辑结束回调 */ private onInputEditingEnded(_editBox: EditBox): void { this.checkAllInputsFilled(); } // ========== 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(); 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 { const unlockItem = index === 2 ? this.unLockItem2 : this.unLockItem3; if (unlockItem) { unlockItem.active = true; console.log(`[PageLevel] 显示解锁按钮${index}`); } } /** * 隐藏解锁按钮 */ private hideUnlockButton(index: number): void { const unlockItem = index === 2 ? this.unLockItem2 : this.unLockItem3; if (unlockItem) { unlockItem.active = false; console.log(`[PageLevel] 隐藏解锁按钮${index}`); } } /** * 初始化解锁按钮事件 */ private initUnlockButtons(): void { // 解锁按钮2 if (this.unLockItem2) { this.unLockItem2.on(Node.EventType.TOUCH_END, () => this.onUnlockClue(2), this); } // 解锁按钮3 if (this.unLockItem3) { this.unLockItem3.on(Node.EventType.TOUCH_END, () => this.onUnlockClue(3), 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] 提交按钮事件已绑定'); } /** * 点击解锁线索 */ private onUnlockClue(index: number): void { // 播放点击音效 this.playClickSound(); // 隐藏解锁按钮 this.hideUnlockButton(index); // 显示线索 this.showClue(index); // 设置线索内容 const config = this.levelConfigs[this.currentLevelIndex]; if (config) { const clueContent = index === 2 ? config.clue2 : config.clue3; this.setClue(index, clueContent); } console.log(`[PageLevel] 解锁线索${index}`); } // ========== 主图相关方法 ========== /** * 设置主图 */ 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] 设置主图'); } } // ========== 音效相关方法 ========== /** * 播放点击音效 */ private playClickSound(): void { if (this.clickAudio) { // 使用 audioSource 组件播放一次性音效 const audioSource = this.node.getComponent(AudioSource); if (audioSource) { audioSource.playOneShot(this.clickAudio); } } } /** * 播放成功音效 */ private playSuccessSound(): void { if (this.successAudio) { const audioSource = this.node.getComponent(AudioSource); if (audioSource) { audioSource.playOneShot(this.successAudio); } } } /** * 播放失败音效 */ private playFailSound(): void { if (this.failAudio) { const audioSource = this.node.getComponent(AudioSource); if (audioSource) { audioSource.playOneShot(this.failAudio); } } } // ========== 倒计时相关方法 ========== /** * 开始倒计时 */ private startCountdown(): void { this._countdown = 60; this._isTimeUp = false; 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(); // 可以在这里添加游戏结束逻辑 } // ========== 答案提交与关卡切换 ========== /** * 提交答案 */ onSubmitAnswer(): void { const config = this.levelConfigs[this.currentLevelIndex]; if (!config) return; const userAnswer = this.getAnswer(); console.log(`[PageLevel] 提交答案: ${userAnswer}, 正确答案: ${config.answer}`); if (userAnswer === config.answer) { // 答案正确 this.playClickSound(); this.showSuccess(); } else { // 答案错误 this.showError(); } } /** * 显示成功提示 */ private showSuccess(): void { console.log('[PageLevel] 答案正确!'); // 停止倒计时 this.stopCountdown(); // 播放成功音效 this.playSuccessSound(); // 延迟后进入下一关 this.scheduleOnce(() => { this.nextLevel(); }, 1.0); } /** * 显示错误提示 */ private showError(): void { console.log('[PageLevel] 答案错误!'); // 播放失败音效 this.playFailSound(); } /** * 进入下一关 */ private nextLevel(): void { this.currentLevelIndex++; if (this.currentLevelIndex >= this.levelConfigs.length) { // 所有关卡完成 console.log('[PageLevel] 恭喜通关!'); this.stopCountdown(); ViewManager.instance.back(); return; } // 重置并加载下一关,重新开始倒计时 this.initLevel(); this.startCountdown(); console.log(`[PageLevel] 进入关卡 ${this.currentLevelIndex + 1}`); } }