# Архитектура mrgameeng ## Обзор **mrgameeng** — кроссплатформенный 2D игровой движок на базе MonoGame 3.8.4 (DesktopGL: Windows, Linux, macOS), .NET 8, C#. Ключевое архитектурное решение — **ECS-first**: движок плотно интегрирует [Friflo.Engine.ECS](https://github.com/friflo/Friflo.Engine.ECS) (v3.6) как ядро модели объектов. Это самая производительная ECS-библиотека в .NET по результатам [Ecs.CSharp.Benchmark](https://github.com/Doraku/Ecs.CSharp.Benchmark): ноль аллокаций в горячих путях, archetype-хранилище, встроенные системы, события и индексы. ## Принципы 1. **Данные отдельно от логики.** Всё состояние игры — в компонентах (`struct : IComponent`), вся логика — в системах (`QuerySystem`). Никаких `Update()` у игровых объектов и иерархий наследования сущностей. 2. **Ноль аллокаций в кадре.** Системы, выполняющиеся каждый кадр, не должны аллоцировать память. Проверяется бенчмарками и code review. 3. **Модульность.** Движок разбит на библиотеки по функциональным областям. Игра подключает только то, что использует. 4. **Всё демонстрируется.** Каждая публичная возможность движка показана в `MrGameEng.Sample` и покрыта тестами (где логика тестируема без GPU). ## Модули | Библиотека | Ответственность | |------------------------------|------------------------------------------------------------| | `MrGameEng.Core` | Игровой цикл (хост над `Game`), `EntityStore`, `SystemRoot`, сцены, время, жизненный цикл | | `MrGameEng.Graphics` | Собственный батчер-рендерер (см. «Рендеринг»), камера, спрайты, анимации, слои | | `MrGameEng.Input` | Абстракция ввода: клавиатура, мышь, геймпад; action maps | | `MrGameEng.Audio` | Звуковые эффекты и музыка | | `MrGameEng.Assets` | Runtime-загрузка ресурсов без Content Pipeline, кэш, `AssetRef` | | `MrGameEng.Assets.Generator` | Roslyn incremental source generator: классы с типизированными хендлами ресурсов | | `MrGameEng.UI` | Игровой UI на [Myra](https://github.com/rds1983/Myra): `Desktop` на сцену, виджеты, скининг | Планируемые модули (по мере развития): `Physics2D`, `Tilemap`, `UI`, `Particles`. ### Правило зависимостей ``` MrGameEng.Graphics ─┐ MrGameEng.Input ─┤ MrGameEng.Audio ─┼──► MrGameEng.Core ──► MonoGame.Framework.DesktopGL MrGameEng.Assets ─┘ └──► Friflo.Engine.ECS ``` Модули зависят **только от `Core`** и никогда друг от друга. `Core` зависит только от MonoGame и Friflo. Если двум модулям нужен общий тип — он переезжает в `Core`. `MrGameEng.Assets.Generator` — особый случай: это анализатор (netstandard2.0), он подключается к проекту игры как `Analyzer`, в рантайме не участвует и не зависит от других модулей движка. ## UI (Myra) Игровой UI — интеграция [Myra](https://github.com/rds1983/Myra) (выбор: экосистема FontStashSharp, не требует Content Pipeline, зрелая и поддерживаемая): - `scene.UseUI()` в `OnLoad` **после** `UseRenderer2D()` — создаёт Myra `Desktop` (свой на сцену) и регистрирует `UiRenderSystem` последней в Draw-фазе. - UI строится кодом через `desktop.Root`; ввод (мышь/клавиатура) Myra обрабатывает сама во время рендера, в оконных пикселях. - Текст рисуется встроенным шрифтом Myra; свои шрифты — `FontSystem` через `AssetManager`. - **Документированное исключение из правил**: Myra рисует собственным SpriteBatch (запрет на SpriteBatch относится к коду движка, не к сторонним библиотекам). Если UI станет узким местом, её рендер можно перевести на наш батчер через `IMyraRenderer` (бэклог). ## Загрузка ресурсов (без Content Pipeline) MGCB / Content Pipeline **не используется**. Все ресурсы лежат в папке `Assets/` проекта игры как сырые файлы (копируются в output при сборке) и грузятся в рантайме: | Тип | Форматы | Как грузим | |----------------|-----------|---------------------------------------------------------| | Текстуры | png, jpg | `Texture2D.FromFile` | | Шрифты | ttf | FontStashSharp (растеризация в рантайме, динамический атлас) | | Звуки | wav | `SoundEffect.FromFile` | | Музыка | ogg | Стриминг через NVorbis + `DynamicSoundEffectInstance` | | Шейдеры | fx | Компиляция `dotnet-mgfxc` на этапе сборки (MSBuild target), в рантайме грузится байткод `.mgfx` | `AssetManager` (в `MrGameEng.Assets`) грузит ресурсы по типизированному хендлу `AssetRef` (путь + тип), кэширует по пути и владеет временем жизни (Dispose при выгрузке сцены/игры). ### Кодогенератор хендлов `MrGameEng.Assets.Generator` — Roslyn **incremental source generator**. Файлы из `Assets/` передаются ему через `AdditionalFiles` (один glob в `.csproj` игры). По дереву папок он генерирует статический класс с типизированными хендлами, тип выводится из расширения файла: ```csharp // Assets/Textures/player.png → public static partial class GameAssets { public static class Textures { public static readonly AssetRef Player = new("Textures/player.png"); } } // использование: Texture2D tex = assets.Load(GameAssets.Textures.Player); ``` Обращение к ресурсу по строковому пути в коде игры — запрещено соглашением; строки существуют только внутри сгенерированного кода. ## Рендеринг `SpriteBatch` в движке **не используется** — в `MrGameEng.Graphics` свой батчер, спроектированный под ECS. ### Камера Ортографическая 2D-камера — из коробки, отдельная сущность с компонентом `Camera`: - Позиция, зум, поворот, опциональные границы мира (clamp). - Строит ортографическую view-projection матрицу; виртуальное разрешение с letterbox/scale под размер окна. - Активная камера — одна на сцену. Утилиты `ScreenToWorld` / `WorldToScreen`. ### Слои (render layers) - Слои регистрируются явно при настройке рендерера: имя + порядок отрисовки (например, `Background → World → Foreground → UI`). - У каждого слоя — **пространство**: `World` (через трансформ камеры) или `Screen` (screen-space, без камеры — HUD, UI). - Сортировка внутри слоя — по `float depth` спрайта; слой можно переключить в режим **Y-sort** (depth = позиция Y, для top-down игр). - Спрайт ссылается на слой компактным id (`LayerId`), не строкой. ### Culling - Перед записью вершин AABB спрайта (с учётом rotation/scale) проверяется против world-прямоугольника камеры; невидимые сущности не попадают в батчер. - Screen-space слои не куллятся по камере (они всегда на экране). - Culling — простой broad-phase в draw-системе, без пространственных структур; если профилирование покажет, что на больших мирах его не хватает, добавим spatial hash отдельным этапом. ### Батчер - Draw-системы итерируют чанки Friflo (`Chunks`) и пишут вершины напрямую в CPU-буфер батчера — без промежуточных списков и аллокаций. - Порядок сортировки: **слой → depth (или Y) → текстура**; спрайты с одной текстурой сливаются в один draw call (динамический vertex buffer + общий quad index buffer). - Сортировка — **стабильный LSD radix sort**: спрайты с равным ключом сохраняют порядок сабмита между кадрами (нет мерцания), сложность O(n); проходы по одинаковым у всех ключей разрядам пропускаются. - Горячий путь без тригонометрии, корней и делений: для спрайтов без поворота SinCos не вычисляется; радиус culling-окружности и UV-координаты предрассчитаны в `Texture2DRegion`. - **Параллелизм**: выше `Renderer2DOptions.ParallelThreshold` (по умолчанию 8192) подача спрайтов и построение вершин идут на всех ядрах. Работа режется на сегменты по 4096 сущностей (чанк Friflo держит весь архетип — сам по себе он слишком крупный для распределения); сегменты сливаются в детерминированном порядке, поэтому стабильность сортировки сохраняется. Ниже порога — прежний однопоточный путь без аллокаций. - Vertex buffer пишется **кольцом** (`SetDataOptions.NoOverwrite`, GPU-буфер вдвое больше кадра): загрузка не ждёт, пока GPU дорисует предыдущий кадр; `Discard` — только на перемотке кольца. - **Тайминги фаз** кадра (submit/sort/build/upload/draw, мс) доступны как свойства `Renderer2D` — выводятся в HUD стресс-сцены; оптимизации делаются только по ним. - Текстурные атласы — первоклассный гражданин: `Sprite` хранит регион атласа, спрайты одного атласа батчатся автоматически. - Цель по производительности: ≥100k спрайтов при 60 FPS на среднем десктопе, 0 аллокаций на кадр ниже порога параллелизма. Стресс-сцена Sample: 100k сущностей (~61k в кадре) ≈ 297 FPS в Release. **Производительность измеряется только в Release** — Debug-сборка медленнее в 5–6 раз (нет инлайнинга JIT). ## Сцены и переходы - `SceneManager` владеет активной сценой; обычное переключение откладывается до начала следующего кадра (сцена никогда не выгружается посреди собственного кадра). - `Scenes.Switch(scene, Transition.Fade(0.5f))` — переключение с визуальным переходом: фаза закрытия (старая сцена живёт) → своп при полном покрытии → фаза открытия. Тяжёлый `OnLoad` новой сцены скрыт за полностью закрытым экраном. - Встроенные переходы: `Transition.Fade(duration, color)` и `Transition.Wipe(duration, color)` (шторка). Свои — наследованием от `Transition` (рисование через `TransitionRenderer.Fill` в нормализованных координатах экрана). - Переходы идут по **unscaled**-времени: работают при паузе геймплея (`TimeScale = 0`). - Повторный `Switch` во время перехода заменяет целевую сцену, не перезапуская переход. ## Интеграция ECS - На каждую сцену создаётся свой `EntityStore` (мир) и **два** `SystemRoot`: `UpdateSystems` (логика) и `DrawSystems` (рендер). Порядок систем детерминирован и задаётся явно при регистрации. - Модули подключаются к сцене extension-методами из `OnLoad`: `scene.UseRenderer2D()`, `scene.UseInput()`, `scene.UseSpriteAnimation()`, `context.UseAssets()`, `context.UseAudio()`. Сервисы модулей живут в `EngineContext.Services` (общие между сценами), системы — в сцене. - Базовый трансформ — один компонент `Transform2D` (position + rotation + scale): один массив на чанк дешевле трёх отдельных компонентов. - Модули поставляют готовые компоненты и системы (например, `Sprite` + `SpriteRenderSystem` из `Graphics`), игра добавляет свои. ## Кадр (frame pipeline) ``` Game.Update ──► Scene.Update ──► SystemRoot (Update-фаза): input → логика игры → анимации Game.Draw ──► Scene.Draw ──► SystemRoot (Draw-фаза): камера → culling → запись вершин в батчер → flush (draw calls) ``` ## Структура репозитория ``` MrGameEng.sln src/ библиотеки движка (MrGameEng.*) samples/ MrGameEng.Sample — демо всех возможностей tests/ xUnit-тесты, по проекту на библиотеку docs/ документация (русский) ``` ## Зафиксированные версии | Зависимость | Версия | Назначение | |---------------------------------|----------|----------------------------------| | .NET (target framework) | net8.0 | | | MonoGame.Framework.DesktopGL | 3.8.4.1 | Базовый фреймворк | | Friflo.Engine.ECS | 3.6.0 | ECS | | FontStashSharp.MonoGame | 1.5.6 | Шрифты (ttf) в рантайме | | NVorbis | 0.10.5 | Декодирование ogg | | Myra | 1.6.1 | Игровой UI | | dotnet-mgfxc (dotnet tool) | 3.8.4.1 | Компиляция шейдеров при сборке |