Newer
Older
rpg / apps / game / src / scenes / BootScene.ts
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 {}
}