Статус: Ready — отражает код на 2026-06-19
Это гайд по фактическому коду проекта (а не план, как доки «10/12»). Цель — чтобы ты понимал каждое архитектурное решение: что за что отвечает, почему выбран один паттерн и не выбран другой. Читается от простого к глубокому.
Каждый документ раздела начинается с блока «Простыми словами», потом идёт детально. Связан с 02. Архитектура реализации — Фаза 1 (Боевое ядро), 04. Архитектура реализации — Фаза 2 (Эффекты + способности), 5. Технологический стек и архитектура.
Документы раздела
| # | Документ | О чём |
|---|---|---|
| 00 | Обзор и карта кода (этот файл) | Слои, поток данных, где что лежит, порядок чтения |
| 01 | 01. Dependency Injection и события | VContainer, скоупы, инъекция, MessagePipe vs C#-события |
| 02 | 02. Детерминированная симуляция и тик | Тик 30 Гц, аккумулятор, очередь команд, RNG, checksum, пауза |
| 03 | 03. Данные, статы и урон | SO-контент, StatType, слоистые модификаторы, пайплайн урона |
| 04 | 04. Эффекты и способности | EffectData + компоненты, стаки, диспел, контроль, реактивность |
| 05 | 05. Презентация и интерполяция | Раздел сим/визуал, UnitView, сглаживание 30→60 |
| 06 | 06. Сетевая модель — решение и план | Host-authoritative vs lockstep, что запарковано, главная таска MP |
| 07 | 07. Техдолг, решения и changelog | Отложенное, исправленное, открытые вопросы |
Простыми словами
Игра внутри разделена на две независимые половины:
-
«Мозг» (симуляция) — чистый C#-код, который считает бой по тикам: кто куда идёт, кто кого бьёт, сколько HP осталось. Он ничего не знает про картинку, звук и Unity-сцену. Его можно запустить «вслепую» (headless) в тесте и проверить, что при одном и том же входе он всегда даёт один и тот же результат.
-
«Тело» (презентация) — Unity-объекты (
MonoBehaviour, спрайты, HP-бары, цифры урона), которые читают состояние мозга и рисуют его. Тело никогда не меняет бой — только показывает.
Между ними — тонкий провод из событий. Мозг говорит «юнит №5 получил 30 урона», тело рисует цифру «30» и трясёт камеру.
Почему так, а не «по-юнитёвому» (каждый юнит — MonoBehaviour, двигает себя в Update)? Потому что игра кооперативная по сети и с паузой. Чтобы у двух игроков бой шёл одинаково (или чтобы пауза/реплей работали), нужна предсказуемая, отделённая от рендера симуляция. «По-юнитёвый» подход здесь упирается в стену синхронизации. Подробно — в 02. Детерминированная симуляция и тик и 06. Сетевая модель — решение и план.
Слои и сборки (Assembly Definitions)
Код разбит на сборки (.asmdef), и зависимости идут строго вниз. Это не косметика: компилятор физически запрещает нижнему слою знать про верхний, поэтому «мозг» не может случайно потянуть за собой Unity-UI.
Core — фундамент: RNG, константы симуляции, интерфейсы
├─ Data → Core — SO-данные, статы (ScriptableObject, структуры)
├─ Combat → Core, Data — «мозг»: симуляция, системы, эффекты, урон
├─ Net → Core, Combat — сетевой слой (NGO)
├─ Presentation→ Core,Data,Combat — «тело»: визуал, читает сим
└─ Game → знает всех — composition root: DI-скоупы, bootstrap, флоу
Game — единственный, кто видит все слои сразу: он их собирает (composition root). Всё остальное зависит только вниз. Карта сборок целиком — 1. Сборки (Assembly Definitions).
Важный приём — кросс-сборочный шов эффектов.
Dataлежит нижеCombat, ноEffectData(вData) должна хранить поведение эффектов, которое оперирует боевым состоянием (вCombat). Прямая ссылкаData → Combatзапрещена графом. Решение: вDataобъявлен пустой маркер-интерфейсIEffectComponent, а реальные хуки (OnApply/OnTick/OnExpire) — вCombatчерез производныйIRuntimeEffectComponent : IEffectComponent. Поле[SerializeReference] IEffectComponent[]вEffectDataхранит Combat-типы, не создавая зависимости вверх. Детали — 04. Эффекты и способности.
Поток данных: от запуска до удара
[Bootstrap] GameBootstrap (MonoBehaviour в CoreScene)
│ Start() → GameFlow.BootAsync()
▼
[Сцены] SceneLoader грузит BattleScene аддитивно к persistent CoreScene
│
▼
[DI] RootLifetimeScope (сессия) ──родитель──► CombatLifetimeScope (бой)
│ RNG, Audio, SceneLoader, battle-RNG, все системы,
│ GameFlow, MessagePipe CombatSimulation, презентеры
▼
[Старт боя] BattleSetupBuilder создаёт RuntimeUnit'ы из SO → EnqueueUnitSpawn
│
▼
[Пульс] CombatLoopService (IAsyncStartable) копит Time.deltaTime и
│ вызывает CombatSimulation.Tick() фиксированными шагами 30 Гц
▼
[Тик] Targeting → Abilities → Movement → SpatialHash → AutoAttack →
│ Projectiles → Regen → Effects → DrainEvents → Death → CheckOutcome
▼
[События] CombatSimulation шлёт C#-события (OnDamageDealt, OnUnitDied…)
▼
[Презентация] CombatPresenter ловит их, двигает UnitView, ретранслирует в
MessagePipe → Audio/VFX/UI подписываются независимо
Каждую стрелку этого потока подробно разбирают документы 01–05.
Карта классов: кто за что отвечает
Core (фундамент)
| Класс | Ответственность |
|---|---|
IRngService / XorShiftRng | Детерминированный генератор случайностей боя. Любой рандом — только через него |
SimConstants | Частоты сим/AI, TickDelta, анти-лавина-кап. Единый источник правды по таймингам |
ISimCommand / ISimEvent | Базовые контракты команд/событий симуляции |
Data (контент)
| Класс | Ответственность |
|---|---|
RelicData, VesselData | SO «Чемпион»/«Пилот»: стат-блоки, эффекты, способности (иммутабельный контент) |
EffectData, AbilityData | SO определения эффекта/способности + полиморфные компоненты поведения |
StatsConfig | Глобальные дефолты статов и тюнинг-константы (armor-K, клампы) |
StatType, StatModifier, ModifierOp, ScalableValue | Стат-система: перечисление статов, модификаторы, скейл от статов |
Combat (мозг)
| Класс | Ответственность |
|---|---|
CombatSimulation | Сердце: тик-цикл, реализует ICombatContext, очередь команд, события, checksum |
ICombatContext | Шов: единственная точка мутаций боя для систем и компонентов эффектов |
RuntimeUnit | Рантайм-юнит на один бой (POCO, без MonoBehaviour) |
Stats | Слоистые модификаторы + кэш итоговых значений |
*System (Targeting/Movement/AutoAttack/Projectile/Death/Regen/Ability) | По одной системе на аспект тика |
EffectSystem | Жизненный цикл эффектов: наложение, стаки, тик, диспел, контроль |
DamagePipeline | Чистый расчёт урона (статические функции) |
SpatialHash | Бесалокационные пространственные запросы |
RuntimeUnitFactory | Единственная точка сборки RuntimeUnit из SO |
ICombatCommand + Commands/* | Мутации боя на границе тика (Pause/Resume/SpawnUnit) |
Game (composition root)
| Класс | Ответственность |
|---|---|
GameBootstrap | Точка входа: поднимает DI, зовёт GameFlow.BootAsync |
RootLifetimeScope / CombatLifetimeScope | DI-скоупы сессии и боя |
GameFlow | Макро-флоу: Boot → BattleScene → результат |
CombatLoopService | Реалтайм-пульс: Time.deltaTime → фиксированные тики |
BattleSetupBuilder | Начальная расстановка юнитов |
Presentation (тело)
| Класс | Ответственность |
|---|---|
CombatPresenter | Мост сим→визуал: подписка на события, спавн вью, ретрансляция в MessagePipe |
UnitView, HealthBarView, DamageNumber(Spawner) | Отображение и интерполяция |
Net (сеть)
| Класс | Ответственность |
|---|---|
NetworkCommandRelay | Реле команд клиент→хост (концепт keeper при host-authoritative) |
FacepunchTransportBootstrap | Steam-транспорт под NGO |
_Parked/SimSyncProbe | ⛔ Запаркован (lockstep-инструмент, см. 06. Сетевая модель — решение и план) |
В каком порядке читать
- Этот обзор — общая карта.
- 01. Dependency Injection и события — как части находят друг друга (ты просил DI отдельно).
- 02. Детерминированная симуляция и тик — сердце, без которого остальное не имеет смысла.
- 03. Данные, статы и урон → 04. Эффекты и способности — содержимое боя.
- 05. Презентация и интерполяция — как это видно.
- 06. Сетевая модель — решение и план — почему вся эта строгость, и куда идём по кооперативу.
- 07. Техдолг, решения и changelog — что отложено и почему.