Newer
Older
rpg / docs / engine / core.md

Ядро: Engine, GameLoop, EventBus, Tween, GameState, StateMachine, Settings

Engine

Собирает все подсистемы и владеет игровым циклом:

const engine = new Engine({ virtualWidth: 480, virtualHeight: 270, scale: 3, background: 0x0b0b12, fixedFps: 60, parent });
await engine.init();   // WebGL, ввод, оверлей fade-переходов
await engine.start();  // запуск цикла

Доступные подсистемы: engine.renderer, engine.camera, engine.scenes, engine.input, engine.tweens, engine.events.

Опции: autoResize (по умолчанию true) — пересчитывать целочисленный масштаб при ресайзе окна.

GameLoop (fixed timestep)

Логика — фиксированный шаг 60 Гц; рендер после каждого шага. dt в Scene.update(dt) всегда 1/60. После лага («вкладка спала») выполняется максимум 5 шагов за кадр — игра замедляется, а не «телепортируется».

Вручную цикл обычно не трогают; Engine.tick делает по порядку: input.update() (опрос геймпада) → tweens.update(dt)scenes.update(dt)input.endTick().

EventBus

Типизированные по темам события — связь систем без прямых зависимостей:

engine.events.on<{ gold: number }>('loot:picked', ({ gold }) => console.log(gold));
const off = engine.events.on('quest:done', () => { ... });
engine.events.emit('loot:picked', { gold: 5 });
off(); // отписка

Tween (твины и таймеры)

Обновляется циклом автоматически. Все методы возвращают handle с cancel().

engine.tweens.to(sprite, { x: 100, alpha: 1 }, {
    duration: 0.5,
    ease: cubicOut,      // linear, quadIn/Out, cubicIn/Out, sineInOut
    delay: 0.2,          // стартовые значения снимаются ПОСЛЕ delay
    onDone: () => console.log('готово')
});

engine.tweens.delay(1.5, () => spawnEnemy()); // таймер, сек
engine.tweens.cancelFor(sprite);              // отменить всё для объекта

Гарантии: значения не выходят за цель (зажим на последнем кадре), onDone вызывается один раз, тик, пересекающий конец delay, не «съедает» часть движения.

GameState

Состояние прохождения: флаги и именованные переменные. Жанронезависимо — какие флаги есть, решает игра. Сериализуется в сейв (GameStateData).

const state = new GameState();
state.setFlag('met_elder');
state.setVar('gold', 30);
state.getNumber('gold');      // 30 (getString/getBool — аналогично)
state.hasFlag('met_elder');   // true

// Сейв/загрузка + миграции при изменении формата:
state.dataVersion = 2;
state.addMigration((d) => ({ ...d, version: 2, vars: { ...d.vars, gold: 0 } }));
const data = state.serialize();       // положить в сейв
state.load(data);                     // миграции применятся автоматически

StateMachine

Конечный автомат для состояний сущностей (idle/walk/attack), AI и т.п.:

const sm = new StateMachine((state, event) => console.warn(`${state} не знает событие ${event}`));
sm.add('idle', { enter, exit, update: (dt) => {...} });
sm.add('chase', { enter, exit, update });
sm.transition('idle', 'seePlayer', 'chase');
sm.transition('chase', 'losePlayer', 'idle');

sm.change('idle');            // exit старого -> enter нового
sm.handleEvent('seePlayer');  // переход по таблице
sm.update(dt);                // тикает текущее состояние; sm.time — время в состоянии

Переход «из любого состояния» — для событий вроде hurt/die, чтобы не регистрировать их из каждого состояния (обычные переходы имеют приоритет):

sm.transitionAny('die', 'dead');

Cooldown

Кулдаун на фиксированном шаге (секунды, без performance.now()):

const cd = new Cooldown(0.45);
cd.trigger();          // запустить (false, если ещё не готов)
cd.triggerForce();     // перезапустить принудительно
cd.update(dt);         // тикать каждый шаг
cd.ready;              // готов?
cd.timeRemaining;      // секунд до готовности
cd.progress;           // 1..0 — удобно для полоски перезарядки

Settings

Настройки игрока (громкости, язык, прочее) в отдельном слоте хранилища — не попадают в игровые сейвы:

const settings = new Settings(localStorage, 'settings'); // или любой StorageLike
settings.load();

settings.update({ master: 0.8, music: 0.6 });
settings.data.master;           // 0.8
settings.update({ extra: { hintSeen: true } });
settings.getExtra('hintSeen');

const off = settings.onChange((s) => audio.setBusVolume('master', s.master));

Хранилище может быть недоступно (приватный режим) — Settings работает в памяти, не падает.