Загрузчик поверх Assets из Pixi. URL решает приложение — движок получает функцию-резолвер:
import { AssetLoader } from '@rpg/engine';
const assets = new AssetLoader((key) => `assets/${key}.png`);
await assets.load(['tiles/grass', 'chars/hero_down_1'], (p) => setProgressBar(p));
assets.texture('tiles/grass'); // Texture; бросает, если не загружено
assets.has('tiles/water');
Грабли: Assets.load без предварительного Assets.init({}) висит навсегда без ошибок в консоли. AssetLoader делает ensureInit() сам.
// Spritesheet JSON должен ссылаться на PNG относительным путём — Pixi загрузит сам
const sheet = await assets.loadAtlas('chars/hero_sheet');
assets.frames('chars/hero_sheet', 'hero_walk'); // кадры hero_walk_1, hero_walk_2, ...
assets.animation('chars/hero_sheet', 'walk'); // готовая анимация из sheet.animations
frames() сортирует кадры по числу в конце имени. Пример генерации атласа — в art-pipeline.md.
import { FrameAnimation } from '@rpg/engine';
const sprite = new Sprite(frames[0]);
const anim = new FrameAnimation(sprite, frames, 8 /* fps */, true /* loop */);
anim.update(dt); // каждый тик
anim.setFrames(otherFrames, true); // сменить кадры (поворот, состояние)
Три шины (master/music/sfx), кроссфейд музыки, кэш сэмплов:
import { AudioManager } from '@rpg/engine';
const audio = new AudioManager((key) => `audio/${key}.mp3`);
// Расблокировать из обработчика пользовательского ввода (требование браузеров):
uiButton.onSelect = () => void audio.unlock();
await audio.load('bell');
await audio.play('bell', 0.8); // sfx
await audio.playMusic('meadows_theme', { fade: 2 }); // зациклится с нарастанием 2 сек
audio.stopMusic(1); // затухание 1 сек
audio.setBusVolume('music', 0.6); // громкость шины
Связка с Settings (громкости применяются при изменении):
settings.onChange((s) => {
audio.setBusVolume('master', s.master);
audio.setBusVolume('music', s.music);
audio.setBusVolume('sfx', s.sfx);
});
play/playMusic не падают, если звук не загружен или AudioContext не разблокирован — тихо ничего не делают.
JSON-слоты в Storage-подобном хранилище (localStorage в браузере, Map в тестах):
import { SaveManager } from '@rpg/engine';
const saves = new SaveManager(localStorage);
saves.save('autosave', { pos: { x: 5, y: 5 }, state: gameState.serialize() });
const data = saves.load<typeof myData>('autosave');
saves.has('autosave');
saves.delete('autosave');
Что класть в сейв: позицию/прогресс игры + gameState.serialize(). Настройки — отдельно через Settings (слот settings), в сейв они не попадают.
interface SaveData {
pos: { x: number; y: number };
state: GameStateData; // из gameState.serialize()
savedAt: number; // Date.now()
}
// Загрузка:
const data = saves.load<SaveData>('autosave');
if (data) gameState.load(data.state);
Версионирование контента — через GameState.dataVersion + addMigration: при изменении формата флагов старые сейвы чинятся автоматически при загрузке.