feat: 完善新版 UI
This commit is contained in:
@@ -24,6 +24,12 @@ export class PageLevel extends BaseView {
|
||||
/** 默认体力上限,服务端未返回 max 时使用 */
|
||||
private static readonly DEFAULT_STAMINA_MAX = 50;
|
||||
|
||||
/** 答案正确后展示包袱答案的停留时间 */
|
||||
private static readonly PASS_MODAL_DELAY_MS = 2000;
|
||||
|
||||
/** 答案错误后清空输入的延迟,给失败音效和错误答案留出反馈时间 */
|
||||
private static readonly CLEAR_INPUT_DELAY_MS = 500;
|
||||
|
||||
// ========== 节点引用 ==========
|
||||
@property(Node)
|
||||
inputLayout: Node | null = null;
|
||||
@@ -80,6 +86,10 @@ export class PageLevel extends BaseView {
|
||||
@property(Label)
|
||||
liveLabel: Label | null = null;
|
||||
|
||||
/** 关卡标题标签,显示为“第 N 关” */
|
||||
@property(Label)
|
||||
titleLevelLabel: Label | null = null;
|
||||
|
||||
// ========== 配置属性 ==========
|
||||
@property({
|
||||
min: 0,
|
||||
@@ -263,6 +273,9 @@ export class PageLevel extends BaseView {
|
||||
this.currentLevelIndex,
|
||||
{
|
||||
answer: enterData.answer,
|
||||
image1Description: enterData.image1Description,
|
||||
image2Description: enterData.image2Description,
|
||||
punchline: enterData.punchline,
|
||||
hint1: enterData.hint1,
|
||||
hint2: enterData.hint2,
|
||||
hint3: enterData.hint3,
|
||||
@@ -308,8 +321,11 @@ export class PageLevel extends BaseView {
|
||||
// 设置图片描述
|
||||
this.setImageDescriptions(config.image1Description, config.image2Description);
|
||||
|
||||
// 隐藏谐音梗说明(通关后才显示)
|
||||
this.setPunchline(null);
|
||||
// 设置关卡标题
|
||||
this.updateTitleLevelLabel();
|
||||
|
||||
// 隐藏包袱答案,通关后再按 punchline 展示
|
||||
this.hidePunchline();
|
||||
|
||||
// 设置线索1(默认解锁,如果有的话)
|
||||
if (config.clue1) {
|
||||
@@ -381,6 +397,7 @@ export class PageLevel extends BaseView {
|
||||
editBox.placeholder = '';
|
||||
editBox.maxLength = chars.length;
|
||||
editBox.string = '';
|
||||
editBox.node.on(EditBox.EventType.EDITING_DID_BEGAN, this.onInputEditingBegan, this);
|
||||
editBox.node.on(EditBox.EventType.TEXT_CHANGED, this.onInputTextChanged, this);
|
||||
editBox.node.on(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this);
|
||||
}
|
||||
@@ -404,6 +421,7 @@ export class PageLevel extends BaseView {
|
||||
if (node.isValid) {
|
||||
const editBox = node.getComponent(EditBox);
|
||||
if (editBox) {
|
||||
editBox.node.off(EditBox.EventType.EDITING_DID_BEGAN, this.onInputEditingBegan, this);
|
||||
editBox.node.off(EditBox.EventType.TEXT_CHANGED, this.onInputTextChanged, this);
|
||||
editBox.node.off(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this);
|
||||
editBox.string = '';
|
||||
@@ -457,42 +475,58 @@ export class PageLevel extends BaseView {
|
||||
// ========== EditBox 事件回调 ==========
|
||||
|
||||
/**
|
||||
* 输入框文本变化回调
|
||||
* 输入框开始编辑时,把当前所有格子的内容合并到当前输入框里
|
||||
*/
|
||||
private onInputTextChanged(editBox: EditBox): void {
|
||||
private onInputEditingBegan(editBox: EditBox): void {
|
||||
if (this._isSyncingInputText) return;
|
||||
|
||||
const inputIndex = this._inputNodes.findIndex(node => node === editBox.node);
|
||||
if (inputIndex < 0) return;
|
||||
|
||||
this.distributeInputText(inputIndex, editBox.string);
|
||||
this.tryAutoSubmitAnswer();
|
||||
const answer = this.getAnswer();
|
||||
this._isSyncingInputText = true;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < this._inputNodes.length; i++) {
|
||||
const itemEditBox = this._inputNodes[i].getComponent(EditBox);
|
||||
if (itemEditBox) {
|
||||
itemEditBox.string = i === inputIndex ? answer : '';
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this._isSyncingInputText = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入框文本变化回调
|
||||
*/
|
||||
private onInputTextChanged(_editBox: EditBox): void {
|
||||
this._lastAutoSubmittedAnswer = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入框编辑结束回调
|
||||
*/
|
||||
private onInputEditingEnded(_editBox: EditBox): void {
|
||||
console.log('[PageLevel] 输入编辑结束');
|
||||
private onInputEditingEnded(editBox: EditBox): void {
|
||||
if (this._isSyncingInputText) return;
|
||||
|
||||
const inputIndex = this._inputNodes.findIndex(node => node === editBox.node);
|
||||
if (inputIndex < 0) return;
|
||||
|
||||
this.distributeInputText(editBox.string);
|
||||
this.tryAutoSubmitAnswer();
|
||||
}
|
||||
|
||||
private distributeInputText(startIndex: number, text: string): void {
|
||||
private distributeInputText(text: string): void {
|
||||
const chars = Array.from(text);
|
||||
this._isSyncingInputText = true;
|
||||
|
||||
try {
|
||||
if (chars.length <= 1) {
|
||||
const editBox = this._inputNodes[startIndex]?.getComponent(EditBox);
|
||||
if (editBox) {
|
||||
editBox.string = chars[0] ?? '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = startIndex; i < this._inputNodes.length; i++) {
|
||||
for (let i = 0; i < this._inputNodes.length; i++) {
|
||||
const editBox = this._inputNodes[i].getComponent(EditBox);
|
||||
if (editBox) {
|
||||
editBox.string = chars[i - startIndex] ?? '';
|
||||
editBox.string = chars[i] ?? '';
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -500,6 +534,22 @@ export class PageLevel extends BaseView {
|
||||
}
|
||||
}
|
||||
|
||||
private clearInputText(): void {
|
||||
this._isSyncingInputText = true;
|
||||
|
||||
try {
|
||||
for (const node of this._inputNodes) {
|
||||
const editBox = node.getComponent(EditBox);
|
||||
if (editBox) {
|
||||
editBox.string = '';
|
||||
}
|
||||
}
|
||||
this._lastAutoSubmittedAnswer = '';
|
||||
} finally {
|
||||
this._isSyncingInputText = false;
|
||||
}
|
||||
}
|
||||
|
||||
private tryAutoSubmitAnswer(): void {
|
||||
if (!this._currentConfig || this._isTransitioning) return;
|
||||
|
||||
@@ -575,20 +625,20 @@ export class PageLevel extends BaseView {
|
||||
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);
|
||||
const label = this.getTipsLabel(tipsItem);
|
||||
if (label) {
|
||||
label.string = `提示 ${index}: ${content}`;
|
||||
label.string = `提示${index}:${content}`;
|
||||
console.log(`[PageLevel] 设置线索${index}: ${content}`);
|
||||
}
|
||||
}
|
||||
|
||||
private getTipsLabel(tipsItem: Node): Label | null {
|
||||
const directLabel = tipsItem.getChildByName('TipsLabel')?.getComponent(Label);
|
||||
if (directLabel) return directLabel;
|
||||
|
||||
return tipsItem.getChildByName('Content')?.getChildByName('TipsLabel')?.getComponent(Label) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示线索
|
||||
*/
|
||||
@@ -748,6 +798,12 @@ export class PageLevel extends BaseView {
|
||||
}
|
||||
}
|
||||
|
||||
private updateTitleLevelLabel(): void {
|
||||
if (!this.titleLevelLabel) return;
|
||||
|
||||
this.titleLevelLabel.string = `第 ${this.currentLevelIndex + 1} 关`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置谐音梗说明(通关后逐字展示,未通关时传 null 隐藏)
|
||||
*/
|
||||
@@ -756,8 +812,7 @@ export class PageLevel extends BaseView {
|
||||
|
||||
const chars = Array.from(punchline ?? '');
|
||||
if (chars.length === 0) {
|
||||
this.punchLayout.active = false;
|
||||
this.clearPunchBlocks();
|
||||
this.hidePunchline();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -768,6 +823,7 @@ export class PageLevel extends BaseView {
|
||||
}
|
||||
|
||||
this.clearPunchBlocks();
|
||||
this.removeUnexpectedPunchLayoutChildren(template);
|
||||
this.punchLayout.active = true;
|
||||
|
||||
for (let i = 0; i < chars.length; i++) {
|
||||
@@ -778,7 +834,12 @@ export class PageLevel extends BaseView {
|
||||
|
||||
const label = this.getPunchBlockLabel(blockNode);
|
||||
if (label) {
|
||||
label.node.active = true;
|
||||
label.enabled = true;
|
||||
label.string = chars[i];
|
||||
console.log(`[PageLevel] 设置包袱块${i + 1}: ${chars[i]}`);
|
||||
} else {
|
||||
console.warn(`[PageLevel] 包袱块${i + 1} 未找到 Label 组件`);
|
||||
}
|
||||
|
||||
if (blockNode.parent !== this.punchLayout) {
|
||||
@@ -801,6 +862,7 @@ export class PageLevel extends BaseView {
|
||||
if (node === template) {
|
||||
node.active = false;
|
||||
} else {
|
||||
node.removeFromParent();
|
||||
node.destroy();
|
||||
}
|
||||
}
|
||||
@@ -808,6 +870,42 @@ export class PageLevel extends BaseView {
|
||||
this._punchBlockNodes = [];
|
||||
}
|
||||
|
||||
private hidePunchline(): void {
|
||||
if (!this.punchLayout) return;
|
||||
|
||||
const template = this.getPunchBlockTemplateNode();
|
||||
if (!template) {
|
||||
this.punchLayout.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearPunchBlocks();
|
||||
this.removeUnexpectedPunchLayoutChildren(template);
|
||||
this.punchLayout.active = false;
|
||||
template.active = false;
|
||||
template.name = 'block';
|
||||
|
||||
const label = this.getPunchBlockLabel(template);
|
||||
if (label) {
|
||||
label.node.active = true;
|
||||
label.enabled = true;
|
||||
label.string = '';
|
||||
}
|
||||
|
||||
this._punchBlockNodes = [];
|
||||
}
|
||||
|
||||
private removeUnexpectedPunchLayoutChildren(template: Node): void {
|
||||
if (!this.punchLayout) return;
|
||||
|
||||
for (const child of [...this.punchLayout.children]) {
|
||||
if (child !== template) {
|
||||
child.removeFromParent();
|
||||
child.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getPunchBlockTemplateNode(): Node | null {
|
||||
if (this._punchBlockTemplateNode?.isValid) return this._punchBlockTemplateNode;
|
||||
|
||||
@@ -816,7 +914,19 @@ export class PageLevel extends BaseView {
|
||||
}
|
||||
|
||||
private getPunchBlockLabel(blockNode: Node): Label | null {
|
||||
return blockNode.getChildByName('Label')?.getComponent(Label) ?? blockNode.getComponent(Label);
|
||||
return this.findLabelInNode(blockNode);
|
||||
}
|
||||
|
||||
private findLabelInNode(node: Node): Label | null {
|
||||
const label = node.getComponent(Label);
|
||||
if (label) return label;
|
||||
|
||||
for (const child of node.children) {
|
||||
const childLabel = this.findLabelInNode(child);
|
||||
if (childLabel) return childLabel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 音效相关方法 ==========
|
||||
@@ -1023,31 +1133,39 @@ export class PageLevel extends BaseView {
|
||||
// 播放成功音效
|
||||
this.playSuccessSound();
|
||||
|
||||
// 通关后展示谐音梗说明
|
||||
if (this._currentConfig?.punchline) {
|
||||
this.setPunchline(this._currentConfig.punchline);
|
||||
}
|
||||
// 通关后根据 punchline 字数重建包袱答案块
|
||||
this.setPunchline(this._currentConfig?.punchline ?? null);
|
||||
|
||||
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.reportLevelCompleted(levelId, timeSpent);
|
||||
await this.delay(PageLevel.PASS_MODAL_DELAY_MS);
|
||||
|
||||
// 显示通关弹窗
|
||||
this._showPassModal();
|
||||
}
|
||||
|
||||
private reportLevelCompleted(levelId: string, timeSpent: number): void {
|
||||
if (!this._isShareMode) {
|
||||
// 标记关卡为已通关(本地缓存),通关上报并行执行,不阻塞包袱展示节奏
|
||||
LevelDataManager.instance.markLevelCompleted(this.currentLevelIndex);
|
||||
void StaminaManager.instance.completeLevel(levelId, timeSpent).then((result) => {
|
||||
if (result) {
|
||||
console.log(`[PageLevel] 通关上报成功,首次通关: ${result.firstClear}`);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// fire-and-forget: errors are logged inside reportLevelProgress
|
||||
void ShareManager.instance.reportLevelProgress(levelId, true, timeSpent);
|
||||
}
|
||||
|
||||
private delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示通关弹窗
|
||||
* 将弹窗添加到 Canvas 根节点下(而非 PageLevel 子节点)
|
||||
@@ -1125,6 +1243,13 @@ export class PageLevel extends BaseView {
|
||||
|
||||
// 显示 Toast 提示
|
||||
ToastManager.show('答案错误,再试试吧!');
|
||||
|
||||
// 输入识别失败或答案错误后延迟清空,避免错误内容瞬间消失
|
||||
void this.delay(PageLevel.CLEAR_INPUT_DELAY_MS).then(() => {
|
||||
if (!this._isTransitioning) {
|
||||
this.clearInputText();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user