Files
shooting-word/ecs-refactoring.md
T

7.0 KiB
Raw Blame History

Архитектура (ECS на miniplex)

Проект переписан на TypeScript + PixiJS с ECS-ядром на miniplex. Этот документ — описание того, как всё устроено сейчас.

Слои

ввод (InputSource) → шина событий → симуляция (мир + системы) → шина событий → рендер (Pixi) и DOM-UI

Каждый слой знает только соседей через интерфейсы: логика не знает про Pixi и DOM, рендер не содержит игровой логики, ввод не знает про игру — только команды.

Сущности и компоненты (src/ecs)

Сущность — объект с опциональными компонентами; «есть компонент» = свойство не undefined.

Компонент Смысл
position, velocity, damping Движение (враги, снаряды, частицы — одна система интеграции)
enemyKind, enemyBox Тип (simple/heavy/boss) и габариты рамки
armor Слои слов: текущий слой отображается и уязвим только он
projectile, player, target / targetPoint, fuse Снаряд: слово, кто выстрелил, цель (или точка промаха), задержка выстрела
lifetime, particle Частицы — полноценные сущности, а не особый массив
waveState Синглтон-сущность с состоянием волны и баннером

Броня единая для всех типов: simple — 1 слой из 1 слова, heavy — 2 слоя по 1 слову (порядок строгий), boss — 3 слоя по 3 слова (внутри слоя порядок свободный). Урон снимает только текущий слой; после каждого попадания вью перерисовывается по диффу layerIndex:killed.

Системы (порядок за кадр, src/systems)

  1. fuseSystem — снаряд стоит у стрелка, пока не истечёт задержка ввода (150 мс), потом получает скорость.
  2. homingSystem — наведение на цель, касание → событие projectile:hit; цель исчезла — снаряд удаляется.
  3. missSystem — промах долетает до точки → projectile:miss.
  4. waveSystem — таймеры волны, спавн, пауза, боссы; пишет в синглтон waveState.
  5. integrationSystem — единое движение для всех сущностей с position+velocity (и damping).
  6. cullSystem — снаряды за экраном.
  7. escapeSystem — враг дошёл до низа → enemy:escaped → урон.
  8. lifetimeSystem — затухание частиц.

Simulation (src/game) задаёт порядок систем, принимает команды ввода (поиск цели по слову), обрабатывает попадания/промахи/урон и держит скаляры партии (счёт, жизни, фаза, тряска) в GameStore — по договорённости это не сущности.

Ввод (src/input) — отдельный интерфейс

Игра зависит только от порта InputSource, а не от библиотеки:

interface InputSource {
  readonly origin: "local" | "chat";
  start(): void;
  stop(): void;
  onCommand(handler: (command: InputCommand) => void): () => void;
}

type InputCommand =
  | { kind: "start" } // !play, награда за баллы канала и т.п.
  | { kind: "clear" }
  | { kind: "word"; word: string; player: Player };

Сейчас реализованы TwitchChatInputSource (tmi.js, парсит !play и первое слово сообщения) и LocalKeyboardInputSource (тест без Twitch). Будущий переход на Twurple и запуск через баллы канала = новый источник, который эмитит { kind: 'start' } на событие награды; InputRouter просто регистрирует его рядом с остальными.

Рендер (src/render)

  • GridLayer — сетка одним TilingSprite (вместо ~40 stroke() за кадр).
  • ParticleLayer — все частицы в одном ParticleContainer (один draw call, пул объектов).
  • Свечение — заранее запечённая радиальная текстура с аддитивным блендингом вместо попиксельного shadowBlur.
  • Вью врагов/снарядов создаются и уничтожаются по сигналам мира (world.onEntityAdded/onEntityRemoved), каждый кадр только синхронизируют позицию/альфу.
  • Тряска экрана — смещение корневого контейнера, состояние в GameStore.
  • Цикл: внешний GameClock (rAF, delta в 60-кадрах), pixi-ticker остановлен; в простое (интро закончилось, партия не идёт) рендер вообще не тикает — canvas хранит последний кадр. Сбой одного кадра не убивает цикл.

События (src/game/events.ts)

Типизированная шина EventBus: ввод, бой (projectile:hit/miss, enemy:*) и состояние партии (game:started/over, score/wave/lives, effect:glitch). Рендер и DOM-UI — только подписчики.

DOM-UI (src/ui)

HUD, экраны старта/поражения, таблица зрителей (имена вставляются через textContent — ник из чата не должен стать разметкой), фидбек ввода, масштабирование оверлея под окно.

Отладка

В консоли браузера доступен хук window.__sw — мир, стор, шина, метрики и рендерер (__sw.world.with('armor').entities, __sw.store.score и т.д.).