You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
7.5 KiB
201 lines
7.5 KiB
|
1 week ago
|
import { assetManager } from 'cc';
|
||
|
|
import { GBundle } from './ConfigRes';
|
||
|
|
import { BattleType } from '../manager/ChapterDataManager';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 进战斗前预载 home(priority=3);audio 为远程包(不计 30M 包体),运行时 loadBundle 从 CDN 拉取。
|
||
|
|
* loadBundle 只拉分包索引 config,不会把包内贴图/预制体载入内存;
|
||
|
|
* 具体资源在 ensurePrefab / getNodeAsync 时按依赖图逐条加载。
|
||
|
|
*/
|
||
|
|
const BATTLE_ENTRY_BUNDLES: string[] = [GBundle.Home, GBundle.Audio];
|
||
|
|
|
||
|
|
const JIUJIUWOYA_BUNDLE_GROUP: string[] = [GBundle.jiujiuwoya, GBundle.jiujiuwoyaCard];
|
||
|
|
|
||
|
|
/** 离开战斗时可卸载的分包(保留 home/audio 等大厅共用包) */
|
||
|
|
const BATTLE_RELEASE_BUNDLES: string[] = [
|
||
|
|
GBundle.tetriCards,
|
||
|
|
GBundle.tetriWeapon,
|
||
|
|
GBundle.tetriGame,
|
||
|
|
GBundle.tetriMap,
|
||
|
|
GBundle.BattleGameMonster,
|
||
|
|
GBundle.jiujiuwoya,
|
||
|
|
GBundle.jiujiuwoyaCard,
|
||
|
|
];
|
||
|
|
// 注意:故意不卸 battleGameWeapon——二次进关高频用 weapon1/hitEffect;
|
||
|
|
// 若每次 removeBundle,体感会比「以前一次预载一堆」更慢。
|
||
|
|
|
||
|
|
/** 进大厅前需加载的分包(启动仅保留 main/first/comm) */
|
||
|
|
const HALL_BUNDLES: string[] = [
|
||
|
|
GBundle.Comm2,
|
||
|
|
GBundle.Home,
|
||
|
|
GBundle.Items,
|
||
|
|
GBundle.Audio,
|
||
|
|
];
|
||
|
|
|
||
|
|
/** 进方块战斗前必须就绪的分包(openPanel 之前) */
|
||
|
|
const TETRI_BATTLE_CORE_BUNDLES: string[] = [
|
||
|
|
GBundle.tetriGame,
|
||
|
|
GBundle.tetriMap,
|
||
|
|
GBundle.tetriCards,
|
||
|
|
GBundle.tetriWeapon,
|
||
|
|
GBundle.BattleGameMonster,
|
||
|
|
];
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 战斗/大厅分包与战斗内增量资源加载。
|
||
|
|
* 目标:缩减启动与进局内峰值内存,怪物按波次再拉。
|
||
|
|
*/
|
||
|
|
export class BattleResLoader {
|
||
|
|
private static _bundleInflight = new Map<string, Promise<boolean>>();
|
||
|
|
|
||
|
|
static loadBundle(bundleName: string): Promise<boolean> {
|
||
|
|
if (assetManager.getBundle(bundleName)) {
|
||
|
|
return Promise.resolve(true);
|
||
|
|
}
|
||
|
|
const pending = this._bundleInflight.get(bundleName);
|
||
|
|
if (pending) {
|
||
|
|
return pending;
|
||
|
|
}
|
||
|
|
const task = new Promise<boolean>((resolve) => {
|
||
|
|
assetManager.loadBundle(bundleName, (err) => {
|
||
|
|
if (err) {
|
||
|
|
console.warn(`[BattleResLoader] loadBundle ${bundleName} 失败`, err);
|
||
|
|
resolve(false);
|
||
|
|
} else {
|
||
|
|
resolve(true);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
this._bundleInflight.set(bundleName, task);
|
||
|
|
task.then(
|
||
|
|
() => { this._bundleInflight.delete(bundleName); },
|
||
|
|
() => { this._bundleInflight.delete(bundleName); },
|
||
|
|
);
|
||
|
|
return task;
|
||
|
|
}
|
||
|
|
|
||
|
|
static async ensureBundles(bundleNames: string[]): Promise<void> {
|
||
|
|
await Promise.all(bundleNames.map((name) => this.loadBundle(name)));
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 进局最小引导:注册 home;audio 远程包按需 loadBundle */
|
||
|
|
static async ensureBattleEntry(): Promise<void> {
|
||
|
|
await this.ensureBundles(BATTLE_ENTRY_BUNDLES);
|
||
|
|
if (gg?.game?.CurentSelectBattleType === BattleType.OrangeWeaponMode_Ya) {
|
||
|
|
await this.ensureBundles(JIUJIUWOYA_BUNDLE_GROUP);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 打开战斗界面前:确保局内 UI/地图/卡牌等核心分包已 loadBundle */
|
||
|
|
static async ensureBattleCoreBundles(): Promise<void> {
|
||
|
|
if (gg?.game?.CurentSelectBattleType === BattleType.OrangeWeaponMode_Ya) {
|
||
|
|
await this.ensureBundles(JIUJIUWOYA_BUNDLE_GROUP);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
await this.ensureBundles(TETRI_BATTLE_CORE_BUNDLES);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @deprecated 请用 ensureBattleEntry;保留别名避免遗漏调用点 */
|
||
|
|
static async ensureBattleBundles(): Promise<void> {
|
||
|
|
return this.ensureBattleEntry();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 回大厅卸载战斗分包(releaseAll + removeBundle)。
|
||
|
|
* battleGameWeapon 不在列表中,避免二次进关冷拉武器。
|
||
|
|
*/
|
||
|
|
static releaseBattleBundles(): void {
|
||
|
|
for (let i = 0; i < BATTLE_RELEASE_BUNDLES.length; i++) {
|
||
|
|
const bundleName = BATTLE_RELEASE_BUNDLES[i];
|
||
|
|
this._bundleInflight.delete(bundleName);
|
||
|
|
const bundle = assetManager.getBundle(bundleName);
|
||
|
|
if (!bundle) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
bundle.releaseAll();
|
||
|
|
assetManager.removeBundle(bundle);
|
||
|
|
} catch (e) {
|
||
|
|
console.warn(`[BattleResLoader] releaseBattleBundles ${bundleName}`, e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static ensureHallBundles(): Promise<void> {
|
||
|
|
return this.ensureBundles(HALL_BUNDLES);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 按需预加载怪物 prefab(仅缓存,不实例化) */
|
||
|
|
static preloadMonsterModels(models: string[] | Iterable<string>): Promise<void> {
|
||
|
|
const names: string[] = [];
|
||
|
|
const pushName = (m: unknown) => {
|
||
|
|
if (typeof m === 'string' && m.length > 0) names.push(m);
|
||
|
|
};
|
||
|
|
if (Array.isArray(models)) {
|
||
|
|
for (let i = 0; i < models.length; i++) pushName(models[i]);
|
||
|
|
} else if (models) {
|
||
|
|
for (const m of models) pushName(m);
|
||
|
|
}
|
||
|
|
const unique = Array.from(new Set(names));
|
||
|
|
if (unique.length === 0) {
|
||
|
|
return Promise.resolve();
|
||
|
|
}
|
||
|
|
return Promise.all(
|
||
|
|
unique.map((model) => gg.res.ensurePrefab(model, 'prefab/monster/', GBundle.BattleGameMonster)),
|
||
|
|
).then(() => undefined);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 进关预热:仅 weapon1 子弹(首发武器)。
|
||
|
|
* 其它武器在三选一选中后再 {@link preloadWeaponCombatAssets}。
|
||
|
|
*/
|
||
|
|
static async preloadWeapon1Bullet(): Promise<void> {
|
||
|
|
await this.preloadWeaponCombatAssets(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 预加载单个武器的战斗资源:
|
||
|
|
* - `prefab/武器/weaponN` 子弹
|
||
|
|
* - 武器表 `hitEffect`(格式 spine名|预制体名)→ `prefab/爆炸/预制体名`
|
||
|
|
*/
|
||
|
|
static async preloadWeaponCombatAssets(weaponIdOrData: number | ITableWeapon2 | null | undefined): Promise<void> {
|
||
|
|
if (!gg?.res) return;
|
||
|
|
let weaponId = 0;
|
||
|
|
let hitEffect = '';
|
||
|
|
if (typeof weaponIdOrData === 'number') {
|
||
|
|
weaponId = weaponIdOrData;
|
||
|
|
const conf = gg.data?.project?.getWeaponData?.(weaponId);
|
||
|
|
hitEffect = conf?.hitEffect ?? '';
|
||
|
|
} else if (weaponIdOrData) {
|
||
|
|
weaponId = weaponIdOrData.weaponid;
|
||
|
|
hitEffect = weaponIdOrData.hitEffect ?? '';
|
||
|
|
}
|
||
|
|
if (!Number.isFinite(weaponId) || weaponId <= 0) return;
|
||
|
|
|
||
|
|
await this.loadBundle(GBundle.BattleGameWeapon);
|
||
|
|
const tasks: Promise<unknown>[] = [
|
||
|
|
gg.res.ensurePrefab(`weapon${weaponId}`, 'prefab/武器/', GBundle.BattleGameWeapon),
|
||
|
|
];
|
||
|
|
const bombName = this._parseHitEffectPrefabName(hitEffect);
|
||
|
|
if (bombName) {
|
||
|
|
tasks.push(gg.res.ensurePrefab(bombName, 'prefab/爆炸/', GBundle.BattleGameWeapon));
|
||
|
|
}
|
||
|
|
await Promise.all(tasks);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 批量预热(如三选一全选) */
|
||
|
|
static preloadWeaponCombatAssetsBatch(weapons: Array<number | ITableWeapon2>): void {
|
||
|
|
for (let i = 0; i < weapons.length; i++) {
|
||
|
|
void this.preloadWeaponCombatAssets(weapons[i]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** hitEffect: `spine名|爆炸预制体名` → 取预制体名 */
|
||
|
|
private static _parseHitEffectPrefabName(hitEffect: string | null | undefined): string {
|
||
|
|
if (!hitEffect || typeof hitEffect !== 'string') return '';
|
||
|
|
const parts = hitEffect.split('|');
|
||
|
|
const name = (parts.length >= 2 ? parts[1] : parts[0])?.trim();
|
||
|
|
return name || '';
|
||
|
|
}
|
||
|
|
}
|