feat: 完善新版 UI

This commit is contained in:
richarjiang
2026-04-24 21:44:22 +08:00
parent 4f93725779
commit ecc82ae9a7
9 changed files with 354 additions and 117 deletions

View File

@@ -19,13 +19,13 @@ Git 历史采用 Conventional Commits且摘要多为中文例如 `feat:
<claude-mem-context> <claude-mem-context>
# Memory Context # Memory Context
# [mp-xieyingeng] recent context, 2026-04-24 8:08pm GMT+8 # [mp-xieyingeng] recent context, 2026-04-24 9:39pm GMT+8
Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖decision 🚨security_alert 🔐security_note Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖decision 🚨security_alert 🔐security_note
Format: ID TIME TYPE TITLE Format: ID TIME TYPE TITLE
Fetch details: get_observations([IDs]) | Search: mem-search skill Fetch details: get_observations([IDs]) | Search: mem-search skill
Stats: 19 obs (4,277t read) | 178,750t work | 98% savings Stats: 45 obs (10,437t read) | 520,499t work | 98% savings
### Apr 24, 2026 ### Apr 24, 2026
101 8:46a 🟣 Live label display format updated to X/Y format 101 8:46a 🟣 Live label display format updated to X/Y format
@@ -47,6 +47,32 @@ Stats: 19 obs (4,277t read) | 178,750t work | 98% savings
138 " 🔄 PageLevel 输入方式从单框改为逐字格子 138 " 🔄 PageLevel 输入方式从单框改为逐字格子
139 " 🔄 谐音梗展示从 Label 改为动态 Block 节点 139 " 🔄 谐音梗展示从 Label 改为动态 Block 节点
140 " ✅ PageLevel.prefab 布局位置微调 140 " ✅ PageLevel.prefab 布局位置微调
165 8:08p 🟣 PageLevel input layout simplified to single-character boxes with auto-distribution
167 8:09p 🔵 PageLevel.ts input block structure and callback stubs discovered
168 " 🟣 Auto-distribute characters across input boxes and auto-submit on completion implemented
169 " 🔴 PageLevel.ts node cleanup now calls removeFromParent before destroy
170 8:10p 🔄 PageLevel.ts full diff: single EditBox replaced with per-character block system
171 " 🔴 distributeInputText() wrapped in try/finally to guarantee flag reset
179 8:23p ✅ Game011_3.ttf font relocated from resources/ to dedicated fonts/ bundle directory
180 " 🟣 PageLoading.ts now loads fonts as a dynamic bundle after level data, before UI
181 " 🔄 AssetManager import switched to type-only import in PageLoading.ts
182 8:31p 🟣 PageLevel input UX improvement: full-string editing support
183 " 🟣 PageLevel input UX: full-string editing implemented
186 8:34p 🔴 PageLevel: clear input text on wrong answer
187 8:35p 🟣 PageLevel: delay pass modal to show punchline
189 8:36p 🔄 PageLevel: level completion reporting made fire-and-forget
191 8:45p 🔴 PageLevel: punchline block cleanup improved
192 8:46p 🟣 PageLevel.ts TitleLevel Label variable reserved
193 8:47p 🔵 PageLevel.ts level number tracking mechanism discovered
195 " 🟣 PageLevel.ts TitleLevel dynamic label update implemented
196 8:53p 🔵 PageLevel punchline not updated from enterLevel API
198 " 🔴 LevelDataManager updateLevelDetails now includes punchline
199 " 🔴 PageLevel punchline flow and empty state layout fixed
200 8:54p 🔴 LevelDataManager preserves existing punchline when enter returns null
203 9:06p 🟣 PageLevel.ts 新增图片描述标签绑定字段
205 9:07p 🟣 PageLevel.ts 图片描述标签字段对接完成
206 9:09p ✅ PageLevel.ts 回退图片描述标签字段命名
208 9:11p 🟣 LevelDataManager 增加图片描述字段存储
Access 179k tokens of past work via get_observations([IDs]) or mem-search skill. Access 520k tokens of past work via get_observations([IDs]) or mem-search skill.
</claude-mem-context> </claude-mem-context>

View File

@@ -1,4 +1,5 @@
import { _decorator, Component, ProgressBar, Label } from 'cc'; import { _decorator, Component, ProgressBar, Label, assetManager } from 'cc';
import type { AssetManager } from 'cc';
import { ViewManager } from './scripts/core/ViewManager'; import { ViewManager } from './scripts/core/ViewManager';
import { LevelDataManager } from './scripts/utils/LevelDataManager'; import { LevelDataManager } from './scripts/utils/LevelDataManager';
import { AuthManager } from './scripts/utils/AuthManager'; import { AuthManager } from './scripts/utils/AuthManager';
@@ -14,6 +15,8 @@ const { ccclass, property } = _decorator;
*/ */
@ccclass('PageLoading') @ccclass('PageLoading')
export class PageLoading extends Component { export class PageLoading extends Component {
private static readonly FONT_BUNDLE_NAME = 'fonts';
@property(ProgressBar) @property(ProgressBar)
progressBar: ProgressBar | null = null; progressBar: ProgressBar | null = null;
@@ -52,6 +55,12 @@ export class PageLoading extends Component {
return; return;
} }
const fontSuccess = await this._loadFontBundle();
if (!fontSuccess) {
this._updateStatusLabel('字体资源加载失败,请重新打开游戏');
return;
}
// 登录 + 关卡数据都就绪后,用服务端进度覆盖本地进度 // 登录 + 关卡数据都就绪后,用服务端进度覆盖本地进度
if (loginSuccess) { if (loginSuccess) {
this._syncProgressFromServer(); this._syncProgressFromServer();
@@ -77,10 +86,10 @@ export class PageLoading extends Component {
console.warn('[PageLoading] 加入分享失败,进入正常模式'); console.warn('[PageLoading] 加入分享失败,进入正常模式');
} }
// 正常流程:预加载 PageHome (80-100%) // 正常流程:预加载 PageHome (82-100%)
ViewManager.instance.preload('PageHome', ViewManager.instance.preload('PageHome',
(progress) => { (progress) => {
this._updateProgress(0.8 + progress * 0.2); this._updateProgress(0.82 + progress * 0.18);
this._updateStatusLabel('正在加载界面资源...'); this._updateStatusLabel('正在加载界面资源...');
}, },
() => { () => {
@@ -112,6 +121,36 @@ export class PageLoading extends Component {
}); });
} }
/**
* 加载字体分包,避免字体资源进入小游戏主包
*/
private _loadFontBundle(): Promise<boolean> {
const bundleName = PageLoading.FONT_BUNDLE_NAME;
const cachedBundle = assetManager.getBundle(bundleName);
if (cachedBundle) {
console.log(`[PageLoading] 字体分包已加载: ${bundleName}`);
this._updateProgress(0.82);
return Promise.resolve(true);
}
this._updateStatusLabel('正在加载字体资源...');
this._updateProgress(0.8);
return new Promise((resolve) => {
assetManager.loadBundle(bundleName, (err: Error | null, bundle: AssetManager.Bundle | null) => {
if (err || !bundle) {
console.error(`[PageLoading] 字体分包加载失败: ${bundleName}`, err);
resolve(false);
return;
}
console.log(`[PageLoading] 字体分包加载完成: ${bundleName}`);
this._updateProgress(0.82);
resolve(true);
});
});
}
/** /**
* 用服务端通关进度同步本地进度 * 用服务端通关进度同步本地进度
* 1. 根据 completedLevelIds 标记已通关关卡 * 1. 根据 completedLevelIds 标记已通关关卡

View File

@@ -2,8 +2,10 @@
"ver": "1.2.0", "ver": "1.2.0",
"importer": "directory", "importer": "directory",
"imported": true, "imported": true,
"uuid": "90eed50e-b353-46da-9510-b79d6628f187", "uuid": "fc1f44af-699f-467c-ba2d-f54994551c4d",
"files": [], "files": [],
"subMetas": {}, "subMetas": {},
"userData": {} "userData": {
"isBundle": true
}
} }

View File

@@ -2578,8 +2578,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 42.255859375, "width": 100,
"height": 50.4 "height": 126
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -2609,22 +2609,25 @@
"_dstBlendFactor": 4, "_dstBlendFactor": 4,
"_color": { "_color": {
"__type__": "cc.Color", "__type__": "cc.Color",
"r": 255, "r": 75,
"g": 255, "g": 75,
"b": 255, "b": 75,
"a": 255 "a": 255
}, },
"_string": "label", "_string": "",
"_horizontalAlign": 1, "_horizontalAlign": 1,
"_verticalAlign": 1, "_verticalAlign": 1,
"_actualFontSize": 20, "_actualFontSize": 100,
"_fontSize": 20, "_fontSize": 100,
"_fontFamily": "Arial", "_fontFamily": "Arial",
"_lineHeight": 40, "_lineHeight": 100,
"_overflow": 0, "_overflow": 0,
"_enableWrapText": true, "_enableWrapText": true,
"_font": null, "_font": {
"_isSystemFontUsed": true, "__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80",
"__expectedType__": "cc.TTFFont"
},
"_isSystemFontUsed": false,
"_spacingX": 0, "_spacingX": 0,
"_isItalic": false, "_isItalic": false,
"_isBold": false, "_isBold": false,
@@ -4588,7 +4591,7 @@
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -50.312, "x": -33.411,
"y": -660.724, "y": -660.724,
"z": 0 "z": 0
}, },
@@ -4692,7 +4695,7 @@
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -316.3829999999999, "x": -340.505,
"y": 0, "y": 0,
"z": 0 "z": 0
}, },
@@ -4733,7 +4736,7 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 500, "width": 464.5749969482422,
"height": 500 "height": 500
}, },
"_anchorPoint": { "_anchorPoint": {
@@ -4745,7 +4748,7 @@
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "183qHdLEFC1ZokLeOAXS4v" "fileId": "22LYNUfANKvbDg/dHkSUwV"
}, },
{ {
"__type__": "cc.Sprite", "__type__": "cc.Sprite",
@@ -4775,7 +4778,7 @@
}, },
"_type": 0, "_type": 0,
"_fillType": 0, "_fillType": 0,
"_sizeMode": 2, "_sizeMode": 0,
"_fillCenter": { "_fillCenter": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
"x": 0, "x": 0,
@@ -4790,7 +4793,7 @@
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "91zu1RBf9Bt4h8bGbBCEf+" "fileId": "c7gLLXlKVCfYCUhQ+jYwy8"
}, },
{ {
"__type__": "cc.PrefabInfo", "__type__": "cc.PrefabInfo",
@@ -4800,7 +4803,7 @@
"asset": { "asset": {
"__id__": 0 "__id__": 0
}, },
"fileId": "4659kCML1J8pPiZ6SwLi2f", "fileId": "2b3ocDGTBC97mggTD6nDXO",
"instance": null, "instance": null,
"targetOverrides": null, "targetOverrides": null,
"nestedPrefabInstanceRoots": null "nestedPrefabInstanceRoots": null
@@ -4828,7 +4831,7 @@
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -156.798, "x": 104.254,
"y": 0, "y": 0,
"z": 0 "z": 0
}, },
@@ -4869,7 +4872,7 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 183.05624389648438, "width": 700,
"height": 63 "height": 63
}, },
"_anchorPoint": { "_anchorPoint": {
@@ -4881,7 +4884,7 @@
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "5330d+6qxIN6lu5Q4FQBz1" "fileId": "b0+JAH+KJFTaX6sbxsby5I"
}, },
{ {
"__type__": "cc.Label", "__type__": "cc.Label",
@@ -4905,14 +4908,14 @@
"b": 65, "b": 65,
"a": 255 "a": 255
}, },
"_string": "提示 1", "_string": "提示 1待解锁",
"_horizontalAlign": 0, "_horizontalAlign": 0,
"_verticalAlign": 1, "_verticalAlign": 1,
"_actualFontSize": 50, "_actualFontSize": 51,
"_fontSize": 50, "_fontSize": 50,
"_fontFamily": "Arial", "_fontFamily": "Arial",
"_lineHeight": 50, "_lineHeight": 50,
"_overflow": 0, "_overflow": 2,
"_enableWrapText": true, "_enableWrapText": true,
"_font": { "_font": {
"__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80", "__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80",
@@ -4952,7 +4955,7 @@
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "78Pcn0jMBHN424ZHHdibDV" "fileId": "c7NNDKisZMy5Fsvl8wdC2E"
}, },
{ {
"__type__": "cc.PrefabInfo", "__type__": "cc.PrefabInfo",
@@ -4962,7 +4965,7 @@
"asset": { "asset": {
"__id__": 0 "__id__": 0
}, },
"fileId": "8czInr32FC34vOGyZgOJl/", "fileId": "664aZaWkNEM5bDQ63gCwvW",
"instance": null, "instance": null,
"targetOverrides": null, "targetOverrides": null,
"nestedPrefabInstanceRoots": null "nestedPrefabInstanceRoots": null
@@ -4993,7 +4996,7 @@
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "37D6b3G0lPlIkr1GzIzW7S" "fileId": "b5JmT+IIBO2oALlaZ3XK0f"
}, },
{ {
"__type__": "cc.PrefabInfo", "__type__": "cc.PrefabInfo",
@@ -5003,7 +5006,7 @@
"asset": { "asset": {
"__id__": 0 "__id__": 0
}, },
"fileId": "c16/HqrHNEsam62hN4pFaY", "fileId": "e2UM2IG6dA9r68xaXEUXjU",
"instance": null, "instance": null,
"targetOverrides": null, "targetOverrides": null,
"nestedPrefabInstanceRoots": null "nestedPrefabInstanceRoots": null
@@ -5085,7 +5088,7 @@
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -318.81899999999996, "x": -340.505,
"y": 0, "y": 0,
"z": 0 "z": 0
}, },
@@ -5098,8 +5101,8 @@
}, },
"_lscale": { "_lscale": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0.517, "x": 0.2,
"y": 0.517, "y": 0.2,
"z": 0.517 "z": 0.517
}, },
"_mobility": 0, "_mobility": 0,
@@ -5126,8 +5129,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 153, "width": 464.5749969482422,
"height": 176 "height": 500
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -5138,7 +5141,7 @@
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "24Muu32rZCxahfvEnv6Lh7" "fileId": "a3hcMCJxVH7qdhcPKmLrrR"
}, },
{ {
"__type__": "cc.Sprite", "__type__": "cc.Sprite",
@@ -5176,14 +5179,14 @@
}, },
"_fillStart": 0, "_fillStart": 0,
"_fillRange": 0, "_fillRange": 0,
"_isTrimmedMode": true, "_isTrimmedMode": false,
"_useGrayscale": false, "_useGrayscale": false,
"_atlas": null, "_atlas": null,
"_id": "" "_id": ""
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "cdz0mOL0lDGqaZ0Fj4ZhzU" "fileId": "66jwZ+mAlCxrMD28cf4lu8"
}, },
{ {
"__type__": "cc.PrefabInfo", "__type__": "cc.PrefabInfo",
@@ -5193,7 +5196,7 @@
"asset": { "asset": {
"__id__": 0 "__id__": 0
}, },
"fileId": "ddvQFKgzlIN6PN2OGZybmB", "fileId": "a97AobrwtDRYZeDUxWwe9q",
"instance": null, "instance": null,
"targetOverrides": null, "targetOverrides": null,
"nestedPrefabInstanceRoots": null "nestedPrefabInstanceRoots": null
@@ -5221,7 +5224,7 @@
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -70.3203125, "x": 104.254,
"y": 0, "y": 0,
"z": 0 "z": 0
}, },
@@ -5262,7 +5265,7 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 342.4312438964844, "width": 700,
"height": 63 "height": 63
}, },
"_anchorPoint": { "_anchorPoint": {
@@ -5301,11 +5304,11 @@
"_string": "提示 2待解锁", "_string": "提示 2待解锁",
"_horizontalAlign": 0, "_horizontalAlign": 0,
"_verticalAlign": 1, "_verticalAlign": 1,
"_actualFontSize": 50, "_actualFontSize": 51,
"_fontSize": 50, "_fontSize": 50,
"_fontFamily": "Arial", "_fontFamily": "Arial",
"_lineHeight": 50, "_lineHeight": 50,
"_overflow": 0, "_overflow": 2,
"_enableWrapText": true, "_enableWrapText": true,
"_font": { "_font": {
"__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80", "__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80",
@@ -5478,8 +5481,8 @@
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -314.4599999999999, "x": -340.505,
"y": 1.1368683772161603e-13, "y": 0,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -5491,8 +5494,8 @@
}, },
"_lscale": { "_lscale": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0.517, "x": 0.2,
"y": 0.517, "y": 0.2,
"z": 0.517 "z": 0.517
}, },
"_mobility": 0, "_mobility": 0,
@@ -5519,8 +5522,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 153, "width": 464.5749969482422,
"height": 176 "height": 500
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -5531,7 +5534,7 @@
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "b1wA0cLKxDJ4h7bFZLjZL4" "fileId": "50azgTm7pCALtYSMaQYyyF"
}, },
{ {
"__type__": "cc.Sprite", "__type__": "cc.Sprite",
@@ -5569,14 +5572,14 @@
}, },
"_fillStart": 0, "_fillStart": 0,
"_fillRange": 0, "_fillRange": 0,
"_isTrimmedMode": true, "_isTrimmedMode": false,
"_useGrayscale": false, "_useGrayscale": false,
"_atlas": null, "_atlas": null,
"_id": "" "_id": ""
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",
"fileId": "26fAcMh7lAaZJs8caeOT/E" "fileId": "006v9WAKhG/JqM9MRgJiRS"
}, },
{ {
"__type__": "cc.PrefabInfo", "__type__": "cc.PrefabInfo",
@@ -5586,7 +5589,7 @@
"asset": { "asset": {
"__id__": 0 "__id__": 0
}, },
"fileId": "2bZ2Ap1yhIhbzJDDtNSerr", "fileId": "ad9JJ1JLNHtpnjMaprKGN7",
"instance": null, "instance": null,
"targetOverrides": null, "targetOverrides": null,
"nestedPrefabInstanceRoots": null "nestedPrefabInstanceRoots": null
@@ -5614,8 +5617,8 @@
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -70.3203125, "x": 104.254,
"y": 1.1368683772161603e-13, "y": 0,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -5655,7 +5658,7 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 343.4078063964844, "width": 700,
"height": 63 "height": 63
}, },
"_anchorPoint": { "_anchorPoint": {
@@ -5694,11 +5697,11 @@
"_string": "提示 3待解锁", "_string": "提示 3待解锁",
"_horizontalAlign": 0, "_horizontalAlign": 0,
"_verticalAlign": 1, "_verticalAlign": 1,
"_actualFontSize": 50, "_actualFontSize": 51,
"_fontSize": 50, "_fontSize": 50,
"_fontFamily": "Arial", "_fontFamily": "Arial",
"_lineHeight": 50, "_lineHeight": 50,
"_overflow": 0, "_overflow": 2,
"_enableWrapText": true, "_enableWrapText": true,
"_font": { "_font": {
"__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80", "__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80",
@@ -5872,10 +5875,10 @@
"__prefab": { "__prefab": {
"__id__": 244 "__id__": 244
}, },
"_alignFlags": 4, "_alignFlags": 44,
"_target": null, "_target": null,
"_left": 0, "_left": 106.589,
"_right": 0, "_right": 173.411,
"_top": 0, "_top": 0,
"_bottom": 249.27599999999995, "_bottom": 249.27599999999995,
"_horizontalCenter": 0, "_horizontalCenter": 0,
@@ -5886,7 +5889,7 @@
"_isAbsBottom": true, "_isAbsBottom": true,
"_isAbsHorizontalCenter": true, "_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true, "_isAbsVerticalCenter": true,
"_originalWidth": 0, "_originalWidth": 800,
"_originalHeight": 0, "_originalHeight": 0,
"_alignMode": 2, "_alignMode": 2,
"_lockFlags": 0, "_lockFlags": 0,
@@ -7933,6 +7936,9 @@
"liveLabel": { "liveLabel": {
"__id__": 158 "__id__": 158
}, },
"titleLevelLabel": {
"__id__": 14
},
"currentLevelIndex": 0, "currentLevelIndex": 0,
"clickAudio": { "clickAudio": {
"__uuid__": "a68a6314-fb7c-48a9-bd6c-0a65ef665d50", "__uuid__": "a68a6314-fb7c-48a9-bd6c-0a65ef665d50",

View File

@@ -24,6 +24,12 @@ export class PageLevel extends BaseView {
/** 默认体力上限,服务端未返回 max 时使用 */ /** 默认体力上限,服务端未返回 max 时使用 */
private static readonly DEFAULT_STAMINA_MAX = 50; 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) @property(Node)
inputLayout: Node | null = null; inputLayout: Node | null = null;
@@ -80,6 +86,10 @@ export class PageLevel extends BaseView {
@property(Label) @property(Label)
liveLabel: Label | null = null; liveLabel: Label | null = null;
/** 关卡标题标签,显示为“第 N 关” */
@property(Label)
titleLevelLabel: Label | null = null;
// ========== 配置属性 ========== // ========== 配置属性 ==========
@property({ @property({
min: 0, min: 0,
@@ -263,6 +273,9 @@ export class PageLevel extends BaseView {
this.currentLevelIndex, this.currentLevelIndex,
{ {
answer: enterData.answer, answer: enterData.answer,
image1Description: enterData.image1Description,
image2Description: enterData.image2Description,
punchline: enterData.punchline,
hint1: enterData.hint1, hint1: enterData.hint1,
hint2: enterData.hint2, hint2: enterData.hint2,
hint3: enterData.hint3, hint3: enterData.hint3,
@@ -308,8 +321,11 @@ export class PageLevel extends BaseView {
// 设置图片描述 // 设置图片描述
this.setImageDescriptions(config.image1Description, config.image2Description); this.setImageDescriptions(config.image1Description, config.image2Description);
// 隐藏谐音梗说明(通关后才显示) // 设置关卡标题
this.setPunchline(null); this.updateTitleLevelLabel();
// 隐藏包袱答案,通关后再按 punchline 展示
this.hidePunchline();
// 设置线索1默认解锁如果有的话 // 设置线索1默认解锁如果有的话
if (config.clue1) { if (config.clue1) {
@@ -381,6 +397,7 @@ export class PageLevel extends BaseView {
editBox.placeholder = ''; editBox.placeholder = '';
editBox.maxLength = chars.length; editBox.maxLength = chars.length;
editBox.string = ''; 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.TEXT_CHANGED, this.onInputTextChanged, this);
editBox.node.on(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this); editBox.node.on(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this);
} }
@@ -404,6 +421,7 @@ export class PageLevel extends BaseView {
if (node.isValid) { if (node.isValid) {
const editBox = node.getComponent(EditBox); const editBox = node.getComponent(EditBox);
if (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.TEXT_CHANGED, this.onInputTextChanged, this);
editBox.node.off(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this); editBox.node.off(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this);
editBox.string = ''; editBox.string = '';
@@ -457,42 +475,58 @@ export class PageLevel extends BaseView {
// ========== EditBox 事件回调 ========== // ========== EditBox 事件回调 ==========
/** /**
* 输入框文本变化回调 * 输入框开始编辑时,把当前所有格子的内容合并到当前输入框里
*/ */
private onInputTextChanged(editBox: EditBox): void { private onInputEditingBegan(editBox: EditBox): void {
if (this._isSyncingInputText) return; if (this._isSyncingInputText) return;
const inputIndex = this._inputNodes.findIndex(node => node === editBox.node); const inputIndex = this._inputNodes.findIndex(node => node === editBox.node);
if (inputIndex < 0) return; if (inputIndex < 0) return;
this.distributeInputText(inputIndex, editBox.string); const answer = this.getAnswer();
this.tryAutoSubmitAnswer(); 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 { private onInputEditingEnded(editBox: EditBox): void {
console.log('[PageLevel] 输入编辑结束'); 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); const chars = Array.from(text);
this._isSyncingInputText = true; this._isSyncingInputText = true;
try { try {
if (chars.length <= 1) { for (let i = 0; i < this._inputNodes.length; i++) {
const editBox = this._inputNodes[startIndex]?.getComponent(EditBox);
if (editBox) {
editBox.string = chars[0] ?? '';
}
return;
}
for (let i = startIndex; i < this._inputNodes.length; i++) {
const editBox = this._inputNodes[i].getComponent(EditBox); const editBox = this._inputNodes[i].getComponent(EditBox);
if (editBox) { if (editBox) {
editBox.string = chars[i - startIndex] ?? ''; editBox.string = chars[i] ?? '';
} }
} }
} finally { } 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 { private tryAutoSubmitAnswer(): void {
if (!this._currentConfig || this._isTransitioning) return; if (!this._currentConfig || this._isTransitioning) return;
@@ -575,20 +625,20 @@ export class PageLevel extends BaseView {
const tipsItem = this.getTipsItem(index); const tipsItem = this.getTipsItem(index);
if (!tipsItem) return; if (!tipsItem) return;
// 查找 TipsLabel 节点Content -> TipsLabel const label = this.getTipsLabel(tipsItem);
const contentNode = tipsItem.getChildByName('Content');
if (!contentNode) return;
const tipsLabelNode = contentNode.getChildByName('TipsLabel');
if (!tipsLabelNode) return;
const label = tipsLabelNode.getComponent(Label);
if (label) { if (label) {
label.string = `提示 ${index}: ${content}`; label.string = `提示${index}${content}`;
console.log(`[PageLevel] 设置线索${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 隐藏) * 设置谐音梗说明(通关后逐字展示,未通关时传 null 隐藏)
*/ */
@@ -756,8 +812,7 @@ export class PageLevel extends BaseView {
const chars = Array.from(punchline ?? ''); const chars = Array.from(punchline ?? '');
if (chars.length === 0) { if (chars.length === 0) {
this.punchLayout.active = false; this.hidePunchline();
this.clearPunchBlocks();
return; return;
} }
@@ -768,6 +823,7 @@ export class PageLevel extends BaseView {
} }
this.clearPunchBlocks(); this.clearPunchBlocks();
this.removeUnexpectedPunchLayoutChildren(template);
this.punchLayout.active = true; this.punchLayout.active = true;
for (let i = 0; i < chars.length; i++) { for (let i = 0; i < chars.length; i++) {
@@ -778,7 +834,12 @@ export class PageLevel extends BaseView {
const label = this.getPunchBlockLabel(blockNode); const label = this.getPunchBlockLabel(blockNode);
if (label) { if (label) {
label.node.active = true;
label.enabled = true;
label.string = chars[i]; label.string = chars[i];
console.log(`[PageLevel] 设置包袱块${i + 1}: ${chars[i]}`);
} else {
console.warn(`[PageLevel] 包袱块${i + 1} 未找到 Label 组件`);
} }
if (blockNode.parent !== this.punchLayout) { if (blockNode.parent !== this.punchLayout) {
@@ -801,6 +862,7 @@ export class PageLevel extends BaseView {
if (node === template) { if (node === template) {
node.active = false; node.active = false;
} else { } else {
node.removeFromParent();
node.destroy(); node.destroy();
} }
} }
@@ -808,6 +870,42 @@ export class PageLevel extends BaseView {
this._punchBlockNodes = []; 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 { private getPunchBlockTemplateNode(): Node | null {
if (this._punchBlockTemplateNode?.isValid) return this._punchBlockTemplateNode; if (this._punchBlockTemplateNode?.isValid) return this._punchBlockTemplateNode;
@@ -816,7 +914,19 @@ export class PageLevel extends BaseView {
} }
private getPunchBlockLabel(blockNode: Node): Label | null { 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(); this.playSuccessSound();
// 通关后展示谐音梗说明 // 通关后根据 punchline 字数重建包袱答案块
if (this._currentConfig?.punchline) { this.setPunchline(this._currentConfig?.punchline ?? null);
this.setPunchline(this._currentConfig.punchline);
}
const levelId = this._currentConfig?.id ?? ''; const levelId = this._currentConfig?.id ?? '';
const timeSpent = Math.max(0, Math.round((Date.now() - this._levelStartTime) / 1000)); const timeSpent = Math.max(0, Math.round((Date.now() - this._levelStartTime) / 1000));
if (!this._isShareMode) { this.reportLevelCompleted(levelId, timeSpent);
// 上报通关耗时 await this.delay(PageLevel.PASS_MODAL_DELAY_MS);
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(); 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 子节点) * 将弹窗添加到 Canvas 根节点下(而非 PageLevel 子节点)
@@ -1125,6 +1243,13 @@ export class PageLevel extends BaseView {
// 显示 Toast 提示 // 显示 Toast 提示
ToastManager.show('答案错误,再试试吧!'); ToastManager.show('答案错误,再试试吧!');
// 输入识别失败或答案错误后延迟清空,避免错误内容瞬间消失
void this.delay(PageLevel.CLEAR_INPUT_DELAY_MS).then(() => {
if (!this._isTransitioning) {
this.clearInputText();
}
});
} }
/** /**

View File

@@ -267,7 +267,7 @@ export class LevelDataManager {
/** /**
* 用 enter 接口返回的数据更新运行时关卡配置(填充答案和线索) * 用 enter 接口返回的数据更新运行时关卡配置(填充答案和线索)
*/ */
updateLevelDetails(index: number, details: { answer: string; hint1: string | null; hint2: string | null; hint3: string | null }): void { updateLevelDetails(index: number, details: { answer: string; image1Description: string | null; image2Description: string | null; punchline: string | null; hint1: string | null; hint2: string | null; hint3: string | null }): void {
const config = this._levelConfigs.get(index); const config = this._levelConfigs.get(index);
if (!config) { if (!config) {
console.warn(`[LevelDataManager] 关卡 ${index} 配置不存在,无法更新详情`); console.warn(`[LevelDataManager] 关卡 ${index} 配置不存在,无法更新详情`);
@@ -277,6 +277,9 @@ export class LevelDataManager {
this._levelConfigs.set(index, { this._levelConfigs.set(index, {
...config, ...config,
answer: details.answer, answer: details.answer,
image1Description: details.image1Description ?? config.image1Description,
image2Description: details.image2Description ?? config.image2Description,
punchline: details.punchline ?? config.punchline,
clue1: details.hint1 ?? null, clue1: details.hint1 ?? null,
clue2: details.hint2 ?? null, clue2: details.hint2 ?? null,
clue3: details.hint3 ?? null, clue3: details.hint3 ?? null,

View File

@@ -1,3 +1,39 @@
{ {
"__version__": "1.3.9" "__version__": "1.3.9",
"bundleConfig": {
"custom": {
"default": {
"displayName": "i18n:builder.asset_bundle.defaultConfig",
"configs": {
"native": {
"preferredOptions": {
"isRemote": false,
"compressionType": "merge_dep"
}
},
"web": {
"preferredOptions": {
"isRemote": false,
"compressionType": "merge_dep"
},
"fallbackOptions": {
"compressionType": "merge_dep"
}
},
"miniGame": {
"fallbackOptions": {
"isRemote": false,
"compressionType": "merge_dep"
},
"configMode": "overwrite",
"overwriteSettings": {
"wechatgame": {
"compressionType": "subpackage"
}
}
}
}
}
}
}
} }