import { Assets, Spritesheet, Texture } from 'pixi.js';
/**
* Загрузчик ассетов поверх Assets из PixiJS: группы, прогресс, готовые Texture.
* URL ассетов передаётся функцией-резолвером (приложение решает, где лежат файлы).
* Атласы (Spritesheet JSON + PNG) загружаются тем же механизмом: loadAtlas().
*/
export class AssetLoader {
private textures = new Map<string, Texture>();
private atlases = new Map<string, Spritesheet>();
private initPromise: Promise<void> | null = null;
constructor(private resolveUrl: (key: string) => string) {}
/** Assets требует однократной инициализации до первой загрузки. */
private ensureInit(): Promise<void> {
if (!this.initPromise) {
this.initPromise = Assets.init({});
}
return this.initPromise;
}
/** Загрузить группу ключей; onProgress получает долю 0..1. */
async load(keys: string[], onProgress?: (p: number) => void): Promise<void> {
await this.ensureInit();
let done = 0;
for (const key of keys) {
try {
const tex = await Assets.load<Texture>(this.resolveUrl(key));
this.textures.set(key, tex);
} catch (err) {
throw new Error(`Не удалось загрузить ${this.resolveUrl(key)}: ${String(err)}`);
}
done++;
onProgress?.(done / keys.length);
}
}
/**
* Загрузить атлас (Spritesheet JSON). JSON должен ссылаться на PNG
* относительным путём — Pixi подгрузит картинку сам.
*/
async loadAtlas(key: string): Promise<Spritesheet> {
await this.ensureInit();
const existing = this.atlases.get(key);
if (existing) return existing;
const sheet = await Assets.load<Spritesheet>(this.resolveUrl(key));
this.atlases.set(key, sheet);
return sheet;
}
/**
* Кадры анимации из загруженного атласа: все кадры, чьё имя начинается
* с prefix, отсортированные по числу в конце имени (hero_walk_1, hero_walk_2, ...).
*/
frames(key: string, prefix: string): Texture[] {
const sheet = this.atlases.get(key);
if (!sheet) throw new Error(`Атлас не загружен: ${key}`);
const entries = Object.keys(sheet.textures)
.filter((name) => name.startsWith(prefix))
.sort((a, b) => {
const na = Number(a.match(/(\d+)$/)?.[1] ?? -1);
const nb = Number(b.match(/(\d+)$/)?.[1] ?? -1);
return na - nb;
});
if (entries.length === 0) {
throw new Error(`В атласе ${key} нет кадров с префиксом ${prefix}`);
}
return entries.map((name) => sheet.textures[name]!);
}
/** Готовая анимация из sheet.animations (если генератор заполняет animations). */
animation(key: string, name: string): Texture[] {
const sheet = this.atlases.get(key);
if (!sheet) throw new Error(`Атлас не загружен: ${key}`);
const frames = sheet.animations[name];
if (!frames) throw new Error(`В атласе ${key} нет анимации ${name}`);
return frames;
}
texture(key: string): Texture {
const t = this.textures.get(key);
if (!t) throw new Error(`Текстура не загружена: ${key}`);
return t;
}
has(key: string): boolean {
return this.textures.has(key);
}
}