Vortex — вход в систему

Что это?

Vortex — это готовая среда для разработки приложений на Unity. Не библиотека, не набор утилит, а оформленный каркас, в котором уже заранее решено:

  • как устроен жизненный цикл приложения,
  • где лежат данные,
  • как UI получает то, что показывает,
  • как одна часть приложения узнаёт об изменениях в другой,
  • как считается время и выполняется асинхронный код,
  • как должны выглядеть редакторские инструменты.

Vortex подходит для любого приложения — игры, утилиты, инструмента, симулятора, конфигуратора. Для игр дополнительно есть готовый Sdk-слой (состояния игры, квесты, мини-игры, save/load), но он опционален.

Проще говоря: ты не строишь архитектуру с нуля — ты добавляешь свою логику в уже работающую систему.

Ключевая идея

Системы не знают друг о друге напрямую. Они общаются через данные.

Это значит:

  • нет жёстких ссылок между модулями;
  • никто не «находит» соседа через FindObjectOfType или DI-контейнер;
  • любой модуль либо публикует данные в общую шину, либо читает их оттуда по типу.

Такой подход даёт два эффекта сразу: модули остаются независимыми (можно выкинуть/заменить), и при этом они автоматически видят актуальное состояние приложения.

Что уже есть «из коробки» (универсальное ядро)

Это базовый набор, на котором строится любое приложение в Vortex — вне зависимости от того, игра это или нет.

Подсистема Что закрывает
Database Шина данных всего фреймворка. Хранилище Record-ов, поиск по типу или GUID.
App Жизненный цикл приложения, состояния (AppStates), события OnStateChanged / OnStarting / OnStart / OnExit.
UIProvider Открытие/закрытие UI по условиям, без прямых ссылок на префабы из логики.
ReactiveValue<T> Подписки на изменения значений (IntData, BoolData, FloatData, StringData).
TimeController / Timer Контроль времени, отложенные действия, накопление вызовов.
Settings Настройки приложения с откатом и пресетами.
Save Сохранение/загрузка состояния через ISaveable.
AudioProvider Музыка/звуки/каналы громкости через шину.
Localization Локализация и переключение языков.
LogicChains Пошаговые цепочки логики из ScriptableObject.
EditorTools Атрибуты и Odin-drawer'ы для удобного инспектора.

Всё это — один согласованный набор, не набор независимых пакетов. Они рассчитаны друг на друга.

Что добавляется для игр (Sdk-слой)

Если делается именно игра, поверх ядра подключается опциональный Sdk-слой:

Подсистема Что добавляет
GameController Состояние конкретной игровой сессии (NewGame / Load / Save), реестр модулей сессии.
Quests Жизненный цикл квестов, условия активации, награды, save-points.
MiniGames Каркас мини-игр со статистикой, паузами, cheat-handler'ами.

Sdk не обязателен. Утилиту или инструмент можно собрать только на ядре.

Как строится логика

Любая фича в Vortex живёт по одной схеме — это работает и для игровой механики, и для экрана настроек, и для бизнес-инструмента:

