import { Container, Graphics, PixelText, ensurePixelFont, type Scene } from '@rpg/engine';
import { Game } from '../Game';
import { AREAS, areaOf } from '../data/locations';
import { AMBIENCE_KEYS, AMBIENCE_LAYER_KEYS, areaAudioKeys } from '../data/audio';
import { MenuScene } from './MenuScene';
/** Атласы персонажей (Spritesheet JSON + PNG). */
const ATLAS_KEYS = ['chars/hero_sheet.json', 'chars/clumps_sheet.json', 'chars/fauna_sheet.json', 'chars/npcs_sheet.json'];
/**
* Загрузочная сцена: грузит ассеты с прогресс-баром, затем меню.
*/
export class BootScene implements Scene {
private view = new Container();
private bar: Graphics;
private started = false;
private progress = -1;
constructor(private game: Game) {
const label = new PixelText({
text: 'пепел оседает...',
size: 11,
color: 0x8a8a9a
});
label.anchor.set(0.5);
label.position.set(240, 120);
this.bar = new Graphics();
this.view.addChild(label, this.bar);
this.game.renderer.uiRoot.addChild(this.view);
}
enter(): void {}
exit(): void {
this.view.destroy({ children: true });
}
update(_dt: number): void {
if (!this.started) {
this.started = true;
void this.loadAll();
}
if (this.progress >= 0) {
this.drawBar(this.progress);
this.progress = -1; // перерисовать один раз на изменение
}
}
/**
* Параллельная загрузка всех групп с общим прогресс-баром: текстуры (0.6),
* атласы (0.15), карты (0.15), шрифт + амбиент стартовой области (0.1).
* Остальные лупы амбиента декодируются лениво при входе в область
* (LocationScene.enter -> game.preloadAudio) — до меню их ждать не нужно.
*/
private async loadAll(): Promise<void> {
const areaIds = Object.keys(AREAS);
let texP = 0;
let atlasP = 0;
let mapP = 0;
const setProgress = () => {
this.progress = texP * 0.6 + atlasP * 0.15 + mapP * 0.15;
};
try {
await Promise.all([
this.game.assets.load(Game.ASSET_KEYS, (p) => {
texP = p;
setProgress();
}),
...ATLAS_KEYS.map((key) =>
this.game.assets.loadAtlas(key).then(() => {
atlasP += 1 / ATLAS_KEYS.length;
setProgress();
})
),
...areaIds.map((id) =>
this.game.loadMap(id).then(() => {
mapP += 1 / areaIds.length;
setProgress();
})
),
ensurePixelFont(Game.FONT_URL),
(async () => {
// Регистрация ключей дешёвая (URL в карту); декодируем только
// лупы стартовой области, чтобы амбиент сразу не тормозил.
for (const key of [...AMBIENCE_KEYS, ...AMBIENCE_LAYER_KEYS]) {
this.game.audio.register(key);
}
await Promise.all(areaAudioKeys(areaOf(undefined)).map((k) => this.game.audio.preload(k)));
})()
]);
this.progress = 1;
console.log('[boot] ассеты загружены, меню...'); // маяк для смоук-тестов
void this.game.scenes.replace(new MenuScene(this.game), { duration: 0.3 });
} catch (err) {
console.error('[boot] ошибка загрузки:', err);
}
}
private drawBar(p: number): void {
this.bar.clear();
this.bar.rect(180, 136, 120, 6).fill(0x23232b);
this.bar.rect(180, 136, Math.round(120 * p), 6).fill(0xd99a32);
}
render(): void {}
}