Собирает все подсистемы и владеет игровым циклом:
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) — пересчитывать целочисленный масштаб при ресайзе окна.
Логика — фиксированный шаг 60 Гц; рендер после каждого шага. dt в Scene.update(dt) всегда 1/60. После лага («вкладка спала») выполняется максимум 5 шагов за кадр — игра замедляется, а не «телепортируется».
Вручную цикл обычно не трогают; Engine.tick делает по порядку: input.update() (опрос геймпада) → tweens.update(dt) → scenes.update(dt) → input.endTick().
Типизированные по темам события — связь систем без прямых зависимостей:
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(); // отписка
Обновляется циклом автоматически. Все методы возвращают 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, не «съедает» часть движения.
Состояние прохождения: флаги и именованные переменные. Жанронезависимо — какие флаги есть, решает игра. Сериализуется в сейв (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); // миграции применятся автоматически
Конечный автомат для состояний сущностей (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 — время в состоянии
Настройки игрока (громкости, язык, прочее) в отдельном слоте хранилища — не попадают в игровые сейвы:
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 работает в памяти, не падает.