Controller  →  Data  →  View
    ▲
    │
    (View → Controller через extension-методы или handler'ы)
  • Controller — статический класс или Singleton. Решает, что делать.
  • DataRecord в Database (или IGameData в GameModel для игр). Хранит состояние.
  • ViewMonoBehaviour, который читает Data и подписывается на изменения. Сам решений не принимает.

Когда пользователь что-то нажимает, View не меняет данные напрямую — он вызывает extension-метод контроллера, который уже всё пересчитывает.

Архитектура в одном взгляде

Три картинки, которые в сумме описывают весь Vortex. Если запомнить их — остальное складывается.

1. Поток управления от ввода до отрисовки

Полный цикл «что произошло у пользователя — как это отразилось на экране»:

[Внешний ввод]              ← клик, gamepad, сенсор, сетевое событие
       │
       ▼
[View / Handler]            ← MonoBehaviour ловит ввод, никаких решений не принимает
       │
       ▼
[Extension-метод / Bus]     ← обращение к контроллеру через шину, не прямой ссылкой
       │
       ▼
[Controller]                ← вычисляет, валидирует, решает
       │
       ▼
[Reactive Data]             ← Set() на ReactiveValue, новое значение в шине
       │
       ▼
[OnUpdate event]            ← подписчики (включая View) получают уведомление
       │
       ▼
[View update]               ← перерисовывается то, что показывается пользователю

Поток строго однонаправленный. View никогда не пишет в Data напрямую — только сообщает контроллеру о намерении. Контроллер мутирует Data. ReactiveValue уведомляет View. Цикл замкнут через события, а не через прямые вызовы.

2. Слой шин — как системы общаются

Каждая системная подсистема Vortex (Inventory, Audio, Localization, Video, Save…) построена по одному шаблону:

   Контроллер-фасад                       Подменяемая реализация
   ┌─────────────────┐                    ┌─────────────────────┐
   │   Inventory     │ ─── IDriver ─────▶ │  InventoryDriver…   │
   │ (статический    │                    │  • Local            │
   │  публичный API) │                    │  • Network          │
   └─────────────────┘                    │  • Test / Mock      │
            ▲                             └─────────────────────┘
            │                                        ▲
            │                                        │
    Любой код проекта                         DriverConfig.asset
    (стратегии, View,                         (выбор реализации
    handler'ы) обращается                     в инспекторе)
    через шину

Контроллер — это тонкий фасад с фиксированным контрактом, доступный отовсюду. За ним стоит подменяемый драйвер. Меняешь реализацию в DriverConfig — меняется поведение системы целиком, остальной код не правится.

Это и есть Vortex-аналог dependency inversion: вызовы идут на стабильный контракт шины, а не на конкретный класс реализации.

3. Сборка приложения из пакетов

Vortex собирается не «вручную в коде», а из набора подключённых пакетов на этапе runtime/editor composition:

Config-ассеты в проекте              Пакеты с реализацией            Собранная среда runtime
┌──────────────────────┐             ┌──────────────────────┐         ┌──────────────────────┐
│ DriverConfig         │             │ ru.vortex.unity.…    │         │ Контроллеры готовы   │
│ DatabaseSettings     │ ──── + ──── │ ru.vortex.sdk.…      │ ────▶   │ Драйверы привязаны   │
│ AudioChannelsConfig  │             │ ru.vortex.steam.…    │         │ Шины эмитят          │
│ SdkSettings          │             │ ru.vortex.nani.…     │         │ Сцена работает       │
└──────────────────────┘             └──────────────────────┘         └──────────────────────┘
        │                                       │
        │                                       │
    Геймдиз выставляет в                Разработчик включает
    инспекторе SO-конфигов              нужные пакеты в SdkSettings

Контейнер на регистрации не нужен — конфигурация хранится в ScriptableObject-ассетах. Каждый пакет автономен: подключил — работает, выключил тогл в SdkSettings — отвалился вместе со своими зависимостями.

Это package-composition-first архитектура: компонуем приложение не кодом, а конфигурацией ассетов.


Запомнить три картинки достаточно, чтобы понимать, где живёт что в Vortex. Дальше каждый пакет конкретизирует свой кусок этой общей схемы.

Где лежат данные

1. Database — шина для любого приложения

Это глобальный реестр Record-ов. Сюда попадает всё, к чему должен иметь доступ любой пакет:

// Получить пресетные данные по GUID:
var preset = Database.GetRecord<ItemPreset>(itemGuid);

// Получить новую копию из пресета (для multi-instance):
var instance = Database.GetNewRecord<ItemData>(itemGuid);

Database — единственное хранилище, которое нужно для любой не-игровой задачи (утилита, инструмент, конфигуратор).

2. GameModel — состояние игровой сессии (только для игр)

Если используется Sdk-слой, для данных текущей игры есть отдельный контейнер. Модули просто описывают свои IGameData (HP игрока, прогресс квестов, инвентарь) — регистрировать их вручную не нужно:

// 1. Описываем данные модуля — достаточно реализовать IGameData:
public class InventoryData : GameModel.IGameData
{
    public List<ItemId> Items = new();
}

// 2. Никакой ручной регистрации не требуется.
//    GameModel (ComplexModel) при старте сессии сам находит все реализации
//    IGameData рефлексией и создаёт по экземпляру каждой.

// 3. Читаем из любого места по типу:
var inventory = GameController.Get<InventoryData>();
inventory.Items.Add(itemId);

GameController.Get<T>() требует, чтобы T реализовывал GameModel.IGameData (маркер-интерфейс) и имел конструктор без параметров — этого достаточно, чтобы данные автоматически попали в сессию.

Шаблоны (.vtp)

В Vortex есть система шаблонов — файлы .vtp, в которых заранее упакована структура конкретного типа фичи: папки, скрипты, заготовки контроллера/данных/view.

Развернул шаблон → получил готовый каркас модуля. Дальше пишешь только специфичную логику.

Шаблоны работают для любых модулей, не только игровых. Особенно удобно для типовых вещей вроде мини-игр: один шаблон — и у тебя уже есть MiniGameController, MiniGameData, view-контейнер, handler'ы — всё подключённое к GameController.

Что ты на самом деле делаешь

В Vortex разработчик не строит инфраструктуру. Он:

  1. описывает данные (Record или IGameData);
  2. пишет логику (контроллер);
  3. вешает view, который подписывается на данные;
  4. публикует данные в Database (для игровой сессии IGameData подхватывается GameModel автоматически).

Всё остальное — состояния, время, UI, реактивность — уже работает.

Что это даёт

  • Скорость: типовые задачи закрыты, не надо каждый раз изобретать.
  • Предсказуемость: все модули устроены одинаково — открыл папку и понял, где что.
  • Масштабируемость: новые механики и фичи добавляются как ещё один модуль, не ломая существующие.
  • Чистота: нет «магических» зависимостей и DI-графов, потоки данных видно невооружённым глазом.
  • Универсальность: одна и та же архитектура подходит и для игры, и для редакторского инструмента, и для приложения-конфигуратора.

Цена

  • Нужно принять правила: данные через шину, логика в контроллере, view только отображает.
  • Нельзя «делать как привык» — попытка прокинуть прямую ссылку между двумя контроллерами сразу заметна и обычно означает, что данные не там.
  • Требуется дисциплина: данные, действия и отображение — это три разные сущности, их не смешивают в одном MonoBehaviour.

Когда это оправдано

Vortex имеет смысл, если:

  • проект нетривиальный (несколько систем, несколько сцен, сохранения, настройки);
  • есть несколько независимых модулей (механики в игре, экраны в инструменте, режимы в утилите);
  • ожидаются рост и изменения на длинной дистанции;
  • важна скорость добавления новых фич силами небольшой команды.

Для прототипа на пару экранов — overkill. Для проекта, который будет жить год+ — окупается за первый месяц.

Коротко

Vortex — это среда для любого Unity-приложения, в которой базовые инфраструктурные задачи уже решены, а разработчик работает только с логикой и данными, не связывая систему вручную. Для игр поверх ядра подключается опциональный Sdk с готовыми game-специфичными инструментами.

Дальше:

  • Vortex/Core/ — слой ядра (чистый C#).
  • Vortex/Unity/ — Unity-адаптация.
  • Vortex/Sdk/ — опциональный игровой Sdk (GameController, Quests, MiniGames).
  • Vortex/*/README.ru.md — документация по конкретным системам.