feat: 新增关卡进度同步系统并优化UI显示

1.  新增GameEvent.STAGE_PROGRESS_UPDATE事件与关卡进度镜像逻辑
2.  在SingletonModuleComp中添加关卡进度VM层字段与同步方法
3.  重构MissionComp的UI显示逻辑,拆分计时与阶段显示
4.  更新mission.prefab的UI节点结构与数据绑定
5.  统一关卡进度更新的调用方式,替换直接赋值为专用方法
This commit is contained in:
pan
2026-09-11 11:17:35 +08:00
parent f6a1a99dc9
commit c04a357183
4 changed files with 614 additions and 229 deletions

View File

@@ -234,6 +234,12 @@ export class SingletonModuleComp extends ecs.Comp {
silver: 0, // 银币数据(MVVM绑定字段,镜像 data.silver,刷新卡牌消耗)
switch_effect: true, // 音效开关(MVVM绑定字段)
switch_music: true, // 音乐开关(MVVM绑定字段)
/** 关卡进度(MVVM绑定字段,镜像 data.stage_progress,供 HUD 实时显示) */
stage_progress: {
chapter: 1,
stage: 1,
section: 1,
},
};
@@ -325,7 +331,9 @@ export class SingletonModuleComp extends ecs.Comp {
// 触发UI更新(同步镜像层 + 事件)
this.syncCurrencyToView();
this.syncStageProgressToView();
oops.message.dispatchEvent(GameEvent.GOLD_UPDATE);
oops.message.dispatchEvent(GameEvent.STAGE_PROGRESS_UPDATE);
} catch (error) {
mLogger.error(this.debugMode, 'SMC', `[SMC]: 数据覆盖失败:`, error);
@@ -342,6 +350,35 @@ export class SingletonModuleComp extends ecs.Comp {
this.vmdata.diamond = this.data.diamond ?? 0;
this.vmdata.silver = this.data.silver ?? 0;
}
/**
* 将 data 层的关卡进度镜像到 vmdata 视图层(MVVM 绑定字段)。
* Why: data.stage_progress 为云端持久权威源,vmdata.stage_progress 仅供 HUD 绑定展示;
* 登录覆盖、关卡推进后都必须调用本方法保持镜像一致。
*/
public syncStageProgressToView(): void {
const p = this.data.stage_progress;
this.vmdata.stage_progress = {
chapter: p?.chapter ?? 1,
stage: p?.stage ?? 1,
section: p?.section ?? 1,
};
}
/**
* 更新关卡进度(权威源 data + 视图镜像 vmdata),并派发事件刷新 UI。
* Why: 替代直接赋值 smc.data.stage_progress,保证视图层实时同步。
* @param progress 新的关卡进度(章节/大关/小节均从 1 开始)
* @param is_sync 是否标记脏并触发云端/本地同步
*/
public updateStageProgress(progress: { chapter: number; stage: number; section: number }, is_sync: boolean = true): void {
this.data.stage_progress = { ...progress };
this.syncStageProgressToView();
if (is_sync) {
gameDataSync.markDataDirty();
}
oops.message.dispatchEvent(GameEvent.STAGE_PROGRESS_UPDATE);
}
getGameDate() {
let data: GameDate = {
...this.data,

View File

@@ -100,6 +100,8 @@ export enum GameEvent {
HERO_GROWTH_UPDATE = "HERO_GROWTH_UPDATE",
/** 天赋数据变更(等级/点数更新,payload 为空,UI 重新读取 smc.data.talents / talent_points) */
TALENT_UPDATE = "TALENT_UPDATE",
/** 关卡进度变更(payload 为空,UI 重新读取 smc.vmdata.stage_progress) */
STAGE_PROGRESS_UPDATE = "STAGE_PROGRESS_UPDATE",
/** 小节开始(payload: { chapter, stage, section },MissionMonComp 据此生成怪物) */
SectionStart = "SectionStart",
/** 小节结束(payload: { chapter, stage, section },玩家点击"下一节"后推进) */

View File

@@ -90,9 +90,12 @@ export class MissionComp extends CCComp {
/** 开始战斗按钮 */
@property(Node)
start_btn: Node = null!
/** 时间/回合数显示节点 */
@property(Node)
time_node: Node = null!
/** 战斗计时 Label(mm:ss) */
@property(Label)
time_label: Label = null!
/** 阶段/进度显示 Label("第 X 节" / 回合间倒计时) */
@property(Label)
phase_label: Label = null!
@property(Node)
tooltip: Node = null!
@@ -298,12 +301,8 @@ export class MissionComp extends CCComp {
let s = remainSecond % 60;
let str = `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
if (str != this.lastTimeStr) {
if (this.time_node && this.time_node.isValid) {
const timeChild = this.time_node.getChildByName("time");
if (timeChild) {
const label = timeChild.getComponent(Label);
if (label) label.string = str;
}
if (this.time_label && this.time_label.isValid) {
this.time_label.string = str;
}
this.lastTimeStr = str;
}
@@ -332,14 +331,8 @@ export class MissionComp extends CCComp {
if (!force && remain === this.lastTimeSecond) return;
this.lastTimeSecond = remain;
if (this.time_node && this.time_node.isValid) {
const phaseNode = this.time_node.getChildByPath("Phase/Label");
if (phaseNode) {
const label = phaseNode.getComponent(Label);
if (label) {
label.string = `下一节 ${remain}s`;
}
}
if (this.phase_label && this.phase_label.isValid) {
this.phase_label.string = `下一节 ${remain}s`;
}
}
@@ -490,23 +483,18 @@ export class MissionComp extends CCComp {
}
// 更新阶段显示 UI
if (this.time_node && this.time_node.isValid) {
const phaseNode = this.time_node.getChildByPath("Phase/Label");
if (phaseNode) {
const label = phaseNode.getComponent(Label);
if (label) {
label.string = `第 ${getProgressText(this.currentChapter, this.currentStage, this.currentSection)} 节`;
}
if (this.phase_label && this.phase_label.isValid) {
this.phase_label.string = `第 ${getProgressText(this.currentChapter, this.currentStage, this.currentSection)} 节`;
// 阶段切换动感表现:只在进入战斗阶段跳动一下,让流程充满心流体验
if (targetPhase === MissionPhase.BattleStart) {
Tween.stopAllByTarget(this.time_node);
this.time_node.scale = v3(1, 1, 1);
tween(this.time_node)
.to(0.3, { scale: v3(1.2, 1.2, 1) }, { easing: "backOut" })
.to(0.2, { scale: v3(1, 1, 1) }, { easing: "sineInOut" })
.start();
}
// 阶段切换动感表现:只在进入战斗阶段跳动一下,让流程充满心流体验
if (targetPhase === MissionPhase.BattleStart) {
const labelNode = this.phase_label.node;
Tween.stopAllByTarget(labelNode);
labelNode.scale = v3(1, 1, 1);
tween(labelNode)
.to(0.3, { scale: v3(1.2, 1.2, 1) }, { easing: "backOut" })
.to(0.2, { scale: v3(1, 1, 1) }, { easing: "sineInOut" })
.start();
}
}
@@ -811,11 +799,11 @@ export class MissionComp extends CCComp {
// 存档直接推进到下一关第 1 小节(仅写存档,内存进度保留用于本局结算显示);
// 第 10 关通关即本章全通,存档停留等待后续章节扩展
if (this.currentStage < 10) {
smc.data.stage_progress = {
smc.updateStageProgress({
chapter: this.currentChapter,
stage: this.currentStage + 1,
section: 1,
};
});
if ((smc as any).updateCloudData) {
(smc as any).updateCloudData();
}
@@ -921,20 +909,22 @@ export class MissionComp extends CCComp {
this.currentStage = 1;
this.currentSection = 1;
}
// 加载完成后将权威进度镜像到 vmdata,驱动 HUD 实时显示
smc.syncStageProgressToView();
mLogger.log(this.debugMode, 'MissionComp',
`[StageProgress] 加载进度: ${getProgressText(this.currentChapter, this.currentStage, this.currentSection)}`);
}
/**
* 保存关卡进度存档
* 写入 smc.data.stage_progress,并标记云端同步
* 写入 smc.data.stage_progress 并镜像到 vmdata,同时标记云端同步
*/
private saveStageProgress() {
smc.data.stage_progress = {
smc.updateStageProgress({
chapter: this.currentChapter,
stage: this.currentStage,
section: this.currentSection,
};
});
// 标记数据脏,触发云端/本地同步
if ((smc as any).updateCloudData) {
(smc as any).updateCloudData();
@@ -1228,13 +1218,13 @@ export class MissionComp extends CCComp {
/** 性能监控相关代码 */
/** 初始化性能监控面板:在 time_node 下创建 Label */
/** 初始化性能监控面板:在本组件节点下创建 Label */
private initMemoryPanel() {
if (!this.showMemoryPanel || !this.time_node) return;
let panel = this.time_node.getChildByName("mem_panel");
if (!this.showMemoryPanel) return;
let panel = this.node.getChildByName("mem_panel");
if (!panel) {
panel = new Node("mem_panel");
panel.parent = this.time_node;
panel.parent = this.node;
panel.setPosition(0, -32, 0);
}
let label = panel.getComponent(Label);
@@ -1248,7 +1238,7 @@ export class MissionComp extends CCComp {
/** 移除性能监控面板 */
private removeMemoryPanel() {
const panel = this.time_node?.getChildByName("mem_panel");
const panel = this.node?.getChildByName("mem_panel");
if (panel) {
panel.destroy();
}