消消方块阵换皮表情
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.
 
 
 

269 lines
11 KiB

import { assetManager } from 'cc';
import { GBundle, GPath } 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,
];
/** 局内弹窗分包:波次宝箱/转盘/暂停/结算/玩法说明(进局前 loadBundle + ensurePrefab) */
const TETRI_BATTLE_POPUP_BUNDLES: string[] = [
GBundle.BattleGameResult,
GBundle.SkillSelect,
GBundle.TurnTable,
GBundle.tetriDoc,
];
/**
* 战斗/大厅分包与战斗内增量资源加载。
* 目标:缩减启动与进局内峰值内存,怪物按波次再拉。
*/
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);
gg.sdk?.pfLogKey?.('RES:BUNDLE_FAIL', `battle:${bundleName}`);
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,
...TETRI_BATTLE_CORE_BUNDLES,
...TETRI_BATTLE_POPUP_BUNDLES,
GBundle.BattleGameWeapon,
]);
return;
}
await this.ensureBundles([
...TETRI_BATTLE_CORE_BUNDLES,
...TETRI_BATTLE_POPUP_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]);
}
}
/**
* 局内弹窗 prefab 预热(进局 await,避免波次宝箱/转盘/暂停/结算首次弹出冷加载)。
* 不含大厅页、机制专用页(如 xiufuBox),那些仍按需加载。
*/
static async preloadBattlePopupAssets(): Promise<void> {
if (!gg?.res) return;
await this.ensureBundles(TETRI_BATTLE_POPUP_BUNDLES);
await Promise.all([
gg.res.ensurePrefab('BattleRevive', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('BattleFail', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('BattleSuccess', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('BattlePause', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('BattleGetCoin', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('SkillSelect', GPath.UIPrefab, GBundle.SkillSelect),
gg.res.ensurePrefab('TurnTable', GPath.UIPrefab, GBundle.TurnTable),
gg.res.ensurePrefab('PopTetriDoc', GPath.UIPrefab, GBundle.tetriDoc),
]);
}
/** @deprecated 请用 {@link preloadBattlePopupAssets} */
static async preloadBattleSettlementAssets(): Promise<void> {
return this.preloadBattlePopupAssets();
}
/** Boss 副本投掷物 / 爆炸特效(同步 getNode 前需已 ensure) */
static async preloadBossSkillAssets(): Promise<void> {
if (!gg?.res) return;
await this.loadBundle(GBundle.BattleGameWeapon);
await Promise.all([
gg.res.ensurePrefab('bossWeapon1', 'prefab/武器/', GBundle.BattleGameWeapon),
gg.res.ensurePrefab('砖块爆炸', 'prefab/爆炸/', GBundle.BattleGameWeapon),
]);
}
/** 救救我鸭:木板 / 螺丝(同步 getNode 生成,未预载则首帧空) */
static async preloadJiujiuwoyaCardAssets(): Promise<void> {
if (!gg?.res) return;
await this.ensureBundles(JIUJIUWOYA_BUNDLE_GROUP);
const tasks: Promise<unknown>[] = [
gg.res.ensurePrefab('螺丝', 'prefab/', GBundle.jiujiuwoyaCard),
gg.res.ensurePrefab('jiujiuwoyaGame', 'prefab/', GBundle.jiujiuwoya),
];
for (let i = 0; i < this.JIUJIUWOYA_MUBAN_PREFABS.length; i++) {
tasks.push(gg.res.ensurePrefab(this.JIUJIUWOYA_MUBAN_PREFABS[i], 'prefab/', GBundle.jiujiuwoyaCard));
}
await Promise.all(tasks);
}
private static readonly JIUJIUWOYA_MUBAN_PREFABS: string[] = [
'木板1_2', '木板2_2', '木板3_2', '木板4_2', '木板5_2', '木板6_1', '木板7_3', '木板8_2',
];
/** 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 || '';
}
}