Files
pixelheros/assets/script/game/common/SingletonModuleComp.ts
pan bb098d690b feat: 新增英雄升级测试模拟数据并修复UI预制件关联
1. 为英雄成长数据添加测试用模拟数据,用于验证英雄升级功能
2. 修复role_controller预制件的hero_btn和tals_btn控件绑定
3. 调整cHero预制件的控件结构与关联关系
2026-08-24 11:16:25 +08:00

396 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { sys, resources, SpriteAtlas } from "cc";
import { VM } from "../../../../extensions/oops-plugin-framework/assets/libs/model-view/ViewModel";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Initialize } from "../initialize/Initialize";
import { GameMap } from "../map/GameMap";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { GameEvent } from "./config/GameEvent";
import { GameScoreStats } from "./config/HeroAttrs";
import { mLogger } from "./Logger";
import { gameDataSync } from "./GameDataSync";
import { HERO_UPGRADE_NEED_FRAGMENTS } from "./config/GameSet";
/**
* 用远程数据覆盖本地数据(统一方法)
* @param remoteData 远程数据(云端或本地调试)
*/
export interface GameDate {
openid?: string,
score?: number,
mission?: number,
diamond?: number,
gold?: number,
task?: number,
noStop?: boolean,
showInfo?: boolean,
tip_done?: {
start: boolean,
hero: boolean,
equip: boolean,
skill: boolean,
shop: boolean,
},
switch_effect?: boolean, // 音效开关
switch_music?: boolean, // 音乐开关
timestamp?: number, // 用于比对本地与云端数据的最新状态
/** 英雄局外成长数据:key 为英雄 uuid,value 为成长记录 */
hero_growth?: Record<number, HeroGrowthRecord>,
}
export interface CloudData {
openid: string;
data: GameDate;
}
/** 英雄局外成长记录 */
export interface HeroGrowthRecord {
/** 英雄模板 uuid(对应 heroSet.HeroInfo 的 key) */
uuid: number;
/** 当前局外等级(从 1 开始) */
lv: number;
/** 当前拥有的英雄碎片数量 */
fragments: number;
}
/**
* 死亡英雄快照:英雄真实死亡时记录,供后续复活机制(重新召唤)消费。
* 仅记录静态/成长数据,不含战斗内临时状态(buff、CD 等)。
*/
export interface DeadHeroRecord {
/** 英雄模板 uuid(决定重新召唤哪个英雄) */
uuid: number;
/** 死亡时的英雄等级(复活后继承) */
lv: number;
/** 卡牌等级(驱动 bg_node 颜色) */
card_lv: number;
/** 死亡时占用的站位索引(复活时优先归位,-1 表示未分配) */
posIndex: number;
/** 死亡时的回合数(用于统计/按回合清理) */
wave: number;
}
/** 游戏模块 */
@ecs.register('SingletonModule')
export class SingletonModuleComp extends ecs.Comp {
private debugMode: boolean = false;
/** 游戏初始化模块 */
initialize: Initialize = null!;
/** 游戏地图 */
map: GameMap = null!;
/** 全局缓存的通用图集 */
uiconsAtlas: SpriteAtlas | null = null;
mission: any = {
status: 0, //0:未开始 1:进行中 2:胜利 3:失败
play: false,
pause: false,
in_select: false,
in_fight: false,
stop_mon_action: false,
heroGrid: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
monGrid: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
/** 本局真实死亡的英雄快照列表,供后续复活机制消费;整局结束(MissionEnd)时清空 */
dead_heroes: [] as DeadHeroRecord[],
};
data: any = {
openid: '',
score: 0,
mission: 1,
diamond: 100, //商店购买 及 双倍奖励资源
gold: 1000,
task: 0,
noStop: false,
showInfo: true,
/** 新手指引完成标记:false=需要提醒,true=已提醒过(仅提醒 1 次) */
tip_done: {
start: false, // 主页开始按钮
hero: false, // 召唤英雄面板按钮
equip: false, // 购买装备面板按钮
skill: false, // 购买技能卷轴面板按钮
shop: false, // 购买药品面板按钮
},
/** 英雄局外成长数据:记录每个英雄的局外等级与碎片数量 */
// TODO: 以下为测试英雄升级功能的模拟数据,验证通过后需移除(避免污染云端存档)
hero_growth: {
// 碎片充足:可连升 2 级(10+20=30,剩余 20 不够升 4 级)
5001: { uuid: 5001, lv: 1, fragments: 50 },
// 碎片不足:升 4 级需 40,验证升级失败路径
5002: { uuid: 5002, lv: 3, fragments: 15 },
// 碎片刚好够升到满级:升 6 级需 160
5003: { uuid: 5003, lv: 5, fragments: 160 },
// 已满级:验证满级后 upgradeHero 返回 false
5004: { uuid: 5004, lv: 6, fragments: 999 },
} as Record<number, HeroGrowthRecord>,
}
vmdata: any = {
game_over: false,
game_pause: false,
mission_data: {
mon_num: 0,//怪物数量
pending_mon_num: 0,//待刷出怪物数量(MissionMonComp 每帧写入,供回合结束检测与 HUD 进度)
wave_early_skip: 0,//本回合清场加速累计节省的秒数(MissionComp 用于还原 DDA 判定口径)
hero_num: 0,//英雄数量
wave_time_num: 0,//回合时间
in_fight: false,
fight_time: 0,//战斗时间
level: 1,//关卡等级
max_mission: 4,//最大关卡
coin: 0,
refresh_stone: 0,//刷新石(刷新卡池时优先消耗,不足时消耗金币)
time: 15 * 60,//游戏时间
},
scores: {
score: 0, // 基础得分
// 战斗统计
crt_count: 0, // 暴击次数
wf_count: 0, // 风怒次数
dod_count: 0, // 闪避次数
back_count: 0, // 击退次数
stun_count: 0, // 击晕次数
freeze_count: 0, // 冰冻次数
// 伤害统计
total_dmg: 0, // 总伤害
atk_count: 0, // 攻击次数
avg_dmg: 0, // 平均伤害
thorns_dmg: 0, // 反伤伤害
crit_dmg_total: 0, // 暴击伤害总额
// 生存统计
heal_total: 0, // 治疗总量
lifesteal_total: 0, // 吸血总量
shield_block_count: 0,
dead_trigger_count: 0,
// 资源统计
exp_total: 0, // 经验总数
gold_total: 0, // 金币总数
gold_earned: 0,
gold_spent: 0,
refresh_count: 0,
refresh_hit_count: 0,
// 击杀统计
melee_kill_count: 0, // 近战怪击杀数量
remote_kill_count: 0, // 远程怪击杀数量
elite_kill_count: 0, // 精英怪击杀数量
boss_kill_count: 0, // Boss击杀数
// 战绩统计
wave_win_count: 0,
wave_remain_monsters: 0,
wave_all_alive_count: 0,
passed_wave_20: false,
highest_dmg: 0,
score_combat: 0,
score_output: 0,
score_defense: 0,
score_build: 0,
score_efficiency: 0,
achieved_highlights: [],
} as GameScoreStats,
gold: 0, // 金币数据(MVVM绑定字段)
switch_effect: true, // 音效开关(MVVM绑定字段)
switch_music: true, // 音乐开关(MVVM绑定字段)
};
vmAdd() {
VM.add(this.vmdata, "data");
// mLogger.log(this.debugMode, 'SMC', "[MissionComp]局内数据初始化",smc.vmdata.mission_data)
}
reset() {
for (var key in this.vmdata) {
delete this.vmdata[key];
}
}
/**
* 重置单局评分数据
* 在每次新战斗开始时调用,确保上一局的得分不会带入新一局
*/
resetScores() {
this.vmdata.scores = {
score: 0,
crt_count: 0,
wf_count: 0,
dod_count: 0,
back_count: 0,
stun_count: 0,
freeze_count: 0,
total_dmg: 0,
atk_count: 0,
avg_dmg: 0,
thorns_dmg: 0,
crit_dmg_total: 0,
heal_total: 0,
lifesteal_total: 0,
shield_block_count: 0,
dead_trigger_count: 0,
exp_total: 0,
gold_total: 0,
gold_earned: 0,
gold_spent: 0,
refresh_count: 0,
refresh_hit_count: 0,
melee_kill_count: 0,
remote_kill_count: 0,
elite_kill_count: 0,
boss_kill_count: 0,
wave_win_count: 0,
wave_remain_monsters: 0,
wave_all_alive_count: 0,
passed_wave_20: false,
highest_dmg: 0,
score_combat: 0,
score_output: 0,
score_defense: 0,
score_build: 0,
score_efficiency: 0,
achieved_highlights: [],
} as GameScoreStats;
}
// ==================== 数据管理方法 ====================
/**
* 判断是否为微信客户端
*/
isWxClient(): boolean {
return gameDataSync.isWxClient();
}
updateCloudData() {
return gameDataSync.updateCloudData();
}
getCloudData() {
gameDataSync.syncFromCloud();
}
public async overrideLocalDataWithRemote(cloudData: any) {
try {
// 直接覆盖基础游戏数据
if (cloudData.openid) {
this.data.openid = cloudData.openid;
}
// 直接覆盖出战英雄配置
if (cloudData.data) {
Object.assign(this.data, cloudData.data);
}
// 触发UI更新
oops.message.dispatchEvent(GameEvent.GOLD_UPDATE);
} catch (error) {
mLogger.error(this.debugMode, 'SMC', `[SMC]: 数据覆盖失败:`, error);
}
}
getGameDate() {
let data: GameDate = {
...this.data,
timestamp: Date.now() // 每次获取当前数据结构时都附带最新的时间戳
};
return data;
}
updateGold(gold: number, is_sync: boolean = true) {
this.vmdata.gold += gold;
if (is_sync) {
gameDataSync.markDataDirty();
}
oops.message.dispatchEvent(GameEvent.GOLD_UPDATE)
return true
}
updateSwitchEffect(isOpen: boolean, is_sync: boolean = true) {
this.vmdata.switch_effect = isOpen;
if (oops.audio) oops.audio.switchEffect = isOpen;
if (is_sync) {
gameDataSync.markDataDirty();
}
}
updateSwitchMusic(isOpen: boolean, is_sync: boolean = true) {
this.vmdata.switch_music = isOpen;
if (oops.audio) oops.audio.switchMusic = isOpen;
if (is_sync) {
gameDataSync.markDataDirty();
}
}
// ==================== 英雄局外成长 ====================
/**
* 获取英雄局外成长记录;若不存在则初始化并返回
* @param uuid 英雄模板 uuid
*/
getHeroGrowth(uuid: number): HeroGrowthRecord {
if (!this.data.hero_growth) this.data.hero_growth = {};
if (!this.data.hero_growth[uuid]) {
this.data.hero_growth[uuid] = { uuid, lv: 1, fragments: 0 };
}
return this.data.hero_growth[uuid];
}
/**
* 获取英雄局外等级(未初始化时返回 1)
* @param uuid 英雄模板 uuid
*/
getHeroOuterLv(uuid: number): number {
return this.getHeroGrowth(uuid).lv;
}
/**
* 增加英雄碎片
* @param uuid 英雄模板 uuid
* @param count 增加的碎片数量(正数)
*/
addHeroFragments(uuid: number, count: number): void {
const growth = this.getHeroGrowth(uuid);
growth.fragments = Math.max(0, growth.fragments + count);
gameDataSync.markDataDirty();
oops.message.dispatchEvent(GameEvent.HERO_GROWTH_UPDATE);
}
/**
* 消耗碎片提升英雄局外等级(内部查表决定所需碎片数)
* @param uuid 英雄模板 uuid
* @returns 是否升级成功(已到满级或碎片不足时返回 false)
*/
upgradeHero(uuid: number): boolean {
const growth = this.getHeroGrowth(uuid);
const needFragments = HERO_UPGRADE_NEED_FRAGMENTS[growth.lv + 1];
// 配置表中查不到下一级 = 已到满级
if (needFragments === undefined) return false;
if (growth.fragments < needFragments) return false;
growth.fragments -= needFragments;
growth.lv += 1;
gameDataSync.markDataDirty();
oops.message.dispatchEvent(GameEvent.HERO_GROWTH_UPDATE);
return true;
}
/**
* 在游戏载入早期调用,预加载常用图集
*/
preloadCommonAssets() {
resources.load("gui/ui6", SpriteAtlas, (err, atlas) => {
if (!err && atlas) {
// 增加引用计数,防止图集被引擎自动垃圾回收(GC)导致底层 spriteFrames 为 null
atlas.addRef();
this.uiconsAtlas = atlas;
} else {
mLogger.error(this.debugMode, 'SMC', "预加载 gui/ui6 图集失败:", err);
}
});
}
}
export var smc: SingletonModuleComp = ecs.getSingleton(SingletonModuleComp);