Files
mrgameeng/docs/architecture.md
T
Leonid PershinandClaude Fable 5 af319d1276 Add MrGameEng.UI module integrating Myra
scene.UseUI() creates a per-scene Myra Desktop and registers
UiRenderSystem last in the draw phase (UI on top, window pixels).
GameHost now exposes itself as a Game service (Myra needs the instance;
useful for games too). Myra was picked over Gum/ImGui for the
FontStashSharp ecosystem fit, pipeline-free assets and maturity; its
internal SpriteBatch use is a documented exception to the engine rule.

Sample: on-screen HUD replaces window-title-only stats — live FPS label,
music volume slider and scene-switch buttons on both scenes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 04:51:12 +03:00

219 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Архитектура 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<T>`).
Никаких `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<T>` |
| `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<T>` (путь + тип), кэширует по пути и владеет временем жизни (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<Texture2D> 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<Position, Sprite, ...>`) и пишут
вершины напрямую в CPU-буфер батчера — без промежуточных списков и аллокаций.
- Порядок сортировки: **слой → depth (или Y) → текстура**; спрайты с одной
текстурой сливаются в один draw call (динамический vertex buffer + общий
quad index buffer).
- Сортировка — **стабильный LSD radix sort**: спрайты с равным ключом сохраняют
порядок сабмита между кадрами (нет мерцания), сложность O(n); проходы по
одинаковым у всех ключей разрядам пропускаются.
- Горячий путь без тригонометрии и корней: для спрайтов без поворота SinCos
не вычисляется, радиус culling-окружности берётся из предрассчитанной
диагонали региона.
- Текстурные атласы — первоклассный гражданин: `Sprite` хранит регион атласа,
спрайты одного атласа батчатся автоматически.
- Цель по производительности: ≥100k спрайтов при 60 FPS на среднем десктопе,
0 аллокаций на кадр. Контролируется стресс-сценой в Sample: 100k сущностей
(~61k в кадре) ≈ 124 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 | Компиляция шейдеров при сборке |