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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a3e6d3bb0a
commit
af319d1276
@@ -18,9 +18,11 @@ docs/ architecture, conventions, roadmap (Russian)
|
|||||||
|
|
||||||
Engine modules: `Core` (game loop, ECS world, scenes, time), `Graphics` (custom batched
|
Engine modules: `Core` (game loop, ECS world, scenes, time), `Graphics` (custom batched
|
||||||
renderer, camera, sprites), `Input`, `Audio`, `Assets` (runtime loading, no content
|
renderer, camera, sprites), `Input`, `Audio`, `Assets` (runtime loading, no content
|
||||||
pipeline), `Assets.Generator` (Roslyn source generator for typed asset handles).
|
pipeline), `Assets.Generator` (Roslyn source generator for typed asset handles),
|
||||||
|
`UI` (Myra integration: `scene.UseUI()` after `UseRenderer2D()`).
|
||||||
Dependency rule: every module may depend only on `Core`; `Core` depends only on
|
Dependency rule: every module may depend only on `Core`; `Core` depends only on
|
||||||
MonoGame and Friflo.Engine.ECS. `Assets.Generator` is a netstandard2.0 analyzer.
|
MonoGame and Friflo.Engine.ECS. `Assets.Generator` is a netstandard2.0 analyzer.
|
||||||
|
Documented exception: Myra renders with its own SpriteBatch internally.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<PackageVersion Include="Friflo.Engine.ECS" Version="3.6.0" />
|
<PackageVersion Include="Friflo.Engine.ECS" Version="3.6.0" />
|
||||||
<PackageVersion Include="FontStashSharp.MonoGame" Version="1.5.6" />
|
<PackageVersion Include="FontStashSharp.MonoGame" Version="1.5.6" />
|
||||||
<PackageVersion Include="NVorbis" Version="0.10.5" />
|
<PackageVersion Include="NVorbis" Version="0.10.5" />
|
||||||
|
<PackageVersion Include="Myra" Version="1.6.1" />
|
||||||
|
|
||||||
<!-- Source generator -->
|
<!-- Source generator -->
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Audio", "src\MrGa
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Input.Tests", "tests\MrGameEng.Input.Tests\MrGameEng.Input.Tests.csproj", "{0E0710AB-6132-4E64-9AFC-03B0601F92C6}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Input.Tests", "tests\MrGameEng.Input.Tests\MrGameEng.Input.Tests.csproj", "{0E0710AB-6132-4E64-9AFC-03B0601F92C6}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.UI", "src\MrGameEng.UI\MrGameEng.UI.csproj", "{17EB97D5-DCF8-47DF-B810-DA45AE314170}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -173,6 +175,18 @@ Global
|
|||||||
{0E0710AB-6132-4E64-9AFC-03B0601F92C6}.Release|x64.Build.0 = Release|Any CPU
|
{0E0710AB-6132-4E64-9AFC-03B0601F92C6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{0E0710AB-6132-4E64-9AFC-03B0601F92C6}.Release|x86.ActiveCfg = Release|Any CPU
|
{0E0710AB-6132-4E64-9AFC-03B0601F92C6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{0E0710AB-6132-4E64-9AFC-03B0601F92C6}.Release|x86.Build.0 = Release|Any CPU
|
{0E0710AB-6132-4E64-9AFC-03B0601F92C6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -189,5 +203,6 @@ Global
|
|||||||
{9F4D0E2A-1FAB-4DEB-AE7F-768EFB660AFD} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
{9F4D0E2A-1FAB-4DEB-AE7F-768EFB660AFD} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
{D55753A2-4CC0-4BCC-80D5-01E919FF97BE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
{D55753A2-4CC0-4BCC-80D5-01E919FF97BE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
{0E0710AB-6132-4E64-9AFC-03B0601F92C6} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
{0E0710AB-6132-4E64-9AFC-03B0601F92C6} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||||
|
{17EB97D5-DCF8-47DF-B810-DA45AE314170} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
| `MrGameEng.Audio` | Звуковые эффекты и музыка |
|
| `MrGameEng.Audio` | Звуковые эффекты и музыка |
|
||||||
| `MrGameEng.Assets` | Runtime-загрузка ресурсов без Content Pipeline, кэш, `AssetRef<T>` |
|
| `MrGameEng.Assets` | Runtime-загрузка ресурсов без Content Pipeline, кэш, `AssetRef<T>` |
|
||||||
| `MrGameEng.Assets.Generator` | Roslyn incremental source generator: классы с типизированными хендлами ресурсов |
|
| `MrGameEng.Assets.Generator` | Roslyn incremental source generator: классы с типизированными хендлами ресурсов |
|
||||||
|
| `MrGameEng.UI` | Игровой UI на [Myra](https://github.com/rds1983/Myra): `Desktop` на сцену, виджеты, скининг |
|
||||||
|
|
||||||
Планируемые модули (по мере развития): `Physics2D`, `Tilemap`, `UI`, `Particles`.
|
Планируемые модули (по мере развития): `Physics2D`, `Tilemap`, `UI`, `Particles`.
|
||||||
|
|
||||||
@@ -52,6 +53,21 @@ MrGameEng.Assets ─┘ └──► Friflo.Engine.ECS
|
|||||||
он подключается к проекту игры как `Analyzer`, в рантайме не участвует и не зависит
|
он подключается к проекту игры как `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)
|
## Загрузка ресурсов (без Content Pipeline)
|
||||||
|
|
||||||
MGCB / Content Pipeline **не используется**. Все ресурсы лежат в папке `Assets/`
|
MGCB / Content Pipeline **не используется**. Все ресурсы лежат в папке `Assets/`
|
||||||
@@ -198,4 +214,5 @@ docs/ документация (русский)
|
|||||||
| Friflo.Engine.ECS | 3.6.0 | ECS |
|
| Friflo.Engine.ECS | 3.6.0 | ECS |
|
||||||
| FontStashSharp.MonoGame | 1.5.6 | Шрифты (ttf) в рантайме |
|
| FontStashSharp.MonoGame | 1.5.6 | Шрифты (ttf) в рантайме |
|
||||||
| NVorbis | 0.10.5 | Декодирование ogg |
|
| NVorbis | 0.10.5 | Декодирование ogg |
|
||||||
|
| Myra | 1.6.1 | Игровой UI |
|
||||||
| dotnet-mgfxc (dotnet tool) | 3.8.4.1 | Компиляция шейдеров при сборке |
|
| dotnet-mgfxc (dotnet tool) | 3.8.4.1 | Компиляция шейдеров при сборке |
|
||||||
|
|||||||
+3
-1
@@ -18,7 +18,9 @@
|
|||||||
- Physics2D (выбор библиотеки: Aether.Physics2D / своя)
|
- Physics2D (выбор библиотеки: Aether.Physics2D / своя)
|
||||||
- Tilemap (поддержка Tiled)
|
- Tilemap (поддержка Tiled)
|
||||||
- Particles
|
- Particles
|
||||||
- UI
|
- UI: загрузка MML-разметки Myra через AssetManager + хендлы в кодогенераторе
|
||||||
|
- UI: рендер Myra через наш батчер (`IMyraRenderer`), если UI станет узким местом по draw call'ам
|
||||||
|
- DevTools-модуль на ImGui.NET: инспектор сущностей, дебаг-панели
|
||||||
- Бенчмарки BenchmarkDotNet для систем (сейчас производительность контролируется стресс-сценой)
|
- Бенчмарки BenchmarkDotNet для систем (сейчас производительность контролируется стресс-сценой)
|
||||||
- Spatial hash для culling на очень больших мирах (если профилирование покажет необходимость)
|
- Spatial hash для culling на очень больших мирах (если профилирование покажет необходимость)
|
||||||
- Параллельная запись вершин (Parallel.For по чанкам), если упрёмся в CPU на ещё больших сценах
|
- Параллельная запись вершин (Parallel.For по чанкам), если упрёмся в CPU на ещё больших сценах
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<ProjectReference Include="..\..\src\MrGameEng.Input\MrGameEng.Input.csproj" />
|
<ProjectReference Include="..\..\src\MrGameEng.Input\MrGameEng.Input.csproj" />
|
||||||
<ProjectReference Include="..\..\src\MrGameEng.Audio\MrGameEng.Audio.csproj" />
|
<ProjectReference Include="..\..\src\MrGameEng.Audio\MrGameEng.Audio.csproj" />
|
||||||
<ProjectReference Include="..\..\src\MrGameEng.Assets\MrGameEng.Assets.csproj" />
|
<ProjectReference Include="..\..\src\MrGameEng.Assets\MrGameEng.Assets.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\MrGameEng.UI\MrGameEng.UI.csproj" />
|
||||||
<ProjectReference Include="..\..\src\MrGameEng.Assets.Generator\MrGameEng.Assets.Generator.csproj"
|
<ProjectReference Include="..\..\src\MrGameEng.Assets.Generator\MrGameEng.Assets.Generator.csproj"
|
||||||
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -113,8 +113,9 @@ public sealed class SceneHotkeysSystem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>FPS и статистика рендера в заголовке окна (обновляется 4 раза в секунду).</summary>
|
/// <summary>FPS и статистика рендера: в HUD-лейбл и в заголовок окна (4 раза в секунду).</summary>
|
||||||
public sealed class TitleStatsSystem(EngineContext context, Renderer2D renderer, string sceneName) : BaseSystem
|
public sealed class StatsSystem(
|
||||||
|
EngineContext context, Renderer2D renderer, string sceneName, Myra.Graphics2D.UI.Label? hudLabel = null) : BaseSystem
|
||||||
{
|
{
|
||||||
private float _accumulated;
|
private float _accumulated;
|
||||||
private int _frames;
|
private int _frames;
|
||||||
@@ -131,8 +132,13 @@ public sealed class TitleStatsSystem(EngineContext context, Renderer2D renderer,
|
|||||||
var fps = _frames / _accumulated;
|
var fps = _frames / _accumulated;
|
||||||
_accumulated = 0f;
|
_accumulated = 0f;
|
||||||
_frames = 0;
|
_frames = 0;
|
||||||
context.Services.Get<GameWindow>().Title =
|
var stats =
|
||||||
$"MrGameEng Sample — {sceneName} | {fps:F0} FPS | sprites: {renderer.SubmittedSprites} | culled: {renderer.CulledSprites} | draw calls: {renderer.DrawCalls}";
|
$"{fps:F0} FPS | sprites: {renderer.SubmittedSprites} | culled: {renderer.CulledSprites} | draw calls: {renderer.DrawCalls}";
|
||||||
|
context.Services.Get<GameWindow>().Title = $"MrGameEng Sample — {sceneName} | {stats}";
|
||||||
|
if (hudLabel is not null)
|
||||||
|
{
|
||||||
|
hudLabel.Text = $"{sceneName}\n{stats}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ using MrGameEng.Audio;
|
|||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Input;
|
||||||
|
using MrGameEng.UI;
|
||||||
|
using Myra.Graphics2D.UI;
|
||||||
|
|
||||||
namespace MrGameEng.Sample.Scenes;
|
namespace MrGameEng.Sample.Scenes;
|
||||||
|
|
||||||
@@ -90,11 +92,32 @@ public sealed class MainScene : Scene
|
|||||||
var hud = new Sprite(shapeRegions[3], SampleLayers.Ui);
|
var hud = new Sprite(shapeRegions[3], SampleLayers.Ui);
|
||||||
Store.CreateEntity(new Transform2D(new Vector2(16f, 16f)), hud);
|
Store.CreateEntity(new Transform2D(new Vector2(16f, 16f)), hud);
|
||||||
|
|
||||||
|
// UI (Myra): HUD со статистикой, слайдер громкости, кнопка перехода. После UseRenderer2D!
|
||||||
|
var desktop = this.UseUI();
|
||||||
|
var statsLabel = new Label();
|
||||||
|
var volumeSlider = new HorizontalSlider { Minimum = 0f, Maximum = 1f, Value = audio.Music.Volume, Width = 180 };
|
||||||
|
volumeSlider.ValueChanged += (_, _) => audio.Music.Volume = volumeSlider.Value;
|
||||||
|
var switchButton = new Button { Content = new Label { Text = "Stress scene (Tab)" } };
|
||||||
|
switchButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
if (!Context.Scenes.IsTransitioning)
|
||||||
|
{
|
||||||
|
Context.Scenes.Switch(new StressScene(), Transition.Fade(0.8f));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var panel = new VerticalStackPanel { Left = 12, Top = 12, Spacing = 6 };
|
||||||
|
panel.Widgets.Add(statsLabel);
|
||||||
|
panel.Widgets.Add(new Label { Text = "Music volume (M — pause)" });
|
||||||
|
panel.Widgets.Add(volumeSlider);
|
||||||
|
panel.Widgets.Add(switchButton);
|
||||||
|
desktop.Root = panel;
|
||||||
|
|
||||||
UpdateSystems.Add(new PlayerControlSystem(player, actions, audio, beep));
|
UpdateSystems.Add(new PlayerControlSystem(player, actions, audio, beep));
|
||||||
UpdateSystems.Add(new BounceSystem(WorldBounds));
|
UpdateSystems.Add(new BounceSystem(WorldBounds));
|
||||||
UpdateSystems.Add(new CameraControlSystem(camera, player, input));
|
UpdateSystems.Add(new CameraControlSystem(camera, player, input));
|
||||||
UpdateSystems.Add(new SceneHotkeysSystem(Context, actions, () => new StressScene(), Transition.Fade(0.8f)));
|
UpdateSystems.Add(new SceneHotkeysSystem(Context, actions, () => new StressScene(), Transition.Fade(0.8f)));
|
||||||
UpdateSystems.Add(new TitleStatsSystem(Context, renderer, "Main"));
|
UpdateSystems.Add(new StatsSystem(Context, renderer, "Main", statsLabel));
|
||||||
|
|
||||||
var music = audio.Music;
|
var music = audio.Music;
|
||||||
if (!music.IsPlaying)
|
if (!music.IsPlaying)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using MrGameEng.Assets;
|
|||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Input;
|
||||||
|
using MrGameEng.UI;
|
||||||
|
|
||||||
namespace MrGameEng.Sample.Scenes;
|
namespace MrGameEng.Sample.Scenes;
|
||||||
|
|
||||||
@@ -52,10 +53,28 @@ public sealed class StressScene : Scene
|
|||||||
|
|
||||||
var camera = Store.CreateEntity(new Camera(Vector2.Zero, zoom: 0.3f));
|
var camera = Store.CreateEntity(new Camera(Vector2.Zero, zoom: 0.3f));
|
||||||
|
|
||||||
|
var desktop = this.UseUI();
|
||||||
|
var statsLabel = new Myra.Graphics2D.UI.Label();
|
||||||
|
var backButton = new Myra.Graphics2D.UI.Button
|
||||||
|
{
|
||||||
|
Content = new Myra.Graphics2D.UI.Label { Text = "Back to main (Tab)" },
|
||||||
|
};
|
||||||
|
backButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
if (!Context.Scenes.IsTransitioning)
|
||||||
|
{
|
||||||
|
Context.Scenes.Switch(new MainScene(), Transition.Wipe(0.8f, Color.DarkSlateBlue));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var panel = new Myra.Graphics2D.UI.VerticalStackPanel { Left = 12, Top = 12, Spacing = 6 };
|
||||||
|
panel.Widgets.Add(statsLabel);
|
||||||
|
panel.Widgets.Add(backButton);
|
||||||
|
desktop.Root = panel;
|
||||||
|
|
||||||
UpdateSystems.Add(new BounceSystem(WorldBounds));
|
UpdateSystems.Add(new BounceSystem(WorldBounds));
|
||||||
UpdateSystems.Add(new StressCameraSystem(camera, input));
|
UpdateSystems.Add(new StressCameraSystem(camera, input));
|
||||||
UpdateSystems.Add(new SceneHotkeysSystem(Context, actions, () => new MainScene(), Transition.Wipe(0.8f, Color.DarkSlateBlue)));
|
UpdateSystems.Add(new SceneHotkeysSystem(Context, actions, () => new MainScene(), Transition.Wipe(0.8f, Color.DarkSlateBlue)));
|
||||||
UpdateSystems.Add(new TitleStatsSystem(Context, renderer, $"Stress {SpriteCount:N0}"));
|
UpdateSystems.Add(new StatsSystem(Context, renderer, $"Stress {SpriteCount:N0}", statsLabel));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Только зум колесом — чтобы регулировать число видимых спрайтов.</summary>
|
/// <summary>Только зум колесом — чтобы регулировать число видимых спрайтов.</summary>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public class GameHost : Game
|
|||||||
Window.AllowUserResizing = _options.AllowResizing;
|
Window.AllowUserResizing = _options.AllowResizing;
|
||||||
Context.AttachGraphicsDevice(GraphicsDevice);
|
Context.AttachGraphicsDevice(GraphicsDevice);
|
||||||
Context.Services.Add(Window);
|
Context.Services.Add(Window);
|
||||||
|
Context.Services.Add<Game>(this);
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
Context.Scenes.Switch(_initialScene);
|
Context.Scenes.Switch(_initialScene);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Myra" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MrGameEng.Core\MrGameEng.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Friflo.Engine.ECS.Systems;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Myra;
|
||||||
|
using Myra.Graphics2D.UI;
|
||||||
|
using MrGameEng.Core;
|
||||||
|
|
||||||
|
namespace MrGameEng.UI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw system rendering the scene's Myra <see cref="Desktop"/>. Must run after the scene's
|
||||||
|
/// world rendering (register UI last in the draw phase) — the UI is drawn on top in window
|
||||||
|
/// pixels and also processes mouse/keyboard interaction during render.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class UiRenderSystem : BaseSystem
|
||||||
|
{
|
||||||
|
private readonly Desktop _desktop;
|
||||||
|
|
||||||
|
/// <summary>Creates the system for <paramref name="desktop"/>.</summary>
|
||||||
|
public UiRenderSystem(Desktop desktop) => _desktop = desktop;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnUpdateGroup() => _desktop.Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Wires the UI module (Myra) into a <see cref="Scene"/>.</summary>
|
||||||
|
public static class SceneUiExtensions
|
||||||
|
{
|
||||||
|
private static bool _environmentInitialized;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a Myra <see cref="Desktop"/> for this scene and registers
|
||||||
|
/// <see cref="UiRenderSystem"/> in the draw phase. Call from <c>OnLoad</c>
|
||||||
|
/// <b>after</b> <c>UseRenderer2D()</c> so the UI draws on top of the world.
|
||||||
|
/// Build the UI by assigning <see cref="Desktop.Root"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static Desktop UseUI(this Scene scene)
|
||||||
|
{
|
||||||
|
// Геттер MyraEnvironment.Game бросает исключение, пока Game не задан — проверять через ??= нельзя.
|
||||||
|
if (!_environmentInitialized)
|
||||||
|
{
|
||||||
|
MyraEnvironment.Game = scene.Context.Services.Get<Game>();
|
||||||
|
_environmentInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var desktop = new Desktop();
|
||||||
|
scene.DrawSystems.Add(new UiRenderSystem(desktop));
|
||||||
|
return desktop;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user