Vortex Framework

Модульный фреймворк для разработки приложений на Unity с четким разделением на слои и паттерном шины данных.

Полная документация здесь

https://vortex-framework.ru/

Философия

Минимальная, защищённая, предсказуемая архитектура, где каждая строчка кода оправдана практической пользой.

Программирование сводится к трём задачам:

  1. Получение данных — данные доступны из любой точки через статические шины
  2. Обработка данных — непрерывная обработка без внешних коррекций промежуточных результатов
  3. Отображение данных — компоненты отображения работают с моделью через шину, не напрямую

Любая технология должна упрощать или улучшать работу с кодом — иначе она избыточна. Главная угроза — деградация архитектуры из-за неконтролируемых изменений, смешения слоёв и отсутствия защиты ключевых компонентов.

Архитектура слоёв

Верхние слои могут ссылаться на нижние. Обратное — запрещено. Слои характеризуют степень универсальности и нейтральности кода, а не условия его запуска. Понятие слоёв относится только к скриптам и не распространяется на ассеты.

┌─────────────────────────────────────────────────────────────────┐
│  Layer 4: AppLocale                                             │
│  Частные скрипты конкретного проекта                            │
├─────────────────────────────────────────────────────────────────┤
│  Layer 3: AppSDK                                                │
│  Универсальные механики для семейства приложений                │
├─────────────────────────────────────────────────────────────────┤
│  Layer 2: Framework Adaptation (Unity)                          │
│  Драйверы, пресеты, платформозависимые реализации               │
├─────────────────────────────────────────────────────────────────┤
│  Layer 1: Framework Core                                        │
│  Нейтральные модели, шины, абстракции (без Unity API)           │
└─────────────────────────────────────────────────────────────────┘

Layer 1: Framework Core

Платформонезависимая логика: чистый C#, .NET Standard, без Unity. Абстрактные модели данных, статические шины доступа, интерфейсы драйверов, системы загрузки и сохранения.

Namespace: Vortex.Core

Layer 2: Framework Adaptation

Платформозависимая, но доменно-нейтральная логика. Реализует универсальные паттерны, требующие Unity (ScriptableObject, Resources, Addressables, Odin). Может использоваться в любом Unity-проекте.

Namespace: Vortex.Unity

Layer 3: AppSDK

Доменно-специфичная, но переиспользуемая внутри семейства продуктов логика. Не привязана к одному проекту.

Layer 4: AppLocale

Уникальная проектно-специфичная логика. Даже если использует шину данных — остаётся локальной.

Критерии отнесения

  • Уровень 1 ↔ 2: если код зависит от Unity — он не может быть уровнем 1. Если код не зависит от предметной области — он может быть уровнем 2, даже если реализует сложную логику.
  • Уровень 2 ↔ 3: уровень 2 — паттерн без домена. Уровень 3 — конкретизация паттерна под домен, но без привязки к одному проекту.
  • Уровень 3 ↔ 4: уровень 3 — можно скопировать в другой проект того же семейства без изменений. Уровень 4 — требует модификации даже для близкого проекта.

Идеал: всё со временем переносится вверх вплоть до уровня 1, если демонстрирует достаточную степень нейтральности и универсальности.


Шина данных

Центральный архитектурный паттерн фреймворка. Статический синглтон с Dictionary<GUID, Record> для O(1) доступа к общим данным из любой точки проекта.

// Singleton — один экземпляр на весь жизненный цикл (сохраняется)
var profile = Database.GetRecord<UserProfile>("user-profile-guid");

// MultiInstance — новая копия при каждом запросе (не сохраняется)
var template = Database.GetNewRecord<DocumentTemplate>("template-guid");

Критерии шины:

  1. Данные доступны для чтения из любой точки проекта
  2. Запрашивающий компонент точно знает что ищет (по GUID)
  3. Выборка по однозначному признаку
  4. Максимальное быстродействие (Dictionary O(1))

Типы данных:

  • Общие → через шину (GetRecord<T>(id), GetNewRecord<T>(id))
  • Частные → только внутри компонента

DI-контейнеры (Zenject, VContainer) не используются — избыточны, когда данные общие и доступны через шину.


Работа с данными

Пресет → Модель

База данных — набор неизменяемых пресетов (ScriptableObject). Модели — изменяемые экземпляры, созданные на основе пресетов. GUID обязателен для всех типовых единиц.

Непрерывность обработки

Нельзя допускать внешнюю коррекцию промежуточных результатов:

// ❌ Плохо: событие срабатывает на каждое изменение
model.HP = 50;  // → OnChange → внешняя коррекция
model.MP = 30;  // → OnChange → рекурсия

// ✓ Хорошо: ручной вызов после всех изменений
model.HP = 50;
model.MP = 30;
model.NotifyChanged();

Аккумуляция вызовов

При многократных изменениях в одном кадре — обработка один раз в конце:

// Множество изменений в кадре
soldier.SetTarget(enemy);
soldier.SetPosture(Crouching);
soldier.TakeDamage(10);

// Визуальное обновление — один раз в конце кадра

Логика изменения данных — только в контроллере

Запрет рекурсивных триггеров без явного контроля. UI не принимает решений — только сообщает о действиях пользователя.


Отображение

Представление ≠ модель. Представление может обладать своими дополнительными параметрами, не переносимыми в модель.

UI-компоненты:

  • Подписываются на события шины или UI-узла
  • Самостоятельно получают данные по признакам
  • Работают с максимально атомарными и нейтральными данными
// ❌ Плохо: прямая передача данных
interface.SetData(model);

// ✓ Хорошо: компонент сам получает данные из шины
public class HeroPanel : MonoBehaviour
{
    void OnEnable()
    {
        var hero = HeroBus.GetCurrent();
        UpdateView(hero);
        HeroBus.OnChanged += UpdateView;
    }
}

Построение пакетов

Одиночная моно-модель

Компонент Слой Описание
{System} Core Шина: кэш, логика изменения/вывода, события
{System}Model Core Модель: публичные свойства (get; private set;)
{System}Preset Adaptation Пресет: публичные свойства (get;), ScriptableObject

Множественные модели (Database)

Компонент Слой Описание
{System} Core Шина: индекс-реестр, методы получения
Record Core Модель: свойства, события, CopyFrom()
RecordPreset<T> Adaptation Пресет: метод создания Record из данных

Ключевые паттерны

  • Singleton + SystemController: SystemController<T, TD> наследуется от Singleton<T>, драйверы валидируются через DriversGenericList.WhiteList
  • ReactiveValue<T>: IntData, BoolData, FloatData с implicit operators и OnUpdate event
  • IProcess + WaitingFor(): асинхронная загрузка с топологической сортировкой зависимостей
  • Partial-классы: крупные системы разбиты по тематике (App, Database, UIProvider, SettingsModel)
  • ActionExt: безопасный вызов делегатов — Fire(), FireAnd/Or(), Accumulate(), FirstNotNull()
  • MonoBehaviour-синглтоны — только при необходимости Update/Coroutine; иначе — чистый C#

Зависимости

Обязательные:

  • Unity 2021.3+
  • UniTask — async/await
  • TextMeshPro — текстовый рендеринг
  • Sirenix Odin Inspector — атрибуты для Inspector и собственный EditorTools-пакет

Опциональные:

  • Addressables — для AddressablesDriver пакета Database и для AssetCache (если не установлен — соответствующие сборки автоматически отключаются через defineConstraints)
  • protobuf-net — для сериализации ComplexModel

Лицензия

Проприетарная. Все права защищены.