Статус: Ready


Утверждённый технологический стек и архитектурные решения проекта. Основано на анализе концепции, дизайн-доков и разборе предыдущего проекта автора (FewSeconds-ManyDeaths-Unity) — см. раздел «Уроки прошлого проекта».


Контекст решений

ПараметрЗначениеВлияние на стек
ЖанрКо-оп рогалик-автобатлер 1–4 игрокаHost-authoritative сеть, без античита
Модель бояРеальное время + пауза (не пошагово)Тиковая симуляция с фиксированным шагом
Поле бояНепрерывное 2D (не клеточное)Своя детерминированная математика, без Unity-физики
ПлатформаSteam (PC), глобальный релизSteamworks, локализация, Steam Cloud
МультиплеерКо-оп; PvP «с друзьями» возможен позжеДетерминируемая симуляция, но без fixed-point/lockstep сейчас
КомандаСоло-код; арт/UX — заказ; музыка — Suno (платный тир!)Минимум кастомной инфраструктуры, максимум готовых решений
ЦельВертикальный срез к фестивалю «Играм быть» (окт. 2026) на правильной архитектуреТяжёлые системы — каркасом сейчас, наполнение позже

Технологический стек (фазированно)

Фаза 1 — Ядро (закладываем сейчас)

ПакетНазначение
VContainerDI-контейнер. Композиционные корни: bootstrap → забег → бой. Замена синглтонам.
UniTaskZero-alloc async/await: тиковый цикл боя, кулдауны, загрузки.
MessagePipeТипизированный pub/sub EventBus через DI. Замена глобальному GlobalEventSystem.
LitMotionZero-alloc твины: UI, HP-бары, damage numbers, фидбэк.
Odin InspectorРедакторы SO-контента, валидация, дропдауны для [SerializeReference]-поведения.
Newtonsoft.JsonСериализация DTO сейвов (через Easy Save).
Multiplayer Play Mode (MPPM)Тест ко-опа в редакторе (до 4 виртуальных игроков). Замена ParrelSync для Unity 6.

Фаза 2 — Контент (стадии 2–3 роадмапа)

ПакетНазначениеПримечание
AddressablesЗагрузка контента, основа для локализации, DLC после фестиваляВводить, когда контента станет много
Unity LocalizationЛокализация (мин. EN + RU)Ключи закладывать в SO сразу; пакет — на этой фазе
Easy Save 3Плумбинг сохранений на диск/Steam CloudАрхитектуру DTO продумываем сами
Netcode for GameObjects (NGO)Сетевой слойКаркас раньше, включение фичи — стадия 3
Facepunch.Steamworks + Facepunch transportSteam-интеграция и транспорт (relay/NAT бесплатно)Автор уже работал с этой связкой
R3 (опционально)Реактивные биндинги UI, если MVVM начнёт болетьПреемник UniRx (UniRx — легаси, не брать)

Фаза 3 — Полировка (стадия 4)

ПакетНазначениеПримечание
FMODАдаптивная боевая музыка (слои по интенсивности)Усложнит GameCI — заложить время. До этого — Unity Audio за интерфейсом IAudioService

Отвергнутые варианты: Zenject (тяжелее VContainer), Mirror/FishNet (NGO достаточно для ко-опа и уже знаком автору), UniRx (легаси → R3), DOTween (LitMotion новее и zero-alloc), Steamworks.NET (Facepunch удобнее), ParrelSync (MPPM в Unity 6).


Обоснование ключевых решений

РешениеПочему
NGO (не Mirror/FishNet)Ко-оп host-authoritative; автор уже работал с NGO + Facepunch в прошлом проекте. FishNet мощнее (rollback/prediction), но это для соревновательного PvP — у нас PvP «с друзьями», без античита.
MPPM (не ParrelSync)Unity 6 даёт Multiplayer Play Mode (виртуальные игроки в редакторе); ParrelSync (клон папки) стал легаси-обходом.
R3 (не UniRx)UniRx — легаси; R3 — официальный преемник (Cysharp). Брать точечно для реактивных биндингов UI.
Easy Save 3 (не ручной JSON)Соло-разработчику убирает boilerplate сериализации/шифрования. Формат сейва (DTO + строковые ID) проектируем сами.
UI Toolkit + MVVMБиндинги UI Toolkit созданы под MVVM; гигантский конфиг-UI AI-редактора требует дизайн-системы. uGUI-гибрид — для боевого world-space HUD (бары, damage numbers).
LitMotion (не DOTween)Новее, zero-alloc, из той же экосистемы (Cysharp).

