feat: 新增关卡进度同步系统并优化UI显示
1. 新增GameEvent.STAGE_PROGRESS_UPDATE事件与关卡进度镜像逻辑 2. 在SingletonModuleComp中添加关卡进度VM层字段与同步方法 3. 重构MissionComp的UI显示逻辑,拆分计时与阶段显示 4. 更新mission.prefab的UI节点结构与数据绑定 5. 统一关卡进度更新的调用方式,替换直接赋值为专用方法
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
@@ -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 },玩家点击"下一节"后推进) */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user