feat: 支持动态创建输入框
This commit is contained in:
31
AGENTS.md
31
AGENTS.md
@@ -19,7 +19,34 @@ Git 历史采用 Conventional Commits,且摘要多为中文,例如 `feat:
|
|||||||
<claude-mem-context>
|
<claude-mem-context>
|
||||||
# Memory Context
|
# Memory Context
|
||||||
|
|
||||||
# [mp-xieyingeng] recent context, 2026-04-24 8:45am GMT+8
|
# [mp-xieyingeng] recent context, 2026-04-24 8:08pm GMT+8
|
||||||
|
|
||||||
No previous sessions found.
|
Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖️decision 🚨security_alert 🔐security_note
|
||||||
|
Format: ID TIME TYPE TITLE
|
||||||
|
Fetch details: get_observations([IDs]) | Search: mem-search skill
|
||||||
|
|
||||||
|
Stats: 19 obs (4,277t read) | 178,750t work | 98% savings
|
||||||
|
|
||||||
|
### Apr 24, 2026
|
||||||
|
101 8:46a 🟣 Live label display format updated to X/Y format
|
||||||
|
114 6:40p 🟣 Dynamic Input Layout Initialization in PageLevel Prefab
|
||||||
|
115 6:41p 🟣 Dynamic Punch Block Layout for PageLevel.prefab
|
||||||
|
116 " 🔵 Layout Component Configuration for Input and Punch Blocks
|
||||||
|
119 6:42p 🟣 Dynamic Input Blocks and Punch Layout System Implemented
|
||||||
|
121 " 🟣 PageLevel Prefab Updated with punchLayout Property
|
||||||
|
122 " 🔄 Cleanup After Dynamic Block Refactoring
|
||||||
|
124 " ✅ TypeScript Compilation Check in Progress
|
||||||
|
126 6:43p ✅ TypeScript Compilation Check Extended
|
||||||
|
127 " 🔄 Complete Diff of PageLevel.ts Dynamic Block System
|
||||||
|
128 " 🔵 PageLevel.prefab Changes Not Persisted
|
||||||
|
129 6:44p 🟣 PageLevel Prefab Correctly Updated with punchLayout
|
||||||
|
130 " ✅ TypeScript Compilation Blocked - Permission Required
|
||||||
|
133 6:45p 🔄 Extracted getPunchBlockLabel Helper Method
|
||||||
|
134 " 🔄 Template Node Hiding Logic Improved
|
||||||
|
136 6:48p ⚖️ TypeScript diagnostics disabled, using IDE/linter instead
|
||||||
|
138 " 🔄 PageLevel 输入方式从单框改为逐字格子
|
||||||
|
139 " 🔄 谐音梗展示从 Label 改为动态 Block 节点
|
||||||
|
140 " ✅ PageLevel.prefab 布局位置微调
|
||||||
|
|
||||||
|
Access 179k tokens of past work via get_observations([IDs]) or mem-search skill.
|
||||||
</claude-mem-context>
|
</claude-mem-context>
|
||||||
9
assets/levels.meta
Normal file
9
assets/levels.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.2.0",
|
||||||
|
"importer": "directory",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "0b928321-a809-4339-8af8-5a053aeda2d5",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, UITransform, Prefab } from 'cc';
|
import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, Prefab } from 'cc';
|
||||||
import { BaseView } from 'db://assets/scripts/core/BaseView';
|
import { BaseView } from 'db://assets/scripts/core/BaseView';
|
||||||
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
||||||
import { StorageManager } from 'db://assets/scripts/utils/StorageManager';
|
import { StorageManager } from 'db://assets/scripts/utils/StorageManager';
|
||||||
@@ -28,6 +28,9 @@ export class PageLevel extends BaseView {
|
|||||||
@property(Node)
|
@property(Node)
|
||||||
inputLayout: Node | null = null;
|
inputLayout: Node | null = null;
|
||||||
|
|
||||||
|
@property(Node)
|
||||||
|
punchLayout: Node | null = null;
|
||||||
|
|
||||||
@property(Node)
|
@property(Node)
|
||||||
submitButton: Node | null = null;
|
submitButton: Node | null = null;
|
||||||
|
|
||||||
@@ -55,9 +58,6 @@ export class PageLevel extends BaseView {
|
|||||||
@property(Label)
|
@property(Label)
|
||||||
image2DescLabel: Label | null = null;
|
image2DescLabel: Label | null = null;
|
||||||
|
|
||||||
@property(Label)
|
|
||||||
punchlineLabel: Label | null = null;
|
|
||||||
|
|
||||||
@property(Node)
|
@property(Node)
|
||||||
tipsItem1: Node | null = null;
|
tipsItem1: Node | null = null;
|
||||||
|
|
||||||
@@ -103,6 +103,21 @@ export class PageLevel extends BaseView {
|
|||||||
/** 当前创建的输入框节点数组 */
|
/** 当前创建的输入框节点数组 */
|
||||||
private _inputNodes: Node[] = [];
|
private _inputNodes: Node[] = [];
|
||||||
|
|
||||||
|
/** InputLayout 中默认放置的输入框模板节点 */
|
||||||
|
private _inputTemplateNode: Node | null = null;
|
||||||
|
|
||||||
|
/** 当前创建的包袱展示块节点数组 */
|
||||||
|
private _punchBlockNodes: Node[] = [];
|
||||||
|
|
||||||
|
/** punchLayout 中默认放置的展示块模板节点 */
|
||||||
|
private _punchBlockTemplateNode: Node | null = null;
|
||||||
|
|
||||||
|
/** 是否正在同步输入格内容,避免设置文本时重复触发事件 */
|
||||||
|
private _isSyncingInputText: boolean = false;
|
||||||
|
|
||||||
|
/** 最近一次自动提交的答案,避免填满后重复提交同一内容 */
|
||||||
|
private _lastAutoSubmittedAnswer: string = '';
|
||||||
|
|
||||||
/** 倒计时剩余秒数 */
|
/** 倒计时剩余秒数 */
|
||||||
private _countdown: number = 60;
|
private _countdown: number = 60;
|
||||||
|
|
||||||
@@ -185,6 +200,7 @@ export class PageLevel extends BaseView {
|
|||||||
onViewDestroy(): void {
|
onViewDestroy(): void {
|
||||||
console.log('[PageLevel] onViewDestroy');
|
console.log('[PageLevel] onViewDestroy');
|
||||||
this.clearInputNodes();
|
this.clearInputNodes();
|
||||||
|
this.clearPunchBlocks();
|
||||||
this.stopCountdown();
|
this.stopCountdown();
|
||||||
this._closePassModal();
|
this._closePassModal();
|
||||||
this._stopStaminaRecoverTimer();
|
this._stopStaminaRecoverTimer();
|
||||||
@@ -310,9 +326,9 @@ export class PageLevel extends BaseView {
|
|||||||
// 显示解锁按钮(单个统一按钮)
|
// 显示解锁按钮(单个统一按钮)
|
||||||
this.showUnlockButton();
|
this.showUnlockButton();
|
||||||
|
|
||||||
// 根据答案长度创建单个输入框
|
// 根据答案字数创建输入格
|
||||||
if (config.answer) {
|
if (config.answer) {
|
||||||
this.createSingleInput(config.answer.length);
|
this.createInputBlocks(config.answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新倒计时显示
|
// 更新倒计时显示
|
||||||
@@ -328,99 +344,106 @@ export class PageLevel extends BaseView {
|
|||||||
LevelDataManager.instance.preloadNextLevel(this.currentLevelIndex);
|
LevelDataManager.instance.preloadNextLevel(this.currentLevelIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${config.answer?.length ?? 0}`);
|
console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${Array.from(config.answer ?? '').length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建单个输入框
|
* 根据答案字数创建输入格
|
||||||
* @param answerLength 答案长度,用于设置 placeholder 和宽度
|
|
||||||
*/
|
*/
|
||||||
private createSingleInput(answerLength: number): void {
|
private createInputBlocks(answer: string): void {
|
||||||
if (!this.inputLayout || !this.inputTemplate) {
|
if (!this.inputLayout) {
|
||||||
console.error('[PageLevel] inputLayout 或 inputTemplate 未设置');
|
console.error('[PageLevel] inputLayout 未设置');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理现有输入框
|
const chars = Array.from(answer);
|
||||||
|
const template = this.getInputTemplateNode();
|
||||||
|
if (!template) {
|
||||||
|
console.error('[PageLevel] InputLayout 下未找到默认 Input 节点');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.inputTemplate && this.inputTemplate !== template) {
|
||||||
|
this.inputTemplate.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.clearInputNodes();
|
this.clearInputNodes();
|
||||||
|
this.removeUnexpectedInputLayoutChildren(template);
|
||||||
|
this._lastAutoSubmittedAnswer = '';
|
||||||
|
|
||||||
// 隐藏模板节点
|
for (let i = 0; i < chars.length; i++) {
|
||||||
this.inputTemplate.active = false;
|
const inputNode = i === 0 ? template : instantiate(template);
|
||||||
|
inputNode.active = true;
|
||||||
|
inputNode.name = `Input_${i + 1}`;
|
||||||
|
inputNode.setPosition(PageLevel.ZERO_POS);
|
||||||
|
|
||||||
// 创建单个输入框
|
const editBox = inputNode.getComponent(EditBox);
|
||||||
const inputNode = instantiate(this.inputTemplate);
|
if (editBox) {
|
||||||
inputNode.active = true;
|
editBox.placeholder = '';
|
||||||
inputNode.name = 'singleInput';
|
editBox.maxLength = chars.length;
|
||||||
|
editBox.string = '';
|
||||||
// 设置位置
|
editBox.node.on(EditBox.EventType.TEXT_CHANGED, this.onInputTextChanged, this);
|
||||||
inputNode.setPosition(PageLevel.ZERO_POS);
|
editBox.node.on(EditBox.EventType.EDITING_DID_ENDED, this.onInputEditingEnded, this);
|
||||||
|
|
||||||
// 获取 EditBox 组件
|
|
||||||
const editBox = inputNode.getComponent(EditBox);
|
|
||||||
if (editBox) {
|
|
||||||
// 设置 placeholder 提示
|
|
||||||
editBox.placeholder = `(${answerLength}个字)`;
|
|
||||||
|
|
||||||
// 设置最大长度为答案长度
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动态调整输入框宽度
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inputNode.parent !== this.inputLayout) {
|
||||||
|
this.inputLayout.addChild(inputNode);
|
||||||
|
}
|
||||||
|
this._inputNodes.push(inputNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inputLayout.addChild(inputNode);
|
console.log(`[PageLevel] 创建输入格,答案长度: ${chars.length}`);
|
||||||
this._inputNodes.push(inputNode);
|
|
||||||
|
|
||||||
console.log(`[PageLevel] 创建单个输入框,答案长度: ${answerLength}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清理所有输入框节点
|
* 清理所有输入框节点
|
||||||
*/
|
*/
|
||||||
private clearInputNodes(): void {
|
private clearInputNodes(): void {
|
||||||
|
const template = this.getInputTemplateNode();
|
||||||
|
|
||||||
for (const node of this._inputNodes) {
|
for (const node of this._inputNodes) {
|
||||||
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.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 = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node === template) {
|
||||||
|
node.active = false;
|
||||||
|
} else {
|
||||||
|
node.removeFromParent();
|
||||||
|
node.destroy();
|
||||||
}
|
}
|
||||||
node.destroy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._inputNodes = [];
|
this._inputNodes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getInputTemplateNode(): Node | null {
|
||||||
|
if (this._inputTemplateNode?.isValid) return this._inputTemplateNode;
|
||||||
|
|
||||||
|
this._inputTemplateNode = this.inputLayout?.children.find(node => !!node.getComponent(EditBox)) ?? this.inputTemplate ?? null;
|
||||||
|
return this._inputTemplateNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeUnexpectedInputLayoutChildren(template: Node): void {
|
||||||
|
if (!this.inputLayout) return;
|
||||||
|
|
||||||
|
for (const child of [...this.inputLayout.children]) {
|
||||||
|
if (child !== template) {
|
||||||
|
child.removeFromParent();
|
||||||
|
child.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有输入框的值
|
* 获取所有输入框的值
|
||||||
*/
|
*/
|
||||||
getInputValues(): string[] {
|
getInputValues(): string[] {
|
||||||
if (this._inputNodes.length === 0) return [];
|
if (this._inputNodes.length === 0) return [];
|
||||||
const editBox = this._inputNodes[0].getComponent(EditBox);
|
return this._inputNodes.map(node => (node.getComponent(EditBox)?.string ?? '').trim());
|
||||||
const str = (editBox?.string ?? '').trim();
|
|
||||||
return [str];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -428,8 +451,7 @@ export class PageLevel extends BaseView {
|
|||||||
*/
|
*/
|
||||||
getAnswer(): string {
|
getAnswer(): string {
|
||||||
if (this._inputNodes.length === 0) return '';
|
if (this._inputNodes.length === 0) return '';
|
||||||
const editBox = this._inputNodes[0].getComponent(EditBox);
|
return this.getInputValues().join('').trim();
|
||||||
return (editBox?.string ?? '').trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== EditBox 事件回调 ==========
|
// ========== EditBox 事件回调 ==========
|
||||||
@@ -437,8 +459,14 @@ export class PageLevel extends BaseView {
|
|||||||
/**
|
/**
|
||||||
* 输入框文本变化回调
|
* 输入框文本变化回调
|
||||||
*/
|
*/
|
||||||
private onInputTextChanged(_editBox: EditBox): void {
|
private onInputTextChanged(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(inputIndex, editBox.string);
|
||||||
|
this.tryAutoSubmitAnswer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -448,6 +476,47 @@ export class PageLevel extends BaseView {
|
|||||||
console.log('[PageLevel] 输入编辑结束');
|
console.log('[PageLevel] 输入编辑结束');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private distributeInputText(startIndex: number, 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++) {
|
||||||
|
const editBox = this._inputNodes[i].getComponent(EditBox);
|
||||||
|
if (editBox) {
|
||||||
|
editBox.string = chars[i - startIndex] ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this._isSyncingInputText = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private tryAutoSubmitAnswer(): void {
|
||||||
|
if (!this._currentConfig || this._isTransitioning) return;
|
||||||
|
|
||||||
|
const values = this.getInputValues();
|
||||||
|
const isFilled = values.length === Array.from(this._currentConfig.answer ?? '').length && values.every(value => value.length === 1);
|
||||||
|
if (!isFilled) {
|
||||||
|
this._lastAutoSubmittedAnswer = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const answer = values.join('');
|
||||||
|
if (answer === this._lastAutoSubmittedAnswer) return;
|
||||||
|
|
||||||
|
this._lastAutoSubmittedAnswer = answer;
|
||||||
|
this.onSubmitAnswer();
|
||||||
|
}
|
||||||
|
|
||||||
// ========== IconSetting 按钮相关 ==========
|
// ========== IconSetting 按钮相关 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -680,18 +749,74 @@ export class PageLevel extends BaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置谐音梗说明(通关后展示,未通关时传 null 隐藏)
|
* 设置谐音梗说明(通关后逐字展示,未通关时传 null 隐藏)
|
||||||
*/
|
*/
|
||||||
private setPunchline(punchline: string | null): void {
|
private setPunchline(punchline: string | null): void {
|
||||||
if (!this.punchlineLabel) return;
|
if (!this.punchLayout) return;
|
||||||
|
|
||||||
if (punchline) {
|
const chars = Array.from(punchline ?? '');
|
||||||
this.punchlineLabel.node.active = true;
|
if (chars.length === 0) {
|
||||||
this.punchlineLabel.string = punchline;
|
this.punchLayout.active = false;
|
||||||
} else {
|
this.clearPunchBlocks();
|
||||||
this.punchlineLabel.node.active = false;
|
return;
|
||||||
this.punchlineLabel.string = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const template = this.getPunchBlockTemplateNode();
|
||||||
|
if (!template) {
|
||||||
|
console.error('[PageLevel] punchLayout 下未找到默认 block 节点');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clearPunchBlocks();
|
||||||
|
this.punchLayout.active = true;
|
||||||
|
|
||||||
|
for (let i = 0; i < chars.length; i++) {
|
||||||
|
const blockNode = i === 0 ? template : instantiate(template);
|
||||||
|
blockNode.active = true;
|
||||||
|
blockNode.name = `block_${i + 1}`;
|
||||||
|
blockNode.setPosition(PageLevel.ZERO_POS);
|
||||||
|
|
||||||
|
const label = this.getPunchBlockLabel(blockNode);
|
||||||
|
if (label) {
|
||||||
|
label.string = chars[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockNode.parent !== this.punchLayout) {
|
||||||
|
this.punchLayout.addChild(blockNode);
|
||||||
|
}
|
||||||
|
this._punchBlockNodes.push(blockNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearPunchBlocks(): void {
|
||||||
|
const template = this.getPunchBlockTemplateNode();
|
||||||
|
|
||||||
|
for (const node of this._punchBlockNodes) {
|
||||||
|
if (node.isValid) {
|
||||||
|
const label = this.getPunchBlockLabel(node);
|
||||||
|
if (label) {
|
||||||
|
label.string = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node === template) {
|
||||||
|
node.active = false;
|
||||||
|
} else {
|
||||||
|
node.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._punchBlockNodes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPunchBlockTemplateNode(): Node | null {
|
||||||
|
if (this._punchBlockTemplateNode?.isValid) return this._punchBlockTemplateNode;
|
||||||
|
|
||||||
|
this._punchBlockTemplateNode = this.punchLayout?.children[0] ?? null;
|
||||||
|
return this._punchBlockTemplateNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPunchBlockLabel(blockNode: Node): Label | null {
|
||||||
|
return blockNode.getChildByName('Label')?.getComponent(Label) ?? blockNode.getComponent(Label);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 音效相关方法 ==========
|
// ========== 音效相关方法 ==========
|
||||||
|
|||||||
Reference in New Issue
Block a user