MVP vs MVVM (разница): в MVP связь View↔данные обновляется вручную через Presenter (проще, привычнее в Unity, хорош для uGUI/world-space). В MVVM View подписан на наблюдаемые свойства ViewModel и обновляется сам (нужны биндинги — UI Toolkit или R3). Выбран MVVM на UI Toolkit; MVP-стиль уместен для uGUI-HUD.

Ключевой принцип «один механизм — три фичи»: детерминированная симуляция + лог команд + сид дают сразу реплеи, сравнение AI-профилей и синхронный мультиплеер. Поэтому детерминизм — несущая стена, а не страховка под мультиплеер.

UI-воркфлоу: связка Figma → UI Toolkit (UXML/USS) рабочая (есть Figma MCP + Code Connect); под AI-редактор обязательна дизайн-система (переиспользуемые компоненты: Filter-чип, Score-слайдер с min/max, список целей).

Референсы механик: Gladiabots и FF12 Gambits (программируемое поведение юнитов), Teamfight Manager (читаемый автобой), Skul (реликвии-черепа), Slay the Spire (структура карты), Across the Obelisk / BG3 (ко-оп-распределение).


Архитектурные принципы

  1. Разделение симуляции и презентации. Боевая логика (Combat) — чистые C#-данные/правила, без MonoBehaviour внутри и без обращений к UI. Презентация (спрайты, бары, числа) только читает состояние и реагирует на события.
  2. Инверсия зависимостей через DI. Никаких static instance-синглтонов и Find*ObjectOfType в рантайме. Зависимости инъектируются VContainer’ом.
  3. Детерминизм. Весь рандом — через инъектируемый IRngService (seeded). Глобальный UnityEngine.Random запрещён в игровой логике.
  4. Команды как единица действия. Действия игрока = команды. Это одновременно даёт replay, воспроизводимость по сиду и синхронизацию в мультиплеере.
  5. SO — иммутабельные определения, рантайм-стейт — в POCO. SO никогда не мутируется в рантайме.
  6. Границы сборок. Зависимости только вниз по графу (см. 1. Сборки (Assembly Definitions)).

Паттерны

ПаттернГде применяем
DI (VContainer)Везде вместо синглтонов. Композиционные корни на bootstrap/забег/бой.
State MachineФлоу игры (Карта → Подготовка → Бой → Награды) и состояние боя/юнита.
CommandДействия игрока, replay, синхронизация по сети, воспроизводимость по сиду.
EventBus (MessagePipe)Развязка Combat → UI/VFX/Audio. Подписки с дисциплиной отписки (через DI lifetime).
Object PoolUnityEngine.Pool.ObjectPool<T> для damage numbers, снарядов, VFX.
Strategy / [SerializeReference]Полиморфное поведение эффектов и способностей (см. ниже).
MVVMUI на UI Toolkit (см. ниже).

Боевая симуляция

  • Поле боя — непрерывное 2D (не клеточное). Дальности, AOE, снаряды — в мировых координатах (радиусы, точки, swept-рейкасты). Размер юнита влияет на дальность атаки.
  • Тиковая симуляция с фиксированным шагом ≈ 30 Гц (dt ≈ 0.0333с), отделена от частоты кадров. Снаряды — swept-проверкой (отрезок пути за тик), чтобы быстрые снаряды не «протыкали» цель между тиками.
  • Чистая C#-логика в сборке Combat, управляется потоком команд.
  • Никакой Unity-физики (Physics2D / Rigidbody2D) в симуляции — она недетерминирована (зависит от кадров, решателя, версии). Движение и попадания — ручная математика (интегрирование, circle-circle, рейкаст). Коллайдеры допустимы только в презентации (наведение курсора, хитбоксы для кликов).
  • IRngService (seeded, System.Random / xorshift) инъектируется в симуляцию. Все криты, лут, генерация — через него. Глобальный UnityEngine.Random запрещён.
  • Спатиальные запросы («кто в радиусе X», точки AOE) ускоряются внутренним spatial hash / uniform grid поверх непрерывных координат — поле остаётся непрерывным, сетка только ради перфоманса. AOE-точки сначала ищутся на юнитах и серединах между парами целей, а не сплошным перебором.

Чек-лист детерминизма

Симуляция детерминирована на одной машине (→ реплеи, сравнение AI-профилей) и при host-authoritative (→ мультиплеер), если:

  • фиксированный dt, без Time.deltaTime в логике;
  • весь рандом через IRngService;
  • детерминированный порядок итерации (не foreach по Dictionary / HashSet без сортировки);
  • ноль Unity-физики и ноль зависимости от порядка кадров / корутин.

