feat: 重构输入框为单输入框模式并添加震动反馈
- 将多输入框改为单个输入框,根据答案长度动态调整宽度 - 输入框 placeholder 显示答案字数提示 - 答案错误时触发微信小游戏震动反馈 - WxSDK 新增 vibrateShort/vibrateLong 方法 - 重构音效播放方法,提取公共 playSound 方法
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource } from 'cc';
|
||||
import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, UITransform } 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 { WxSDK } from 'db://assets/scripts/utils/WxSDK';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
@@ -168,25 +169,21 @@ export class PageLevel extends BaseView {
|
||||
this.showUnlockButton(2);
|
||||
this.showUnlockButton(3);
|
||||
|
||||
// 根据答案长度创建输入框
|
||||
const inputCount = config.answer.length;
|
||||
this.createInputs(inputCount);
|
||||
|
||||
// 隐藏提交按钮
|
||||
if (this.submitButton) {
|
||||
this.submitButton.active = false;
|
||||
}
|
||||
// 根据答案长度创建单个输入框
|
||||
const answerLength = config.answer.length;
|
||||
this.createSingleInput(answerLength);
|
||||
|
||||
// 更新倒计时显示
|
||||
this.updateClockLabel();
|
||||
|
||||
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${inputCount}`);
|
||||
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${answerLength}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态创建输入框
|
||||
* 创建单个输入框
|
||||
* @param answerLength 答案长度,用于设置 placeholder 和宽度
|
||||
*/
|
||||
private createInputs(count: number): void {
|
||||
private createSingleInput(answerLength: number): void {
|
||||
if (!this.inputLayout || !this.inputTemplate) {
|
||||
console.error('[PageLevel] inputLayout 或 inputTemplate 未设置');
|
||||
return;
|
||||
@@ -198,31 +195,53 @@ export class PageLevel extends BaseView {
|
||||
// 隐藏模板节点
|
||||
this.inputTemplate.active = false;
|
||||
|
||||
// 创建指定数量的输入框
|
||||
for (let i = 0; i < count; i++) {
|
||||
const inputNode = instantiate(this.inputTemplate);
|
||||
inputNode.active = true;
|
||||
inputNode.name = `input${i}`;
|
||||
// 创建单个输入框
|
||||
const inputNode = instantiate(this.inputTemplate);
|
||||
inputNode.active = true;
|
||||
inputNode.name = 'singleInput';
|
||||
|
||||
// 设置位置(Layout 会自动排列)
|
||||
inputNode.setPosition(new Vec3(0, 0, 0));
|
||||
// 设置位置
|
||||
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);
|
||||
}
|
||||
// 获取 EditBox 组件
|
||||
const editBox = inputNode.getComponent(EditBox);
|
||||
if (editBox) {
|
||||
// 设置 placeholder 提示
|
||||
editBox.placeholder = `(${answerLength}个字)`;
|
||||
|
||||
this.inputLayout.addChild(inputNode);
|
||||
this._inputNodes.push(inputNode);
|
||||
// 设置最大长度为答案长度
|
||||
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);
|
||||
}
|
||||
|
||||
console.log(`[PageLevel] 创建了 ${count} 个输入框`);
|
||||
// 动态调整输入框宽度
|
||||
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}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,110 +256,39 @@ export class PageLevel extends BaseView {
|
||||
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;
|
||||
if (this._inputNodes.length === 0) return [];
|
||||
const editBox = this._inputNodes[0].getComponent(EditBox);
|
||||
const str = (editBox?.string ?? '').trim();
|
||||
return [str];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取拼接后的答案字符串
|
||||
*/
|
||||
getAnswer(): string {
|
||||
return this.getInputValues().join('');
|
||||
if (this._inputNodes.length === 0) return '';
|
||||
const editBox = this._inputNodes[0].getComponent(EditBox);
|
||||
return (editBox?.string ?? '').trim();
|
||||
}
|
||||
|
||||
// ========== 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 onInputTextChanged(_editBox: EditBox): void {
|
||||
console.log('[PageLevel] 输入内容变化');
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入框编辑结束回调
|
||||
*/
|
||||
private onInputEditingEnded(_editBox: EditBox): void {
|
||||
this.checkAllInputsFilled();
|
||||
console.log('[PageLevel] 输入编辑结束');
|
||||
}
|
||||
|
||||
// ========== IconSetting 按钮相关 ==========
|
||||
@@ -533,41 +481,34 @@ export class PageLevel extends BaseView {
|
||||
|
||||
// ========== 音效相关方法 ==========
|
||||
|
||||
/**
|
||||
* 播放音效(通用方法)
|
||||
*/
|
||||
private playSound(clip: AudioClip | null): void {
|
||||
if (!clip) return;
|
||||
const audioSource = this.node.getComponent(AudioSource);
|
||||
audioSource?.playOneShot(clip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放点击音效
|
||||
*/
|
||||
private playClickSound(): void {
|
||||
if (this.clickAudio) {
|
||||
// 使用 audioSource 组件播放一次性音效
|
||||
const audioSource = this.node.getComponent(AudioSource);
|
||||
if (audioSource) {
|
||||
audioSource.playOneShot(this.clickAudio);
|
||||
}
|
||||
}
|
||||
this.playSound(this.clickAudio);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放成功音效
|
||||
*/
|
||||
private playSuccessSound(): void {
|
||||
if (this.successAudio) {
|
||||
const audioSource = this.node.getComponent(AudioSource);
|
||||
if (audioSource) {
|
||||
audioSource.playOneShot(this.successAudio);
|
||||
}
|
||||
}
|
||||
this.playSound(this.successAudio);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放失败音效
|
||||
*/
|
||||
private playFailSound(): void {
|
||||
if (this.failAudio) {
|
||||
const audioSource = this.node.getComponent(AudioSource);
|
||||
if (audioSource) {
|
||||
audioSource.playOneShot(this.failAudio);
|
||||
}
|
||||
}
|
||||
this.playSound(this.failAudio);
|
||||
}
|
||||
|
||||
// ========== 倒计时相关方法 ==========
|
||||
@@ -719,6 +660,9 @@ export class PageLevel extends BaseView {
|
||||
|
||||
// 播放失败音效
|
||||
this.playFailSound();
|
||||
|
||||
// 触发手机震动
|
||||
WxSDK.vibrateLong();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user