feat: 完善关卡

This commit is contained in:
richarjiang
2026-03-14 11:02:49 +08:00
parent f6feb14ff3
commit 774486ba21
26 changed files with 5117 additions and 267 deletions

View File

@@ -1,16 +1,27 @@
import { _decorator, Node, EditBox, instantiate, Vec3, Button } from 'cc';
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;
/**
* 关卡配置接口
* 关卡配置
*/
export interface LevelConfig {
/** 需要的输入框数量 */
inputCount: number;
/** 题目文本 */
questionText?: string;
@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 = '';
}
/**
@@ -35,23 +46,58 @@ export class PageLevel extends BaseView {
@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({
tooltip: '默认输入框数量',
min: 1,
max: 10
min: 0,
tooltip: '当前关卡索引'
})
defaultInputCount: number = 2;
currentLevelIndex: number = 0;
@property(AudioClip)
clickAudio: AudioClip | null = null;
@property(AudioClip)
successAudio: AudioClip | null = null;
@property(AudioClip)
failAudio: AudioClip | null = null;
// ========== 内部状态 ==========
/** 当前输入框数量 */
private _inputCount: number = 0;
/** 当前创建的输入框节点数组 */
private _inputNodes: Node[] = [];
/** 当前关卡配置 */
private _levelConfig: LevelConfig | null = null;
/** 倒计时剩余秒数 */
private _countdown: number = 60;
/** 倒计时是否结束 */
private _isTimeUp: boolean = false;
/**
* 页面首次加载时调用
@@ -60,6 +106,9 @@ export class PageLevel extends BaseView {
console.log('[PageLevel] onViewLoad');
this.initLevel();
this.initIconSetting();
this.initUnlockButtons();
this.initSubmitButton();
this.startCountdown();
}
/**
@@ -82,30 +131,50 @@ export class PageLevel extends BaseView {
onViewDestroy(): void {
console.log('[PageLevel] onViewDestroy');
this.clearInputNodes();
}
/**
* 设置关卡配置
*/
setLevelConfig(config: LevelConfig): void {
this._levelConfig = config;
this.initLevel();
this.stopCountdown();
}
/**
* 初始化关卡
*/
private initLevel(): void {
// 使用配置或默认值
const inputCount = this._levelConfig?.inputCount ?? this.defaultInputCount;
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.createInputs(inputCount);
// 更新倒计时显示
this.updateClockLabel();
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${inputCount}`);
}
/**
@@ -120,8 +189,6 @@ export class PageLevel extends BaseView {
// 清理现有输入框
this.clearInputNodes();
this._inputCount = count;
// 隐藏模板节点
this.inputTemplate.active = false;
@@ -162,7 +229,6 @@ export class PageLevel extends BaseView {
}
}
this._inputNodes = [];
this._inputCount = 0;
}
/**
@@ -194,7 +260,9 @@ export class PageLevel extends BaseView {
const values: string[] = [];
for (const node of this._inputNodes) {
const editBox = node.getComponent(EditBox);
values.push(editBox?.string ?? '');
// 只取第一个字符,去除空格
const str = (editBox?.string ?? '').trim();
values.push(str.charAt(0));
}
return values;
}
@@ -208,17 +276,64 @@ export class PageLevel extends BaseView {
// ========== 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 {
private onInputEditingEnded(_editBox: EditBox): void {
this.checkAllInputsFilled();
}
@@ -248,8 +363,319 @@ export class PageLevel extends BaseView {
*/
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}`);
}
}