Float ок: на одной машине и при host-authoritative float-математика детерминирована при соблюдении чек-листа. Fixed-point понадобился бы только для lockstep / кросс-машинной воспроизводимости в соревновательном PvP — не наш случай.

🔴 Первый рефактор: DamageCalculator.RollCrit использует UnityEngine.Random.value — заменить на IRngService с EditMode-тестом на детерминизм. Заодно UnitData подразумевает сетку («дальность в клетках») — пересмотреть под непрерывное поле (радиус, float).


AI «Сосудов» (Filter / Score / Override)

Поведение юнитов в бою — основной слой реиграбельности (профили, шаринг, сравнение до 4 штук, Workshop — сырые заметки в GDD 0.3. Черновик). Архитектура интерфейс-ориентированная — каждый интерфейс это реальная точка полиморфной вариативности, тестируемая и сериализуемая:

  • ITargetFilter — кого вообще можно рассматривать (живой, враг, в радиусе, без эффекта X, из списка).
  • ITargetScorer — насколько цель хороша (ближе / ниже HP / ниже броня / +за цель из списка), с min/max-клампом.
  • IOverrideRule — что перебивает обычный выбор (self HP < 10% → отступать; союзник < 15% → хилить; добить < 5%).
  • IPositioningMode — режим передвижения (к цели, стоять, держаться у союзника с гистерезисом, смещение поддержки к лоу-HP).
  • IAIAction — действие (бить / бежать / каст 1–2 / стоять).

AI-профиль = сериализуемые ДАННЫЕ, а не код: набор этих кирпичиков, собранный игроком в редакторе. Это даёт шаринг файлом / через Workshop, честное сравнение профилей и сохранение между забегами. При старте боя профиль «компилируется» один раз в объекты-стратегии (кэш), а не парсится каждый тик.

Исполнение на AI-тике: Override → Filter → Score → Action/Positioning → формируется AIIntent → команда в общий лог. AIContext (срез боя: я, союзники, враги, спатиальный индекс) передаётся in-параметром, без аллокаций.

Частоты: симуляция 30 Гц; переоценка AI 10 Гц базово, со stagger (юниты размазаны по окну, без пика CPU) и событийными прерываниями (цель умерла / вышла из фильтра / крупный урон / сработал Override → немедленная переоценка).

Объём к фестивалю уточняется: движок Filter/Score/Override — полноценный сразу; полный редактор-конструктор (classic/extra-режимы), шаринг, сравнение, полигон, Workshop — могут переехать на post-release («не горит»).

Дисциплина интерфейсов: интерфейс ставим там, где есть несколько реализаций или граница для теста/мока/подмены. Не плодим IFooFoo 1:1 — это засоряет. Интерфейсы на швах, конкретные типы внутри.


Моделирование контента (реликвии, способности, эффекты)

Подход: «данные в SO + поведение в коде».

  • SO = только данные-определения (иммутабельный контент): цифры, ссылки, ключи локализации, список применяемых эффектов.
  • Поведение = полиморфные plain-C# классы через [SerializeReference] (IEffect, ISkillBehavior), вложенные в SO. Odin даёт удобный выбор конкретного типа в инспекторе.
  • Логику исполняет боевая симуляция, передавая эффектам IRngService. Эффект сам по себе не дёргает UnityEngine.Random.
  • Рантайм-состояние — в POCO (RuntimeUnit, RuntimeEffect). SO не мутируется и не клонируется как стейт.

Это сознательный уход от прошлого подхода «один SO/скрипт с логикой на каждый скилл», который вёл к взрыву классов и протеканию рантайм-данных в SO.


UI

  • Основа: UI Toolkit + MVVM. ViewModel держит наблюдаемые свойства, View подписан через биндинги.
  • Гибрид: для боевого HUD и world-space элементов (бары над юнитами, damage numbers), где UI Toolkit неудобен, допускается uGUI.
  • При усложнении биндингов — точечно подключить R3.

Мультиплеер

  • Модель: host-authoritative. Хост — источник истины, клиенты шлют команды-интенты. Античит не нужен (ко-оп / PvP «с друзьями»); приоритет — комфорт игроков.
  • Сеть: NGO + Facepunch transport. Steam relay/NAT-punch бесплатно, выделенный сервер не нужен.
  • Тестирование: MPPM (виртуальные игроки в редакторе).
  • Host migration — прагматичный вариант (не бесшовный): при выходе хоста сессия завершается, последний автосейв расшарен (Steam Cloud), другой игрок перехостит и загрузит с последней точки. Опирается на модель сейвов (см. 2. Сохранения). Бесшовная runtime-миграция — post-release стретч, не к фестивалю.
  • Реализация — последней (стадия 3), но код пишем «multiplayer-ready» с первого дня (симуляция отделена, RNG инъектируем, действия — команды).

