# Архитектура 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. **Всё демонстрируется.** Каждая публичная возможность движка покрыта тестами (где логика тестируема без GPU) и показана в игре [LittleSim](https://gitea.hsrv.site/mrleo1nid/LittleSim) — живой витрине движка (движок подключён туда сабмодулем `engine/`). ## Модули Библиотеки сгруппированы по архитектурной роли, а не по одной на фичу. Каждая фича — подпапка библиотеки-хоста со своим неймспейсом `MrGameEng.<Фича>` (неймспейс не привязан к сборке), поэтому `using MrGameEng.Tilemaps;` и т.п. работают и после слияния. | Библиотека (сборка) | Фичи (неймспейсы) и ответственность | |------------------------------|------------------------------------------------------------| | `MrGameEng.Core` | Платформо-независимое ядро (зависит только от Friflo): `EngineContext`, `EntityStore`, `SystemRoot`, сцены и переходы (тайминг — `Transition`, `SceneManager`; визуал переходов — в `Host`), время (`GameClock.TimeScale`; `GameSpeed` — дискретная скорость пауза/1×/3×/6× поверх часов; `Calendar` — игровые дни поверх масштабированного времени, `context.UseCalendar(...)`; `Climate` — непрерывная сезонная/суточная температура и сезон поверх календаря, `context.UseClimate(...)`), жизненный цикл, `ServiceRegistry`, `Log`. **`HeadlessHost`** — цикл без окна и GPU на фиксированном тике (`Tick`/`RunTicks`/`Run` с realtime-пейсингом): дедикейтед-серверы, батч-симуляция, тесты | | `MrGameEng.Host` | Оконный MonoGame-хост: `GameHost` (обёртка над `Game` — цикл, окно, `GraphicsDeviceManager`; публикует `GraphicsDevice` сервисом в контексте), визуальные переходы сцен (`OverlayTransition`, фабрики `Transitions.Fade/Wipe`, `TransitionRenderer`). **Input** (`MrGameEng.Input`, `Host/Input/`): клавиатура, мышь, геймпад, action maps | | `MrGameEng.Graphics` | Собственный батчер-рендерер (см. «Рендеринг»), камера, спрайты, анимации, слои. **Tilemaps** (`MrGameEng.Tilemaps`, `Graphics/Tilemaps/`): тайловые карты кодом — `TileGrid` + `TileSet` + компонент `Tilemap`, отрисовка видимых клеток через батчер. **Lighting** (`MrGameEng.Lighting`, `Graphics/Lighting/`): амбиент день/ночь поверх `Calendar` → `Renderer2D.AmbientLight`, `scene.UseDayNight(renderer)`; по-клеточный лайтмап — `LightmapBuilder` (амбиент × окклюзия + точечные `PointLight` с трассировкой теней), накладывается multiply поверх мира через `scene.UseLighting(...)`, сэмплируется `Lighting.SampleAt` | | `MrGameEng.Audio` | Звуковые эффекты и музыка (NVorbis); `AudioManager` с `SoundVolume`/`MasterVolume` (одна ручка на эффекты и музыку) | | `MrGameEng.Content` | Пайплайн контента. **Assets** (`MrGameEng.Assets`): runtime-загрузка без Content Pipeline, кэш, `AssetRef`. **Atlases** (`MrGameEng.Atlases`): текстурные атласы — сборка из дерева картинок (`AtlasBuilder`) и рантайм-загрузка (`TextureAtlas`), CLI `tools/MrGameEng.AtlasTool`. **Mods** (`MrGameEng.Mods`): система модов — порядок загрузки, JSON-дефы, локализация, слияние деревьев контента | | `MrGameEng.Simulation` | Детерминированные геймплей-примитивы без данных мира. **Pathfinding** (`MrGameEng.Pathfinding`): A*, Dijkstra, BFS, flow fields по гриду. **AI** (`MrGameEng.AI`): utility-ИИ — кривые отклика, соображения, действия, выбор (`UtilityAi`), `Blackboard`. **Collisions** (`MrGameEng.Collisions`): компонент `Collider`, spatial hash, пары/запросы/raycast | | `MrGameEng.UI` | Игровой UI на [Myra](https://github.com/rds1983/Myra): `Desktop` на сцену, виджеты, скининг. **DevConsole** (`MrGameEng.DevConsole`): ингейм-консоль — логи `Log`, команды, история, автодополнение. **Inspector** (`MrGameEng.Inspector`): ECS-дебагер в духе Chrome DevTools — дерево сущностей по архетипам, компоненты/поля с правкой простых полей, выбор кликом по миру с подсветкой, вкладка перфа рендера (`scene.UseInspector(renderer)`, F1) | | `MrGameEng.Assets.Generator` | Roslyn incremental source generator: классы с типизированными хендлами ресурсов (отдельный анализатор netstandard2.0) | Планируемые области (по мере развития): `Physics2D`, `Particles` — добавляются как подпапки подходящей библиотеки или новой библиотекой, если это новая роль/тяжёлая зависимость. ### MrGameEng.Mods Контент игры описывается **модами** — папками вида `Mods/` с метаданными в `About/About.json` (id, имя, версия, зависимости). `ModLoader.Load` упорядочивает моды детерминированно: зависимости раньше зависимых, при равенстве — по алфавиту id. Поздний мод переопределяет ранние во всех системах контента. Сама игра поставляет свой контент как мод `Core` — любой другой мод может переопределить её данные. - **Дефы** (`Defs/**/*.json`): файл-конверт `{ "type": "<ключ>", "defs": [ … ] }`; CLR-тип на ключ регистрирует игра (`DefDatabase.RegisterType`). Поля: `defName` (уникален в типе; одноимённый деф позднего мода полностью заменяет ранний), `parent` (поля родителя как основа, свои — поверх; вложенные объекты заменяются целиком), `abstract` (только родитель, в базу не попадает; не наследуется), `label`. - **Локализация** (`Languages/<код>/**/*.json`): плоские словари ключ→строка; `LanguageManager` переключает язык на лету, недостающие ключи берёт из языка по умолчанию, в крайнем случае возвращает сам ключ. - **Деревья контента**: `ModContentTree.Build(mods, "Textures")` сливает одноимённые папки всех модов (поздний мод побеждает по относительному пути) — результат кормится, например, в `AtlasBuilder.Build(options, sources)` для инкрементальной сборки атласов при старте игры. ### Правило зависимостей ``` MrGameEng.Host ─┐ MrGameEng.Audio ─┤ MrGameEng.Graphics ─┼──► MrGameEng.Core ──► Friflo.Engine.ECS │ MrGameEng.Content ─┤ MrGameEng.Simulation ─┤ (Content — за Texture2DRegion, MrGameEng.UI ─┴──► MrGameEng.Graphics Simulation/UI — за Transform2D/рендер) ``` Библиотека зависит **только от `Core` и `Graphics`**. `Core` зависит только от Friflo — ни MonoGame, ни другой платформы: благодаря этому симуляция запускается и без окна (`HeadlessHost`). MonoGame (`MonoGame.Framework.DesktopGL`) тянут платформенные и графические библиотеки: `Host`, `Graphics`, `Audio` (и транзитивно их потребители). Платформенные ресурсы (например `GraphicsDevice`) хосты публикуют сервисами в `EngineContext.Services`; графический код достаёт девайс через `context.GetGraphicsDevice()` (расширение в `Graphics`). На `Host` не зависит никто, кроме самой игры — это точка входа оконной платформы. Если двум библиотекам нужен общий тип — он переезжает в `Core` (или, если это графический тип, в `Graphics`). `Content` тянет `Graphics` (атласы выдают `Texture2DRegion`); `Simulation` тянет `Graphics` (`Collisions` использует `Transform2D`, `RectF`); `UI` — только `Core` (Myra рисует своим SpriteBatch). Фичи, собранные в одну библиотеку, делят её набор пакетов (например, `Content` несёт и FontStash, и StbImage). Тяжёлые/опциональные зависимости (Myra, NVorbis) держим в отдельных библиотеках, чтобы остальной движок их не тянул. `MrGameEng.Assets.Generator` — особый случай: это анализатор (netstandard2.0), он подключается к проекту игры как `Analyzer`, в рантайме не участвует и не зависит от других модулей движка. ## Консоль разработчика `MrGameEng.DevConsole` — ингейм-консоль (тогглинг клавишей `): - Перехватывает всё, что пишется через статический `MrGameEng.Core.Log` (Debug/Info/Warning/Error; движок логирует ключевые события сам). - Команды: `console.Register(name, description, handler)`; встроенные — `help`, `clear`, `echo`, `timescale`, `close`, `quit`; игра добавляет свои. - История ввода (↑/↓), автодополнение по Tab, скролл (PageUp/PageDown/End/колесо). - Производительность: кольцевой буфер строк (2048), отрисовка — один Label, текст которого перестраивается только при изменении (`Revision`); при закрытой консоли — ноль работы в кадре. - Ядро (`DevConsole`) — чистая логика без Myra, полностью покрыта headless-тестами; Myra-обвязка отдельно. `scene.UseDevConsole()` — последним в `OnLoad`, чтобы консоль рисовалась поверх UI. Сервис общий, живёт между сценами. ## 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); ``` Обращение к ресурсу по строковому пути в коде игры — запрещено соглашением; строки существуют только внутри сгенерированного кода. ## Текстурные атласы `MrGameEng.Atlases` превращает дерево отдельных картинок в атласы и грузит их в рантайме. Спрайты с регионами одной страницы атласа батчер сливает в один draw call. ### Сборка (билд-тайм, без GPU) `AtlasBuilder.Build(AtlasBuildOptions)` — чистый CPU (StbImageSharp/StbImageWriteSharp): - Источник сканируется рекурсивно (png/jpg/jpeg/bmp); картинки группируются в атласы по первым `GroupDepth` папкам относительного пути (0 — один атлас на всё). - Упаковка — детерминированный shelf-packer (`ShelfPacker`): сортировка по высоте, полки, страницы до `MaxPageSize`² (по умолчанию 2048), зазор `Padding` (2 px), размер страницы подрезается до степени двойки; негабаритные картинки получают отдельную страницу под себя. - Выход: страницы `<Имя>.atlas..png` + метаданные `<Имя>.atlas` (JSON: страницы, регионы с ключами и прямоугольниками). Ключ региона — путь от корня источника без расширения (`Things/Pawn/Animal/Fox`). - Инкрементальность: группа пересобирается только если изменились исходники, состав файлов или параметры сборки; атласы исчезнувших групп удаляются из выходной папки. CLI-обёртка: `dotnet run --project tools/MrGameEng.AtlasTool -- <источник> <выход> [--group-depth N] [--page-size N] [--padding N] [--root-name Имя] [--force]`. ### Загрузка (рантайм) - `context.UseTextureAtlases()` регистрирует загрузчик `TextureAtlas` в `AssetManager`; кодогенератор выдаёт хендлы `AssetRef` для файлов `.atlas` (страницы `*.atlas.N.png` собственных Texture2D-хендлов не получают). - `TextureAtlas` владеет страницами (premultiplied alpha, как все текстуры движка) и отдаёт регионы: `atlas.GetRegion("Things/Pawn/Animal/Fox")` → `Texture2DRegion`, готовый для `Sprite`. ## Тайловые карты `MrGameEng.Tilemaps` — тайловые карты, создаваемые **кодом** (загрузка Tiled — в бэклоге): - `TileSet` — словарь тайлов: `Add(region, tint?)` возвращает id; id 0 зарезервирован под «пусто». Регион + тинт позволяют строить тайлсеты и из текстур атласа, и из тонированной белой текстуры. - `TileGrid` — плотная сетка `ushort`-id с проверкой границ; заполняется генератором мира, мутируется в рантайме (изменение видно со следующего кадра). - Компонент `Tilemap` (`struct : IComponent`): грид + тайлсет + `Origin`, `TileSize`, слой, `Depth`, общий тинт карты. Обычная сущность — карт может быть несколько (земля, декор поверх). - `scene.UseTilemaps()` (после `UseRenderer2D()`) вставляет `TilemapRenderSystem` в Draw-фазу перед flush. Система считает видимый диапазон клеток по cull-rect камеры (`TilemapMath.VisibleCells`) и сабмитит только его: стоимость кадра зависит от экрана, а не от размера грида. Тайлы батчатся со спрайтами по обычному порядку слой → depth → текстура: пол из одной текстуры атласа — один draw call. ## Поиск пути `MrGameEng.Pathfinding` — алгоритмы поиска пути по гриду; зависит только от Core и не владеет данными мира — игра реализует `IPathGrid` (Width/Height, `IsPassable`, `Cost ≥ 1`) поверх своего рельефа или `TileGrid`. - `GridPathfinder.FindPath(start, goal, path, algorithm)` — **A\*** (octile/Manhattan эвристика), **Dijkstra** (с учётом цены клеток) и **BFS** (быстрейший для равномерных гридов, цену игнорирует). Связность 4 или 8; диагонали никогда не срезают углы. - `FlowFieldBuilder.Build(goals, field)` — multi-source Dijkstra строит **flow field**: дистанция + нормализованное направление на клетку. Толпа любого размера дальше стоит O(1) на агента в кадр — основной инструмент для масс агентов (LittleSim). - Оптимизация под ECS: все буферы созданы один раз под размер грида и инвалидируются generation-штампом — повторные запросы не аллоцируют и не чистят массивы. Один экземпляр на систему; результаты детерминированы. ## ИИ агентов (utility) `MrGameEng.AI` — примитивы для принятия решений агентами (жители LittleSim). Зависит только от Core, **не владеет данными мира** и не привязан к ECS: всё параметризовано контекстом `TContext`, который игра передаёт сама (снимок восприятия, хендл сущности, blackboard — что угодно). Основан на Infinite-Axis Utility System: - `ResponseCurve` (value type) — кривая отклика, нормализованный вход `[0,1]` → полезность `[0,1]`: `Linear`, `Polynomial` (степень), `Logistic` (S-кривая), `SmoothStep`. Вход и выход клампятся. **Внимание:** `default(ResponseCurve)` имеет нулевой наклон (всегда 0) — для тождества используйте `ResponseCurve.Identity`. - `Consideration` — одно соображение: читает сырое значение из контекста, нормирует по диапазону `[min,max]` и прогоняет через кривую. - `UtilityAction` — действие из набора соображений. Очки = произведение соображений × `Weight`; любой ноль ветирует действие. Компенсирующий множитель (make-up value) убирает смещение произведения многих факторов вниз. - `UtilityAi` — reasoner: `Select` (детерминированно лучшее действие, при равенстве — первое) и `SelectWeighted(random)` (рулетка по очкам для разнообразия, воспроизводимо при seed). Очки пишутся в переиспользуемый буфер — повторные вычисления не аллоцируют; один экземпляр на вид агента, не потокобезопасен. - `Blackboard` — типизированная рабочая память агента (`Set`/`TryGet`/`GetOrDefault`) для холодных путей (восприятие, планирование). Витрина в LittleSim: `PawnDecisionSystem` выбирает «бродить/отдыхать» по энергии жителя, `PawnNeedsSystem` тратит/восстанавливает энергию, уставшие темнеют (`PawnAppearanceSystem`). Команда консоли `ai [energy]` печатает очки и расклад отдыхающих/блуждающих. ## Коллизии `MrGameEng.Collisions` — определение столкновений (без разрешения физики — она в бэклоге): - Компонент `Collider` (`struct : IComponent`): круг или AABB (без вращения), `Offset`, битовые маски `Layer`/`CollidesWith` (пара регистрируется, только если маски согласны в обе стороны). Создание через `Collider.Circle(r)` / `Collider.Box(w,h)`. - `CollisionWorld` — uniform spatial hash на плоских массивах (головы бакетов + связные списки индексов), **перестраивается с нуля каждый тик** за O(n): для движущихся сущностей это дешевле инкрементальных обновлений. Ноль аллокаций после прогрева; порядок пар детерминирован (порядок чанков Friflo). - `scene.UseCollisions(cellSize)` добавляет `CollisionSystem` — регистрируй её **после** систем движения; системы, читающие `world.Pairs`, — после неё. - Запросы для игровых систем: `Pairs` (пересекающиеся пары кадра), `QueryAabb(rect, span)`, `Raycast(from, to, mask)` (ближайшее попадание). - `cellSize` подбирается под типичный размер коллайдера; крупные коллайдеры занимают несколько ячеек — корректность не страдает, страдает константа. ## Рендеринг `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 аллокаций на кадр ниже порога параллелизма. Эталонный замер (стресс-сцена, до переноса демо в LittleSim): 100k сущностей, ~61k в кадре ≈ 297 FPS в Release. **Производительность измеряется только в Release** — Debug-сборка медленнее в 5–6 раз (нет инлайнинга JIT). ## Сцены и переходы - `SceneManager` владеет активной сценой; обычное переключение откладывается до начала следующего кадра (сцена никогда не выгружается посреди собственного кадра). - `Scenes.Switch(scene, Transitions.Fade(0.5f))` — переключение с визуальным переходом: фаза закрытия (старая сцена живёт) → своп при полном покрытии → фаза открытия. Тяжёлый `OnLoad` новой сцены скрыт за полностью закрытым экраном. - Тайминг-машина (`Transition` — длительности фаз, покрытие) живёт в `Core` и работает и в headless-контексте; визуал — в `Host`: встроенные `Transitions.Fade(duration, color)` и `Transitions.Wipe(duration, color)` (шторка). Свои — наследованием от `OverlayTransition` (рисование через `TransitionRenderer.Fill` в нормализованных координатах экрана); `GameHost` рисует оверлей поверх сцены, читая `Scenes.ActiveTransition`/`TransitionCoverage`/`TransitionPhase`. - Переходы идут по **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.*) tests/ xUnit-тесты, по проекту на библиотеку tools/ CLI-инструменты (упаковщик атласов) docs/ документация (русский) ``` Демо-проекта в движке нет: витрина движка — игра [LittleSim](https://gitea.hsrv.site/mrleo1nid/LittleSim) (движок подключён туда сабмодулем `engine/`). ## Зафиксированные версии | Зависимость | Версия | Назначение | |---------------------------------|----------|----------------------------------| | .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 | | StbImageSharp | 2.30.15 | Декодирование картинок при сборке атласов | | StbImageWriteSharp | 1.16.7 | Запись PNG-страниц атласов | | dotnet-mgfxc (dotnet tool) | 3.8.4.1 | Компиляция шейдеров при сборке |