大版本优化,主要在Map,JiaZai,GameManager, 优化新内存方案进游戏慢的问题,重新调整了所有的分包加载逻辑顺序

This commit is contained in:
COMPUTER\EDY
2026-09-08 20:40:59 +08:00
parent af740a5b3c
commit 4e0378577d
31 changed files with 2079 additions and 743 deletions
+170 -36
View File
@@ -4,6 +4,11 @@ import { MiniGameSdk } from "../../Sdk/MiniGameSdk";
import { GameTool } from "../Tool/GameTool";
import BlockAssetManager from "../Tool/BlockAssetManager";
interface GameplayEntryPreparation {
startedAt: number;
downloads: Promise<boolean>;
release: () => void;
}
const { ccclass, property } = cc._decorator;
@ccclass('GameConfig')
@@ -74,6 +79,84 @@ export class GameConfig {
private static sceneLoadRequest = 0;
private static homeWarmupRequest = 0;
private static cancelBlockLoading: () => void = null;
private static gameplayScenePreload: Promise<void> = null;
private static gameplaySceneForegroundPreload: Promise<void> = null;
private static pendingEntryDownloadRelease: () => void = null;
static gameplayEntryStartedAt = 0;
// 仅本次启动有效;地图就绪即可完成首次进关,不等待弹窗实例。
static firstGameplayEntryComplete = false;
static firstGameplayEntryPending = false;
/** 仅合并正在进行的预加载,不能把历史完成标记当作永久有效的场景缓存。 */
static preloadGameplayScene(foreground = false): Promise<void> {
const key = foreground ? "gameplaySceneForegroundPreload" : "gameplayScenePreload";
if (this[key]) return this[key];
const request = this.timeGameplayStage(foreground ? "GameScene前台文件准备" : "首页GameScene预取",
() => new Promise<void>((resolve, reject) => {
const done = (error: Error) => error ? reject(error) : resolve();
const bundle = cc.assetManager.bundles.find(bundle => !!bundle.getSceneInfo("GameScene"));
if (!bundle) { reject(new Error("GameScene 所属 Bundle 尚未加载")); return; }
// 不等待首页的低优先级任务;引擎复用已下载文件,前台请求使用 scene 调度配置。
bundle.preloadScene("GameScene", { preset: "scene", priority: 10 }, done);
}));
this[key] = request;
const clear = () => {
if (this[key] === request) this[key] = null;
};
request.then(clear, clear);
return request;
}
static async timeGameplayStage<T>(stage: string, task: () => Promise<T>): Promise<T> {
const start = Date.now();
// console.log(`[进关耗时][${stage}] 开始`);
try {
const result = await task();
// console.log(`[进关耗时][${stage}] ${result === false ? "未就绪" : "完成"},独立耗时=${Date.now() - start}ms`);
return result;
} catch (error) {
// console.log(`[进关耗时][${stage}] 失败,独立耗时=${Date.now() - start}ms`, error);
throw error;
}
}
/** 点击进关即开始不依赖关卡 JSON 的任务,所有入口共用原有下载保障。 */
static beginGameplayEntryPreparation(): GameplayEntryPreparation {
const startedAt = Date.now();
this.gameplayEntryStartedAt = startedAt;
const previousRelease = this.takeEntryDownloadRelease();
const releaseHold = BlockAssetManager.instance.holdBackgroundDownloads();
if (previousRelease) previousRelease();
const release = () => {
releaseHold();
if (this.pendingEntryDownloadRelease === release) this.pendingEntryDownloadRelease = null;
};
this.pendingEntryDownloadRelease = release;
// console.log("[进关耗时] 开始进关准备(各并行阶段耗时不能相加)");
const downloads = this.timeGameplayStage("五个弹窗分包磁盘检查/下载",
() => GameTool.ensureGameplayPopupSubpackagesDownloaded()).catch(() => false);
// 不把文件预取作为门槛;必要资源就绪后 loadScene 立即正式加载并复用已有文件。
this.preloadGameplayScene(true).catch(error => cc.warn("场景文件预取失败,正式加载时重试", error));
return { startedAt, downloads, release };
}
/** 新 Map 接管自己的暂停凭证,旧 Map 销毁不能恢复新进关的后台派发。 */
static takeEntryDownloadRelease(): () => void {
const release = this.pendingEntryDownloadRelease;
this.pendingEntryDownloadRelease = null;
return release;
}
/** 首页仅用来判断能否启动自动预加载,不暂停或接管任何在途回调。 */
static isGameplayEntryPreparing(): boolean {
return !!this.pendingEntryDownloadRelease;
}
static shouldPrepareFirstGameplayEntry(): boolean {
const scene = cc.director.getScene();
return !this.firstGameplayEntryComplete && !!scene && scene.name === "HomeScene"
&& GameTool.shouldPreloadGameplayPopupInstances();
}
static getCurrentLevelBlockDefinitions(): any[] {
const blocks = this.BLOCK_INFO && this.BLOCK_INFO[0] ? this.BLOCK_INFO[0] : [];
@@ -175,59 +258,95 @@ export class GameConfig {
}
/** 所有正式进关入口都在弹窗分包及当前关图片就绪后才切换 GameScene。 */
static enterGameSceneWhenReady(time = 0): void {
static enterGameSceneWhenReady(time = 0, preparation?: GameplayEntryPreparation): void {
const id = ++this.sceneLoadRequest;
const scene = cc.director.getScene();
const blocks = this.BLOCK_INFO;
const isCurrent = () => id === this.sceneLoadRequest && cc.director.getScene() === scene && this.BLOCK_INFO === blocks;
const loading = this.createBlockLoading(isCurrent);
const firstEntry = this.shouldPrepareFirstGameplayEntry();
const entry = preparation || this.beginGameplayEntryPreparation();
const { release, startedAt } = entry;
if (!GameTool.areGameplayPopupSubpackagesDownloaded()) loading.open();
GameTool.ensureGameplayPopupSubpackagesDownloaded().then(popupReady => {
if (firstEntry || !GameTool.areGameplayPopupSubpackagesDownloaded()) loading.open();
// 只等待磁盘分包与当前关图片,不等待后台场景预加载回调。
Promise.all([
entry.downloads,
this.timeGameplayStage("当前关方块+公共图集", () => this.prepareBlockWindow(loading.open)),
]).then(([popupReady, ready]) => {
if (!isCurrent()) {
loading.close();
return false;
release();
return;
}
if (!popupReady) {
loading.close();
release();
const missing = GameTool.getMissingGameplayPopupSubpackages();
this.showBlockLoadError(
new Error(`游戏弹窗分包下载失败: ${missing.join(", ")}`),
() => this.enterGameSceneWhenReady(time),
"游戏界面资源下载失败\n请检查网络,点击此处重试"
);
return false;
return;
}
return this.prepareBlockWindow(loading.open);
}).then(ready => {
if (!ready) {
loading.close();
release();
return;
}
// console.log(`[进关耗时][进关门槛就绪] 从开始累计=${Date.now() - startedAt}ms`);
loading.close();
if (!ready || !isCurrent()) return;
cc.director.preloadScene("GameScene", error => {
if (!isCurrent()) return;
if (error) {
this.showBlockLoadError(error, () => this.enterGameSceneWhenReady(time));
if (firstEntry) {
const home = cc.find("Canvas").getComponent("JiaZai");
home.playGameEntryTransition(true);
}
const transitionStartedAt = Date.now();
setTimeout(() => {
if (!isCurrent()) {
release();
return;
}
setTimeout(() => {
if (!isCurrent()) return;
const retainPopupBundles = !!scene && scene.name === "GameScene"
&& GameTool.beginGameplayPopupGameSceneReloadRetention();
try {
cc.director.loadScene("GameScene", () => {
if (retainPopupBundles) {
GameTool.finishGameplayPopupGameSceneReloadRetention();
}
});
} catch (error) {
const retainPopupBundles = !!scene && scene.name === "GameScene"
&& GameTool.beginGameplayPopupGameSceneReloadRetention();
// console.log(`[进关耗时][转场等待] 独立耗时=${Date.now() - transitionStartedAt}ms`);
const sceneStartedAt = Date.now();
// console.log("[进关耗时][GameScene正式加载及激活] 开始");
try {
if (firstEntry) this.firstGameplayEntryPending = GameTool.shouldPreloadGameplayPopupInstances();
const accepted = cc.director.loadScene("GameScene", (error: Error) => {
if (retainPopupBundles) {
GameTool.finishGameplayPopupGameSceneReloadRetention();
}
this.showBlockLoadError(error, () => this.enterGameSceneWhenReady(time));
if (error) {
// console.log(`[进关耗时][GameScene正式加载及激活] 失败,独立耗时=${Date.now() - sceneStartedAt}ms`, error);
release();
if (isCurrent()) {
this.firstGameplayEntryPending = false;
this.showBlockLoadError(error, () => this.enterGameSceneWhenReady(time));
}
} else {
// console.log(`[进关耗时][GameScene正式加载及激活] 完成,独立耗时=${Date.now() - sceneStartedAt}ms,从开始累计=${Date.now() - startedAt}ms`);
}
});
if (accepted === false) {
release();
if (retainPopupBundles) GameTool.finishGameplayPopupGameSceneReloadRetention();
this.firstGameplayEntryPending = false;
this.showBlockLoadError(new Error("GameScene 切换未启动"), () => this.enterGameSceneWhenReady(time));
}
}, time);
});
} catch (error) {
release();
if (firstEntry) this.firstGameplayEntryPending = false;
if (retainPopupBundles) {
GameTool.finishGameplayPopupGameSceneReloadRetention();
}
this.showBlockLoadError(error, () => this.enterGameSceneWhenReady(time));
}
}, firstEntry ? Math.max(time, 1000) : time);
}).catch(error => {
loading.close();
release();
if (isCurrent()) this.showBlockLoadError(error, () => this.enterGameSceneWhenReady(time));
});
}
@@ -543,7 +662,7 @@ export class GameConfig {
vibrateOpen: true, //震动
coinnum: 0, //每局的金币数
paid_user: false, //是否是付费用户
version: "1.0.47", //版本号
version: "1.0.50", //版本号
shushu_DistinctId: "", //数数访客ID
shushu_AccountId: "", //数数账号ID
uid: "", //用户和后端唯一id
@@ -671,6 +790,8 @@ export class GameConfig {
var self = this;
const requestId = ++this.levelLoadRequest;
this.sceneLoadRequest++;
const previousEntryRelease = this.takeEntryDownloadRelease();
if (previousEntryRelease) previousEntryRelease();
if (this.cancelBlockLoading) this.cancelBlockLoading();
let name = null;
@@ -701,17 +822,30 @@ export class GameConfig {
const scene = cc.director.getScene();
const isCurrent = () => requestId === this.levelLoadRequest && (!type || cc.director.getScene() === scene);
const firstEntryLoading = type && this.shouldPrepareFirstGameplayEntry() ? this.createBlockLoading(isCurrent) : null;
if (firstEntryLoading) firstEntryLoading.open();
const preparation = type ? this.beginGameplayEntryPreparation() : null;
const configStartedAt = Date.now();
// console.log(`[进关耗时][${name}/custom分包] 开始`);
let bundleName = "custom"; // 替换为实际的分包名称
cc.assetManager.loadBundle(bundleName, (err: Error, bundle: cc.AssetManager.Bundle) => {
if (!isCurrent()) return;
cc.assetManager.loadBundle(bundleName, { priority: 10 }, (err: Error, bundle: cc.AssetManager.Bundle) => {
// console.log(`[进关耗时][${name}/custom分包] ${err ? "失败" : "完成"},独立耗时=${Date.now() - configStartedAt}ms`);
if (!isCurrent()) { if (preparation) preparation.release(); return; }
if (err) {
if (preparation) preparation.release();
if (firstEntryLoading) firstEntryLoading.close();
if (type) this.showBlockLoadError(err, () => this.LEVEL_INFO_init(type, time, otherLevel, endless));
return;
}
bundle.load(name, cc.JsonAsset, (err: Error, res: cc.JsonAsset) => {
if (!isCurrent()) return;
const jsonStartedAt = Date.now();
// console.log(`[进关耗时][${name}/JSON] 开始`);
cc.assetManager.loadAny({ path: name, type: cc.JsonAsset, bundle: bundleName }, { priority: 10 }, (err: Error, res: cc.JsonAsset) => {
// console.log(`[进关耗时][${name}/JSON] ${err ? "失败" : "完成"},独立耗时=${Date.now() - jsonStartedAt}ms`);
if (!isCurrent()) { if (preparation) preparation.release(); return; }
if (err) {
if (preparation) preparation.release();
if (firstEntryLoading) firstEntryLoading.close();
if (bundle.getInfoWithPath(name, cc.JsonAsset)) {
if (type) this.showBlockLoadError(err, () => this.LEVEL_INFO_init(type, time, otherLevel, endless));
return;
@@ -727,7 +861,7 @@ export class GameConfig {
return;
}
self.applyLevelData(res.json, type, time, Number(name.substring("Json/level".length)));
self.applyLevelData(res.json, type, time, Number(name.substring("Json/level".length)), preparation);
});
});
@@ -1013,13 +1147,13 @@ export class GameConfig {
const bundleName = "custom";
const name = `Json/level${level}`;
cc.assetManager.loadBundle(bundleName, (err: Error, bundle: cc.AssetManager.Bundle) => {
cc.assetManager.loadBundle(bundleName, { priority: 10 }, (err: Error, bundle: cc.AssetManager.Bundle) => {
if (err) {
reject(new Error(`加载分包失败: ${err.message}`));
return;
}
bundle.load(name, cc.JsonAsset, (err: Error, res: cc.JsonAsset) => {
cc.assetManager.loadAny({ path: name, type: cc.JsonAsset, bundle: bundleName }, { priority: 10 }, (err: Error, res: cc.JsonAsset) => {
if (err) {
reject(new Error(`加载关卡资源失败: ${err.message}`));
return;
@@ -1111,7 +1245,7 @@ export class GameConfig {
/**
* 应用关卡数据
*/
static applyLevelData(jsonData: any, type: any, time: any, levelNumber = Number(jsonData.LEVEL_INFO[0].id)) {
static applyLevelData(jsonData: any, type: any, time: any, levelNumber = Number(jsonData.LEVEL_INFO[0].id), preparation?: GameplayEntryPreparation) {
this.loadedLevelNumber = levelNumber;
this.BLOCK_INFO = jsonData["BLOCK_INFO"];
this.LEVEL_INFO = jsonData["LEVEL_INFO"];
@@ -1119,7 +1253,7 @@ export class GameConfig {
console.log("关卡配置加载完成");
if (type) {
this.enterGameSceneWhenReady(time);
this.enterGameSceneWhenReady(time, preparation);
}
}
@@ -1,25 +1,8 @@
export const REVIVE_COST_AB_NAME = "revive_cost_ascend";
export const MAX_REVIVE_COST_STEP = 5;
// 第三层实验结束,所有玩家统一采用原 C 组复活费用。
const REVIVE_COSTS = [500, 700, 900, 1100, 1300, 1500];
export const MAX_REVIVE_COST_STEP = REVIVE_COSTS.length - 1;
const DEFAULT_REVIVE_COSTS = [500];
const REVIVE_COSTS_BY_GROUP = {
B: [500, 700, 900],
C: [500, 700, 900, 1100, 1300, 1500]
};
export function getABReviveCoinCost(assignment: any, reviveCount: any): number {
const isTargetExperiment = assignment
&& String(assignment.layer) === "3"
&& assignment.name === REVIVE_COST_AB_NAME
&& assignment.enabled !== false;
const group = isTargetExperiment
? String(assignment.group || "Default").toUpperCase()
: "DEFAULT";
const costs = group === "B"
? REVIVE_COSTS_BY_GROUP.B
: group === "C"
? REVIVE_COSTS_BY_GROUP.C
: DEFAULT_REVIVE_COSTS;
export function getReviveCoinCost(reviveCount: any): number {
const step = Math.max(0, Math.floor(Number(reviveCount) || 0));
return costs[Math.min(step, costs.length - 1)];
return REVIVE_COSTS[Math.min(step, MAX_REVIVE_COST_STEP)];
}