Vortex Framework
Модульный фреймворк для разработки приложений на Unity с четким разделением на слои и паттерном шины данных.
Полная документация здесь
https://vortex-framework.ru/
Философия
Минимальная, защищённая, предсказуемая архитектура, где каждая строчка кода оправдана практической пользой.
Программирование сводится к трём задачам:
- Получение данных — данные доступны из любой точки через статические шины
- Обработка данных — непрерывная обработка без внешних коррекций промежуточных результатов
- Отображение данных — компоненты отображения работают с моделью через шину, не напрямую
Любая технология должна упрощать или улучшать работу с кодом — иначе она избыточна. Главная угроза — деградация архитектуры из-за неконтролируемых изменений, смешения слоёв и отсутствия защиты ключевых компонентов.
Архитектура слоёв
Верхние слои могут ссылаться на нижние. Обратное — запрещено. Слои характеризуют степень универсальности и нейтральности кода, а не условия его запуска. Понятие слоёв относится только к скриптам и не распространяется на ассеты.
┌─────────────────────────────────────────────────────────────────┐
│ 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");
Критерии шины:
- Данные доступны для чтения из любой точки проекта
- Запрашивающий компонент точно знает что ищет (по GUID)
- Выборка по однозначному признаку
- Максимальное быстродействие (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 иOnUpdateevent - 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
Лицензия
Проприетарная. Все права защищены.