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>
15 KiB
Архитектура mrgameeng
Обзор
mrgameeng — кроссплатформенный 2D игровой движок на базе MonoGame 3.8.4 (DesktopGL: Windows, Linux, macOS), .NET 8, C#.
Ключевое архитектурное решение — ECS-first: движок плотно интегрирует Friflo.Engine.ECS (v3.6) как ядро модели объектов. Это самая производительная ECS-библиотека в .NET по результатам Ecs.CSharp.Benchmark: ноль аллокаций в горячих путях, archetype-хранилище, встроенные системы, события и индексы.
Принципы
- Данные отдельно от логики. Всё состояние игры — в компонентах
(
struct : IComponent), вся логика — в системах (QuerySystem<T>). НикакихUpdate()у игровых объектов и иерархий наследования сущностей. - Ноль аллокаций в кадре. Системы, выполняющиеся каждый кадр, не должны аллоцировать память. Проверяется бенчмарками и code review.
- Модульность. Движок разбит на библиотеки по функциональным областям. Игра подключает только то, что использует.
- Всё демонстрируется. Каждая публичная возможность движка показана
в
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: 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 (выбор: экосистема FontStashSharp, не требует Content Pipeline, зрелая и поддерживаемая):
scene.UseUI()вOnLoadпослеUseRenderer2D()— создаёт MyraDesktop(свой на сцену) и регистрирует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 игры).
По дереву папок он генерирует статический класс с типизированными хендлами,
тип выводится из расширения файла:
// 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 | Компиляция шейдеров при сборке |