Сохранения

  • Easy Save 3 для плумбинга (диск, шифрование, Unity-типы).
  • Свой слой DTO (RunSaveData, UnitSaveData): контент хранится по строковым ID ("relic_flame_sword"), а не ссылками на SO.
  • Steam Cloud — Auto-Cloud по маске файлов в Steamworks-партнёрке. Фиксированный путь сейвов задать с самого начала.
  • Детали и точки автосейва — в 2. Сохранения.

Локализация

  • Unity Localization — выбранное решение (мин. EN + RU).
  • Сейчас (бесплатно по усилиям): в SO реликвий/перков/событий закладывать строковые ключи, а не хардкод-текст.
  • Пакет интегрировать на стадии 2–3 (тянет Addressables, которые всё равно нужны).

Уроки прошлого проекта (FewSeconds-ManyDeaths)

Метрики по 191 файлу _Project и принятые контрмеры:

Проблема в прошлом проектеМасштабРешение в Guildmaster
Нет Assembly Definitions0 .asmdef (монолит)Граф сборок Core←Units←Combat/Guild←UI уже есть ✅
Синглтоны18 классов, 480 обращений к .instanceDI (VContainer)
Find*ObjectOfType87 вызовов в 54 файлах, в т.ч. в геттерахИнъекция зависимостей, явные ссылки
Глобальный UnityEngine.Random17 вызовов; SessionSeedRandom.InitState глобальноIRngService (seeded, инъектируемый)
God-object стейтCombatPlayerDataInSession: параллельные массивы, SO.CreateInstance для рантаймаPOCO рантайм-стейт, SO иммутабельны
God-object EventBusGlobalEventSystem: 60+ статических UnityEventMessagePipe через DI с дисциплиной отписки
Логика в SO/скриптах на каждый скиллвзрыв классов (SkillScript 186 стр. и т.п.)Данные в SO + поведение через [SerializeReference]

Главный вывод: прошлые проблемы шли не от незнания C#, а от отсутствия границ (asmdef) и инверсии зависимостей (DI). Оба пункта закрыты в фундаменте.


Дорожная карта внедрения

Все пакеты установлены (май 2026). Активация фич — по мере готовности систем.

ПакетСтатусСледующий шаг
VContainer✅ УстановленНаписать первый LifetimeScope (bootstrap)
UniTask✅ УстановленЗаменить корутины при написании тиковой симуляции
MessagePipe✅ УстановленЗарегистрировать в LifetimeScope; заменить будущий EventBus
LitMotion✅ УстановленПодключать при реализации UI-фидбэка
Odin Inspector✅ УстановленИспользовать для SO-контента и [SerializeReference]-поведений
Newtonsoft.Json✅ УстановленПодключать при проектировании DTO сейвов
MPPM✅ УстановленНастроить виртуальные игроки для кооп-тестирования
Addressables✅ УстановленВводить когда контента станет много (стадия 2–3)
Unity Localization✅ УстановленКлючи в SO закладывать сразу; таблицы — стадия 2–3
Easy Save 3✅ УстановленСпроектировать DTO-слой (RunSaveData, UnitSaveData)
NGO✅ УстановленКаркас сети — стадия 3; код писать multiplayer-ready с первого дня
Facepunch.Steamworks✅ Установлен (DLL)Инициализировать при добавлении Steam-фич
FMOD✅ УстановленЗа интерфейсом IAudioService. Подключать на стадии 4
R3 (опционально)⏳ Не установленДобавить если MVVM-биндинги начнут болеть

Приоритеты к фестивалю «Играм быть» (окт. 2026):

  1. Рефактор DamageCalculator на IRngService
  2. Каркас тиковой симуляции + Command-слой (VContainer + UniTask + MessagePipe)
  3. Базовый UI (LitMotion + Odin)
  4. Кооп-тест через MPPM
  5. Steam + сохранения — стадия 3

Открытые вопросы

  • Suno: платный тиррешено: берётся платный тир (коммерческое использование). Раскрыть AI-контент в анкете Steam при публикации.
  • Частота тиковрешено: симуляция 30 Гц, AI 10 Гц (stagger + событийные прерывания).
  • Нужен ли R3 на старте UI или достаточно ручных биндингов UI Toolkit — решить по факту боли.
  • Объём AI-редактора к фестивалю (движок vs полный конструктор) — уточняется в процессе; допустим перенос на post-release.
  • Точная модель стат-системы (~30 статов из 0.3. Черновик: слои base/flat/percent, источники модификаторов) — спроектировать перед боевым кодом.