From 19ab3cbbc0b008d613aa6a1e11423bbdacf2058b Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 11 Jun 2026 15:25:02 +0300 Subject: [PATCH] Remove the sample project: LittleSim is the engine showcase now The MrGameEng.Sample demo game is deleted along with its assets; the LittleSim god-sim (which vendors this engine as a submodule) takes over as the living showcase where every engine feature is demonstrated. CLAUDE.md, README and docs updated; the demonstration rule now points to LittleSim, and the stress-scene perf figure is kept as a historical reference measurement. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 11 +- MrGameEng.sln | 17 -- README.md | 4 + docs/architecture.md | 21 +- docs/roadmap.md | 3 +- .../Assets/Atlases/Textures.atlas | 31 -- .../Assets/Atlases/Textures.atlas.0.png | Bin 3078 -> 0 bytes .../MrGameEng.Sample/Assets/Music/theme.ogg | Bin 14056 -> 0 bytes .../MrGameEng.Sample/Assets/Sounds/beep.wav | Bin 6660 -> 0 bytes .../Assets/Textures/player.png | Bin 760 -> 0 bytes .../Assets/Textures/shapes.png | Bin 933 -> 0 bytes .../MrGameEng.Sample/MrGameEng.Sample.csproj | 29 -- samples/MrGameEng.Sample/Program.cs | 16 - samples/MrGameEng.Sample/SampleShared.cs | 46 --- samples/MrGameEng.Sample/SampleSystems.cs | 219 -------------- samples/MrGameEng.Sample/Scenes/MainScene.cs | 200 ------------ .../Scenes/PathfindingScene.cs | 284 ------------------ .../MrGameEng.Sample/Scenes/StressScene.cs | 93 ------ 18 files changed, 27 insertions(+), 947 deletions(-) delete mode 100644 samples/MrGameEng.Sample/Assets/Atlases/Textures.atlas delete mode 100644 samples/MrGameEng.Sample/Assets/Atlases/Textures.atlas.0.png delete mode 100644 samples/MrGameEng.Sample/Assets/Music/theme.ogg delete mode 100644 samples/MrGameEng.Sample/Assets/Sounds/beep.wav delete mode 100644 samples/MrGameEng.Sample/Assets/Textures/player.png delete mode 100644 samples/MrGameEng.Sample/Assets/Textures/shapes.png delete mode 100644 samples/MrGameEng.Sample/MrGameEng.Sample.csproj delete mode 100644 samples/MrGameEng.Sample/Program.cs delete mode 100644 samples/MrGameEng.Sample/SampleShared.cs delete mode 100644 samples/MrGameEng.Sample/SampleSystems.cs delete mode 100644 samples/MrGameEng.Sample/Scenes/MainScene.cs delete mode 100644 samples/MrGameEng.Sample/Scenes/PathfindingScene.cs delete mode 100644 samples/MrGameEng.Sample/Scenes/StressScene.cs diff --git a/CLAUDE.md b/CLAUDE.md index 04d747f..6a86892 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,11 +11,15 @@ Keep them up to date when architecture or conventions change. ``` src/ MrGameEng.* engine libraries (one per functional area) -samples/ MrGameEng.Sample — demo game showcasing every engine feature tests/ xUnit test projects, one per engine library +tools/ CLI tools (atlas packer) docs/ architecture, conventions, roadmap (Russian) ``` +The engine has no sample project: the **LittleSim** game +(https://gitea.hsrv.site/mrleo1nid/LittleSim, this repo as the `engine/` submodule) +is the living showcase — new engine features are demonstrated there. + Engine modules: `Core` (game loop, ECS world, scenes, time), `Graphics` (custom batched renderer, camera, sprites), `Input`, `Audio`, `Assets` (runtime loading, no content pipeline), `Assets.Generator` (Roslyn source generator for typed asset handles), @@ -38,7 +42,6 @@ depends on `Graphics` (Texture2DRegion) and `Assets` (loader registration); ``` dotnet build MrGameEng.sln dotnet test MrGameEng.sln -dotnet run --project samples/MrGameEng.Sample ``` ## Architecture rules @@ -61,8 +64,8 @@ dotnet run --project samples/MrGameEng.Sample only through generated typed handles (`AssetRef`), never string paths. - New engine functionality goes into the matching module, or a new `MrGameEng.` library if it is a distinct area — never into `Core` by default. -- Every public engine feature must be demonstrated in `MrGameEng.Sample` - and covered by tests where logic is testable without a GPU. +- Every public engine feature must be covered by tests where logic is testable + without a GPU, and demonstrated in the LittleSim game (the engine's showcase). ## Code conventions diff --git a/MrGameEng.sln b/MrGameEng.sln index fc2374f..1e184bc 100644 --- a/MrGameEng.sln +++ b/MrGameEng.sln @@ -7,10 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Core", "src\MrGameEng.Core\MrGameEng.Core.csproj", "{09FF1FD5-7CCD-4A0B-AC0C-4910A2B5DF6B}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Sample", "samples\MrGameEng.Sample\MrGameEng.Sample.csproj", "{E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Core.Tests", "tests\MrGameEng.Core.Tests\MrGameEng.Core.Tests.csproj", "{2E9A054C-77A1-484B-BBFF-51FC40CD7D87}" @@ -79,18 +75,6 @@ Global {09FF1FD5-7CCD-4A0B-AC0C-4910A2B5DF6B}.Release|x64.Build.0 = Release|Any CPU {09FF1FD5-7CCD-4A0B-AC0C-4910A2B5DF6B}.Release|x86.ActiveCfg = Release|Any CPU {09FF1FD5-7CCD-4A0B-AC0C-4910A2B5DF6B}.Release|x86.Build.0 = Release|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Debug|x64.ActiveCfg = Debug|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Debug|x64.Build.0 = Debug|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Debug|x86.ActiveCfg = Debug|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Debug|x86.Build.0 = Debug|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Release|Any CPU.Build.0 = Release|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Release|x64.ActiveCfg = Release|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Release|x64.Build.0 = Release|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Release|x86.ActiveCfg = Release|Any CPU - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139}.Release|x86.Build.0 = Release|Any CPU {2E9A054C-77A1-484B-BBFF-51FC40CD7D87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E9A054C-77A1-484B-BBFF-51FC40CD7D87}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E9A054C-77A1-484B-BBFF-51FC40CD7D87}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -349,7 +333,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {09FF1FD5-7CCD-4A0B-AC0C-4910A2B5DF6B} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {E6C4D3FA-4FEF-4FA0-9AB3-42928E576139} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} {2E9A054C-77A1-484B-BBFF-51FC40CD7D87} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {1936FFA7-B642-4B1C-A01A-A3B25BBFCAD1} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {F3C7CE85-EF6B-4CEE-9042-50B8710EF656} = {0AB3BF05-4346-4AA6-1389-037BE0695223} diff --git a/README.md b/README.md index dde80c1..8d5b669 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ and [Friflo.Engine.ECS](https://github.com/friflo/Friflo.Engine.ECS) — ECS-fir Developer documentation (Russian): [docs/architecture.md](docs/architecture.md), [docs/roadmap.md](docs/roadmap.md). +Showcase: the [LittleSim](https://gitea.hsrv.site/mrleo1nid/LittleSim) game — a god-sim +built on this engine (vendored there as the `engine/` submodule) that demonstrates +every engine feature. + ## License MIT — see [LICENSE](LICENSE). diff --git a/docs/architecture.md b/docs/architecture.md index ae9612d..bb92500 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -20,8 +20,10 @@ аллоцировать память. Проверяется бенчмарками и code review. 3. **Модульность.** Движок разбит на библиотеки по функциональным областям. Игра подключает только то, что использует. -4. **Всё демонстрируется.** Каждая публичная возможность движка показана - в `MrGameEng.Sample` и покрыта тестами (где логика тестируема без GPU). +4. **Всё демонстрируется.** Каждая публичная возможность движка покрыта тестами + (где логика тестируема без GPU) и показана в игре + [LittleSim](https://gitea.hsrv.site/mrleo1nid/LittleSim) — живой витрине движка + (движок подключён туда сабмодулем `engine/`). ## Модули @@ -276,13 +278,14 @@ CLI-обёртка: `dotnet run --project tools/MrGameEng.AtlasTool -- <исто вдвое больше кадра): загрузка не ждёт, пока GPU дорисует предыдущий кадр; `Discard` — только на перемотке кольца. - **Тайминги фаз** кадра (submit/sort/build/upload/draw, мс) доступны как свойства - `Renderer2D` — выводятся в HUD стресс-сцены; оптимизации делаются только по ним. + `Renderer2D` — выводи их в HUD при оптимизации; решения только по замерам. - Текстурные атласы — первоклассный гражданин: `Sprite` хранит регион атласа, спрайты одного атласа батчатся автоматически. - Цель по производительности: ≥100k спрайтов при 60 FPS на среднем десктопе, - 0 аллокаций на кадр ниже порога параллелизма. Стресс-сцена Sample: 100k сущностей - (~61k в кадре) ≈ 297 FPS в Release. **Производительность измеряется только - в Release** — Debug-сборка медленнее в 5–6 раз (нет инлайнинга JIT). + 0 аллокаций на кадр ниже порога параллелизма. Эталонный замер (стресс-сцена, + до переноса демо в LittleSim): 100k сущностей, ~61k в кадре ≈ 297 FPS в Release. + **Производительность измеряется только в Release** — Debug-сборка медленнее + в 5–6 раз (нет инлайнинга JIT). ## Сцены и переходы @@ -323,11 +326,15 @@ Game.Draw ──► Scene.Draw ──► SystemRoot (Draw-фаза): кам ``` MrGameEng.sln src/ библиотеки движка (MrGameEng.*) -samples/ MrGameEng.Sample — демо всех возможностей tests/ xUnit-тесты, по проекту на библиотеку +tools/ CLI-инструменты (упаковщик атласов) docs/ документация (русский) ``` +Демо-проекта в движке нет: витрина движка — игра +[LittleSim](https://gitea.hsrv.site/mrleo1nid/LittleSim) (движок подключён туда +сабмодулем `engine/`). + ## Зафиксированные версии | Зависимость | Версия | Назначение | diff --git a/docs/roadmap.md b/docs/roadmap.md index 05ed5ba..e97bfbd 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -3,7 +3,8 @@ Базовый движок (ядро, рендер, ассеты с кодогенерацией, ввод, звук, демо) реализован — текущие возможности описаны в [architecture.md](architecture.md). Здесь — только планы. -Пункт считается завершённым, когда покрыт тестами и показан в `MrGameEng.Sample`; +Пункт считается завершённым, когда покрыт тестами и показан в игре +[LittleSim](https://gitea.hsrv.site/mrleo1nid/LittleSim) — витрине движка; после завершения он убирается отсюда и фиксируется в architecture.md. ## Ближайшее diff --git a/samples/MrGameEng.Sample/Assets/Atlases/Textures.atlas b/samples/MrGameEng.Sample/Assets/Atlases/Textures.atlas deleted file mode 100644 index 0ae5c9b..0000000 --- a/samples/MrGameEng.Sample/Assets/Atlases/Textures.atlas +++ /dev/null @@ -1,31 +0,0 @@ -{ - "version": 1, - "name": "Textures", - "pageSize": 2048, - "padding": 2, - "pages": [ - { - "file": "Textures.atlas.0.png", - "width": 256, - "height": 128 - } - ], - "regions": [ - { - "key": "player", - "page": 0, - "x": 68, - "y": 2, - "w": 64, - "h": 32 - }, - { - "key": "shapes", - "page": 0, - "x": 2, - "y": 2, - "w": 64, - "h": 64 - } - ] -} \ No newline at end of file diff --git a/samples/MrGameEng.Sample/Assets/Atlases/Textures.atlas.0.png b/samples/MrGameEng.Sample/Assets/Atlases/Textures.atlas.0.png deleted file mode 100644 index 6e8632d3c3e4f948b486678c736c3740e1b00887..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3078 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 diff --git a/samples/MrGameEng.Sample/Assets/Sounds/beep.wav b/samples/MrGameEng.Sample/Assets/Sounds/beep.wav deleted file mode 100644 index 2f2e70e56259ec8a955354406072344d2b0a855c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6660 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 Di3prQ diff --git a/samples/MrGameEng.Sample/MrGameEng.Sample.csproj b/samples/MrGameEng.Sample/MrGameEng.Sample.csproj deleted file mode 100644 index 7565eca..0000000 --- a/samples/MrGameEng.Sample/MrGameEng.Sample.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - WinExe - net8.0 - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/MrGameEng.Sample/Program.cs b/samples/MrGameEng.Sample/Program.cs deleted file mode 100644 index 15145c3..0000000 --- a/samples/MrGameEng.Sample/Program.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.Xna.Framework; -using MrGameEng.Core; -using MrGameEng.Sample.Scenes; - -using var host = new GameHost( - new GameHostOptions - { - Title = "MrGameEng Sample", - Width = 1280, - Height = 720, - ClearColor = new Color(24, 26, 32), - VSync = false, // техдемо: показываем реальный FPS, не ограниченный частотой монитора - }, - new MainScene()); - -host.Run(); diff --git a/samples/MrGameEng.Sample/SampleShared.cs b/samples/MrGameEng.Sample/SampleShared.cs deleted file mode 100644 index 43b0853..0000000 --- a/samples/MrGameEng.Sample/SampleShared.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Friflo.Engine.ECS; -using Microsoft.Xna.Framework; -using MrGameEng.Graphics; - -namespace MrGameEng.Sample; - -/// Действия игрока, привязанные к клавишам через ActionMap. -public enum SampleAction -{ - MoveLeft, - MoveRight, - MoveUp, - MoveDown, - Jump, - Pause, - ToggleMusic, - SwitchScene, -} - -/// Скорость для движущихся сущностей сэмпла. -public struct Velocity : IComponent -{ - public Vector2 Value; -} - -/// Слои рендера сэмпла; регистрируются один раз на общий Renderer2D. -public static class SampleLayers -{ - public static LayerId Actors { get; private set; } - - public static LayerId Ui { get; private set; } - - private static bool _registered; - - public static void EnsureRegistered(Renderer2D renderer) - { - if (_registered) - { - return; - } - - Actors = renderer.Layers.Register("Actors", LayerSpace.World, LayerSortMode.YSort); - Ui = renderer.Layers.Register("UI", LayerSpace.Screen); - _registered = true; - } -} diff --git a/samples/MrGameEng.Sample/SampleSystems.cs b/samples/MrGameEng.Sample/SampleSystems.cs deleted file mode 100644 index 454391c..0000000 --- a/samples/MrGameEng.Sample/SampleSystems.cs +++ /dev/null @@ -1,219 +0,0 @@ -using Friflo.Engine.ECS; -using Friflo.Engine.ECS.Systems; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Audio; -using Microsoft.Xna.Framework.Input; -using MrGameEng.Audio; -using MrGameEng.Core; -using MrGameEng.Graphics; -using MrGameEng.Input; - -namespace MrGameEng.Sample; - -/// Управление игроком (WASD/стрелки) и прыжок-писк на пробел. -public sealed class PlayerControlSystem( - Entity player, ActionMap actions, AudioManager audio, SoundEffect beep, - MrGameEng.DevConsole.DevConsole console) : BaseSystem -{ - protected override void OnUpdateGroup() - { - if (console.IsOpen) - { - return; - } - - ref var transform = ref player.GetComponent(); - var move = new Vector2( - actions.GetAxis(SampleAction.MoveLeft, SampleAction.MoveRight), - actions.GetAxis(SampleAction.MoveUp, SampleAction.MoveDown)); - - if (move != Vector2.Zero) - { - move.Normalize(); - transform.Position += move * 260f * Tick.deltaTime; - } - - if (actions.IsPressed(SampleAction.Jump)) - { - audio.Play(beep); - } - } -} - -/// Камера следует за игроком; колесо — зум, Q/E — поворот. -public sealed class CameraControlSystem(Entity cameraEntity, Entity player, InputManager input) : BaseSystem -{ - protected override void OnUpdateGroup() - { - ref var camera = ref cameraEntity.GetComponent(); - var target = player.GetComponent().Position; - - camera.Position = Vector2.Lerp(camera.Position, target, Math.Min(1f, 6f * Tick.deltaTime)); - camera.Zoom = Math.Clamp(camera.Zoom * (1f + input.WheelDelta * 0.001f), 0.2f, 5f); - - var rotate = (input.IsKeyDown(Keys.E) ? 1f : 0f) - (input.IsKeyDown(Keys.Q) ? 1f : 0f); - camera.Rotation += rotate * 1.2f * Tick.deltaTime; - } -} - -/// -/// Отскок сущностей со скоростью от границ мира. На больших количествах работа режется -/// на сегменты по всем ядрам (чанк Friflo держит весь архетип — сам по себе он слишком крупный). -/// -public sealed class BounceSystem(RectF bounds) : QuerySystem -{ - private const int ParallelThreshold = 8192; - private const int SegmentSize = 8192; - - private readonly List<(Chunk Transforms, Chunk Velocities)> _chunks = []; - private readonly List<(int Chunk, int Start, int Length)> _segments = []; - - protected override void OnUpdate() - { - var delta = Tick.deltaTime; - _chunks.Clear(); - var total = 0; - foreach (var (transforms, velocities, _) in Query.Chunks) - { - _chunks.Add((transforms, velocities)); - total += transforms.Length; - } - - if (total < ParallelThreshold) - { - foreach (var (transforms, velocities) in _chunks) - { - Move(transforms, velocities, 0, transforms.Length, delta); - } - - return; - } - - _segments.Clear(); - for (var c = 0; c < _chunks.Count; c++) - { - var length = _chunks[c].Transforms.Length; - for (var start = 0; start < length; start += SegmentSize) - { - _segments.Add((c, start, Math.Min(SegmentSize, length - start))); - } - } - - Parallel.For(0, _segments.Count, i => - { - var (chunk, start, length) = _segments[i]; - Move(_chunks[chunk].Transforms, _chunks[chunk].Velocities, start, length, delta); - }); - } - - private void Move(Chunk transforms, Chunk velocities, int start, int length, float delta) - { - var t = transforms.Span.Slice(start, length); - var v = velocities.Span.Slice(start, length); - for (var i = 0; i < t.Length; i++) - { - ref var position = ref t[i].Position; - ref var velocity = ref v[i].Value; - position += velocity * delta; - - if (position.X < bounds.Left || position.X > bounds.Right) - { - velocity.X = -velocity.X; - position.X = Math.Clamp(position.X, bounds.Left, bounds.Right); - } - - if (position.Y < bounds.Top || position.Y > bounds.Bottom) - { - velocity.Y = -velocity.Y; - position.Y = Math.Clamp(position.Y, bounds.Top, bounds.Bottom); - } - } - } -} - -/// Пауза (P), музыка (M), переключение сцены с переходом (Tab). -public sealed class SceneHotkeysSystem( - EngineContext context, ActionMap actions, Func nextScene, Transition transition) : BaseSystem -{ - protected override void OnUpdateGroup() - { - if (context.Services.Get().IsOpen) - { - return; - } - - if (actions.IsPressed(SampleAction.Pause)) - { - context.Clock.TimeScale = context.Clock.TimeScale > 0f ? 0f : 1f; - } - - if (actions.IsPressed(SampleAction.ToggleMusic)) - { - var music = context.Services.Get().Music; - if (music.IsPlaying) - { - music.Pause(); - } - else - { - music.Resume(); - } - } - - if (actions.IsPressed(SampleAction.SwitchScene) && !context.Scenes.IsTransitioning) - { - context.Scenes.Switch(nextScene(), transition); - } - } -} - -/// FPS и статистика рендера: в HUD-лейбл и в заголовок окна (4 раза в секунду). -public sealed class StatsSystem( - EngineContext context, Renderer2D renderer, string sceneName, Myra.Graphics2D.UI.Label? hudLabel = null) : BaseSystem -{ - private float _accumulated; - private int _frames; - - protected override void OnUpdateGroup() - { - _accumulated += context.Clock.UnscaledDeltaTime; - _frames++; - if (_accumulated < 0.25f) - { - return; - } - - var fps = _frames / _accumulated; - _accumulated = 0f; - _frames = 0; - var stats = - $"{fps:F0} FPS | sprites: {renderer.SubmittedSprites} | culled: {renderer.CulledSprites} | draw calls: {renderer.DrawCalls}"; - context.Services.Get().Title = $"MrGameEng Sample — {sceneName} | {stats}"; - if (hudLabel is not null) - { - hudLabel.Text = - $"{sceneName}\n{stats}\n" + - $"submit {renderer.SubmitMs:F2} | sort {renderer.SortMs:F2} | build {renderer.BuildMs:F2} | " + - $"upload {renderer.UploadMs:F2} | draw {renderer.DrawMs:F2} (ms)"; - } - } -} - -/// Общая настройка ввода для сцен сэмпла. -public static class SampleInput -{ - public static ActionMap CreateActions(InputManager input) => - new ActionMap(input) - .Bind(SampleAction.MoveLeft, Keys.A) - .Bind(SampleAction.MoveLeft, Keys.Left) - .Bind(SampleAction.MoveRight, Keys.D) - .Bind(SampleAction.MoveRight, Keys.Right) - .Bind(SampleAction.MoveUp, Keys.W) - .Bind(SampleAction.MoveUp, Keys.Up) - .Bind(SampleAction.MoveDown, Keys.S) - .Bind(SampleAction.MoveDown, Keys.Down) - .Bind(SampleAction.Jump, Keys.Space) - .Bind(SampleAction.Pause, Keys.P) - .Bind(SampleAction.ToggleMusic, Keys.M) - .Bind(SampleAction.SwitchScene, Keys.Tab); -} diff --git a/samples/MrGameEng.Sample/Scenes/MainScene.cs b/samples/MrGameEng.Sample/Scenes/MainScene.cs deleted file mode 100644 index 3513e16..0000000 --- a/samples/MrGameEng.Sample/Scenes/MainScene.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Friflo.Engine.ECS; -using Microsoft.Xna.Framework; -using MrGameEng.Assets; -using MrGameEng.Atlases; -using MrGameEng.Audio; -using MrGameEng.Core; -using MrGameEng.DevConsole; -using MrGameEng.Graphics; -using MrGameEng.Input; -using MrGameEng.Tilemaps; -using MrGameEng.UI; -using Myra.Graphics2D.UI; - -namespace MrGameEng.Sample.Scenes; - -/// -/// Интерактивная демо-сцена: ассеты через сгенерированные хендлы, анимация, слои -/// (мир + Y-sort + screen-space HUD), камера с зумом/поворотом, ввод, звук и музыка. -/// WASD — игрок, колесо — зум, Q/E — поворот, Space — звук, P — пауза, M — музыка, Tab — стресс-сцена. -/// -public sealed class MainScene : Scene -{ - private const int DecorCount = 1500; - private static readonly RectF WorldBounds = new(-2000f, -2000f, 4000f, 4000f); - - protected override void OnLoad() - { - var assets = Context.Services.GetOrDefault() ?? Context.UseAssets(); - var audio = Context.Services.GetOrDefault() ?? Context.UseAudio(); - var input = this.UseInput(); - var actions = SampleInput.CreateActions(input); - this.UseSpriteAnimation(); - - var renderer = this.UseRenderer2D(new Renderer2DOptions { VirtualResolution = new Point(1280, 720) }); - SampleLayers.EnsureRegistered(renderer); - this.UseTilemaps(); - - var playerTexture = assets.Load(GameAssets.Textures.Player); - var shapesTexture = assets.Load(GameAssets.Textures.Shapes); - var beep = assets.Load(GameAssets.Sounds.Beep); - - // Текстурный атлас: Assets/Atlases собран утилитой MrGameEng.AtlasTool из Assets/Textures - // (см. README). Регионы адресуются исходным путём без расширения и батчатся в один draw call. - Context.UseTextureAtlases(); - var atlas = assets.Load(GameAssets.Atlases.Textures); - - var shapeRegions = new[] - { - new Texture2DRegion(shapesTexture, new Rectangle(0, 0, 32, 32)), - new Texture2DRegion(shapesTexture, new Rectangle(32, 0, 32, 32)), - new Texture2DRegion(shapesTexture, new Rectangle(0, 32, 32, 32)), - new Texture2DRegion(shapesTexture, new Rectangle(32, 32, 32, 32)), - }; - - // Тайловая площадка под стартовой зоной: шахматный пол, строится кодом из TileSet/TileGrid. - // Видимые клетки сабмитятся каждый кадр; Depth -10 кладёт пол под декорации. - var floorTiles = new TileSet(); - var floorA = floorTiles.Add(shapeRegions[0], new Color(70, 75, 95)); - var floorB = floorTiles.Add(shapeRegions[2], new Color(55, 60, 78)); - var floorGrid = new TileGrid(24, 14); - for (var y = 0; y < floorGrid.Height; y++) - { - for (var x = 0; x < floorGrid.Width; x++) - { - floorGrid[x, y] = (x + y) % 2 == 0 ? floorA : floorB; - } - } - - Store.CreateEntity(new Tilemap(floorGrid, floorTiles, tileSize: 48f) - { - Origin = new Vector2(-24 * 24f, -14 * 24f), - Depth = -10f, - }); - - // Декорации по всему миру: уезжаешь камерой — попадают под culling (см. заголовок окна). - var random = new Random(42); - for (var i = 0; i < DecorCount; i++) - { - var sprite = new Sprite(shapeRegions[random.Next(shapeRegions.Length)]); - sprite.CenterOrigin(); - sprite.Color = Color.White * 0.35f; - Store.CreateEntity( - new Transform2D( - new Vector2( - random.NextSingle() * WorldBounds.Width + WorldBounds.Left, - random.NextSingle() * WorldBounds.Height + WorldBounds.Top), - rotation: random.NextSingle() * MathF.Tau, - scale: new Vector2(0.5f + random.NextSingle())), - sprite); - } - - // Бродячие "существа" на Y-sort слое: кто ниже на экране — тот ближе. - for (var i = 0; i < 200; i++) - { - var sprite = new Sprite(shapeRegions[random.Next(shapeRegions.Length)], SampleLayers.Actors); - sprite.CenterOrigin(); - var angle = random.NextSingle() * MathF.Tau; - Store.CreateEntity( - new Transform2D(new Vector2(random.Next(-600, 600), random.Next(-400, 400))), - sprite, - new Velocity { Value = new Vector2(MathF.Cos(angle), MathF.Sin(angle)) * (40f + random.Next(80)) }); - } - - // Игрок: анимированный спрайт (2 кадра из атласа player.png), Y-sort слой. - var playerSprite = new Sprite(new Texture2DRegion(playerTexture, new Rectangle(0, 0, 32, 32)), SampleLayers.Actors); - playerSprite.CenterOrigin(); - var blink = new SpriteAnimationClip( - [ - new Texture2DRegion(playerTexture, new Rectangle(0, 0, 32, 32)), - new Texture2DRegion(playerTexture, new Rectangle(32, 0, 32, 32)), - ], framesPerSecond: 3f); - - var player = Store.CreateEntity( - new Transform2D(Vector2.Zero, scale: new Vector2(2f)), - playerSprite, - new SpriteAnimator(blink)); - - // Пара спрайтов из атласа рядом со стартом игрока — вся пара рисуется одним draw call. - var atlasShowcase = new[] { ("player", -80f), ("shapes", 80f) }; - foreach (var (key, offsetX) in atlasShowcase) - { - var sprite = new Sprite(atlas.GetRegion(key), SampleLayers.Actors); - sprite.CenterOrigin(); - Store.CreateEntity(new Transform2D(new Vector2(offsetX, -120f)), sprite); - } - - var camera = Store.CreateEntity(new Camera(Vector2.Zero, zoom: 1f, bounds: WorldBounds)); - - // HUD: золотой квадрат в углу на screen-space слое — не двигается с камерой. - var hud = new Sprite(shapeRegions[3], SampleLayers.Ui); - 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; - - // Консоль разработчика (клавиша `) — последней, чтобы рисовалась поверх UI. - var console = this.UseDevConsole(); - console.Register("stress", "switch to the stress scene", (_, _) => - { - if (!Context.Scenes.IsTransitioning) - { - Context.Scenes.Switch(new StressScene(), Transition.Fade(0.8f)); - } - }); - console.Register("main", "switch to the main scene", (_, _) => - { - if (!Context.Scenes.IsTransitioning) - { - Context.Scenes.Switch(new MainScene(), Transition.Fade(0.8f)); - } - }); - console.Register("beep", "play the beep sound", (_, _) => audio.Play(beep)); - console.Register("path", "switch to the pathfinding & collisions demo", (_, _) => - { - if (!Context.Scenes.IsTransitioning) - { - Context.Scenes.Switch(new PathfindingScene(), Transition.Fade(0.6f)); - } - }); - console.Register("atlas", "list texture atlas regions", (c, _) => - { - c.WriteLine($"atlas '{atlas.Name}': {atlas.Pages.Count} page(s), {atlas.Regions.Count} region(s)"); - foreach (var (key, region) in atlas.Regions.OrderBy(r => r.Key, StringComparer.Ordinal)) - { - c.WriteLine($" {key}: {region.Bounds.Width}x{region.Bounds.Height} at ({region.Bounds.X},{region.Bounds.Y})"); - } - }); - - UpdateSystems.Add(new PlayerControlSystem(player, actions, audio, beep, console)); - UpdateSystems.Add(new BounceSystem(WorldBounds)); - UpdateSystems.Add(new CameraControlSystem(camera, player, input)); - UpdateSystems.Add(new SceneHotkeysSystem(Context, actions, () => new StressScene(), Transition.Fade(0.8f))); - UpdateSystems.Add(new StatsSystem(Context, renderer, "Main", statsLabel)); - - var music = audio.Music; - if (!music.IsPlaying) - { - music.Volume = 0.4f; - music.Play(assets.Load(GameAssets.Music.Theme)); - } - } -} diff --git a/samples/MrGameEng.Sample/Scenes/PathfindingScene.cs b/samples/MrGameEng.Sample/Scenes/PathfindingScene.cs deleted file mode 100644 index 3baa2c3..0000000 --- a/samples/MrGameEng.Sample/Scenes/PathfindingScene.cs +++ /dev/null @@ -1,284 +0,0 @@ -using Friflo.Engine.ECS; -using Friflo.Engine.ECS.Systems; -using Microsoft.Xna.Framework; -using MrGameEng.Assets; -using MrGameEng.Collisions; -using MrGameEng.Core; -using MrGameEng.DevConsole; -using MrGameEng.Graphics; -using MrGameEng.Input; -using MrGameEng.Pathfinding; -using MrGameEng.UI; - -namespace MrGameEng.Sample.Scenes; - -/// -/// Демо поиска пути и коллизий: ЛКМ ставит цель — толпа агентов стекается к ней по -/// flow field, жёлтым подсвечен A*-путь из левого верхнего угла, столкнувшиеся агенты -/// вспыхивают красным. Tab/'main' — назад. -/// -public sealed class PathfindingScene : Scene -{ - private const int CellsX = 64; - private const int CellsY = 36; - private const int CellSize = 20; - private const int AgentCount = 250; - private static readonly Point AStarStart = new(1, 1); - - private readonly WallGrid _grid = new(CellsX, CellsY, seed: 99); - private readonly FlowField _field = new(); - private readonly List _path = []; - private readonly List _pathMarkers = []; - private FlowFieldBuilder _builder = null!; - private GridPathfinder _pathfinder = null!; - private Texture2DRegion _white = null!; - private Entity _goalMarker; - - /// Состояние агента: время красной вспышки после столкновения. - private struct AgentState : IComponent - { - public float Flash; - } - - protected override void OnLoad() - { - var assets = Context.Services.GetOrDefault() ?? Context.UseAssets(); - var input = this.UseInput(); - var actions = SampleInput.CreateActions(input); - var renderer = this.UseRenderer2D(new Renderer2DOptions { VirtualResolution = new Point(1280, 720) }); - SampleLayers.EnsureRegistered(renderer); - - var shapesTexture = assets.Load(GameAssets.Textures.Shapes); - _white = new Texture2DRegion(shapesTexture, new Rectangle(40, 41, 14, 14)); // жёлтый квадрат как "белый" регион - _builder = new FlowFieldBuilder(_grid); - _pathfinder = new GridPathfinder(_grid); - - // Стены. - for (var y = 0; y < CellsY; y++) - { - for (var x = 0; x < CellsX; x++) - { - if (!_grid.IsPassable(x, y)) - { - var sprite = new Sprite(_white) { Color = new Color(70, 74, 84), Depth = 0f }; - Store.CreateEntity(CellTransform(x, y), sprite); - } - } - } - - // Агенты на проходимых клетках. - var random = new Random(7); - for (var i = 0; i < AgentCount; i++) - { - var cell = RandomPassableCell(random); - var sprite = new Sprite(_white) { Color = new Color(120, 180, 255), Depth = 1f }; - sprite.CenterOrigin(); - Store.CreateEntity( - new Transform2D(CellCenter(cell), scale: new Vector2(0.45f)), - sprite, - new AgentState(), - Collider.Circle(4f)); - } - - // Маркер цели. - var goalSprite = new Sprite(_white) { Color = new Color(80, 220, 120), Depth = 0.6f }; - _goalMarker = Store.CreateEntity(CellTransform(0, 0), goalSprite); - - var world = this.UseCollisions(cellSize: 16f); - - UpdateSystems.Add(new ClickTargetSystem(this, input, renderer)); - UpdateSystems.Add(new AgentSteerSystem(this)); - UpdateSystems.Add(new CollisionFlashSystem(world)); - UpdateSystems.Add(new FlashDecaySystem()); - UpdateSystems.Add(new SceneHotkeysSystem(Context, actions, () => new MainScene(), Transition.Fade(0.6f))); - - var desktop = this.UseUI(); - var label = new Myra.Graphics2D.UI.Label - { - Text = "ЛКМ — цель: толпа идёт по flow field, жёлтое — путь A*.\nСтолкновения агентов подсвечиваются красным. Tab — назад.", - }; - desktop.Root = new Myra.Graphics2D.UI.VerticalStackPanel { Left = 12, Top = 12 }; - ((Myra.Graphics2D.UI.VerticalStackPanel)desktop.Root).Widgets.Add(label); - - this.UseDevConsole(); - - SetGoal(new Point(CellsX / 2, CellsY / 2)); - } - - private void SetGoal(Point goal) - { - if (!_grid.IsPassable(goal.X, goal.Y)) - { - return; - } - - _builder.Build([goal], _field); - _goalMarker.GetComponent().Position = CellTopLeft(goal); - - foreach (var marker in _pathMarkers) - { - marker.DeleteEntity(); - } - - _pathMarkers.Clear(); - if (_pathfinder.FindPath(AStarStart, goal, _path)) - { - foreach (var cell in _path) - { - var sprite = new Sprite(_white) { Color = new Color(240, 210, 60) * 0.55f, Depth = 0.5f }; - _pathMarkers.Add(Store.CreateEntity(CellTransform(cell.X, cell.Y), sprite)); - } - } - } - - private static Transform2D CellTransform(int x, int y) => - new(CellTopLeft(new Point(x, y)), scale: new Vector2(CellSize / 14f)); - - private static Vector2 CellTopLeft(Point cell) => new(cell.X * CellSize, cell.Y * CellSize); - - private static Vector2 CellCenter(Point cell) => - new(cell.X * CellSize + CellSize / 2f, cell.Y * CellSize + CellSize / 2f); - - private Point RandomPassableCell(Random random) - { - while (true) - { - var cell = new Point(random.Next(CellsX), random.Next(CellsY)); - if (_grid.IsPassable(cell.X, cell.Y)) - { - return cell; - } - } - } - - /// Случайные прямоугольные стены; старт A* и центр всегда свободны. - private sealed class WallGrid : IPathGrid - { - private readonly bool[,] _walls; - - public WallGrid(int width, int height, int seed) - { - Width = width; - Height = height; - _walls = new bool[width, height]; - var random = new Random(seed); - for (var i = 0; i < 70; i++) - { - var w = random.Next(1, 7); - var h = random.Next(1, 7); - var x0 = random.Next(width - w); - var y0 = random.Next(height - h); - for (var y = y0; y < y0 + h; y++) - { - for (var x = x0; x < x0 + w; x++) - { - _walls[x, y] = true; - } - } - } - - for (var y = 0; y < 3; y++) - { - for (var x = 0; x < 3; x++) - { - _walls[AStarStart.X + x, AStarStart.Y + y] = false; - _walls[width / 2 + x - 1, height / 2 + y - 1] = false; - } - } - } - - public int Width { get; } - - public int Height { get; } - - public bool IsPassable(int x, int y) => !_walls[x, y]; - - public float Cost(int x, int y) => 1f; - } - - private sealed class ClickTargetSystem(PathfindingScene scene, InputManager input, Renderer2D renderer) : BaseSystem - { - protected override void OnUpdateGroup() - { - if (!input.IsMousePressed(MouseButton.Left)) - { - return; - } - - var world = renderer.ScreenToWorld(input.MousePosition.ToVector2()); - var cell = new Point((int)(world.X / CellSize), (int)(world.Y / CellSize)); - if (cell.X >= 0 && cell.X < CellsX && cell.Y >= 0 && cell.Y < CellsY) - { - scene.SetGoal(cell); - } - } - } - - private sealed class AgentSteerSystem(PathfindingScene scene) : QuerySystem - { - private const float Speed = 70f; - - protected override void OnUpdate() - { - var delta = Tick.deltaTime; - var field = scene._field; - foreach (var (transforms, _, _) in Query.Chunks) - { - var t = transforms.Span; - for (var i = 0; i < t.Length; i++) - { - ref var position = ref t[i].Position; - var cx = Math.Clamp((int)(position.X / CellSize), 0, CellsX - 1); - var cy = Math.Clamp((int)(position.Y / CellSize), 0, CellsY - 1); - if (!field.IsReachable(cx, cy) || field.DistanceAt(cx, cy) <= 0.6f) - { - continue; // недостижимо или уже у цели - } - - position += field.DirectionAt(cx, cy) * (Speed * delta); - } - } - } - } - - private sealed class CollisionFlashSystem(CollisionWorld world) : BaseSystem - { - protected override void OnUpdateGroup() - { - foreach (var pair in world.Pairs) - { - Flash(pair.A); - Flash(pair.B); - } - } - - private static void Flash(Entity entity) - { - if (entity.HasComponent()) - { - entity.GetComponent().Flash = 0.25f; - } - } - } - - private sealed class FlashDecaySystem : QuerySystem - { - private static readonly Color Calm = new(120, 180, 255); - private static readonly Color Hit = new(235, 70, 60); - - protected override void OnUpdate() - { - var delta = Tick.deltaTime; - foreach (var (states, sprites, _) in Query.Chunks) - { - var a = states.Span; - var s = sprites.Span; - for (var i = 0; i < a.Length; i++) - { - a[i].Flash = Math.Max(0f, a[i].Flash - delta); - s[i].Color = a[i].Flash > 0f ? Hit : Calm; - } - } - } - } -} diff --git a/samples/MrGameEng.Sample/Scenes/StressScene.cs b/samples/MrGameEng.Sample/Scenes/StressScene.cs deleted file mode 100644 index d28a282..0000000 --- a/samples/MrGameEng.Sample/Scenes/StressScene.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Friflo.Engine.ECS; -using Microsoft.Xna.Framework; -using MrGameEng.Assets; -using MrGameEng.Core; -using MrGameEng.DevConsole; -using MrGameEng.Graphics; -using MrGameEng.Input; -using MrGameEng.UI; - -namespace MrGameEng.Sample.Scenes; - -/// -/// Стресс-сцена: 100 000 спрайтов скачут в мире 4000×4000. Колесо — зум (чем дальше, -/// тем больше спрайтов в кадре), Tab — обратно в основную сцену. Цель: 60 FPS. -/// -public sealed class StressScene : Scene -{ - private const int SpriteCount = 100_000; - private static readonly RectF WorldBounds = new(-2000f, -2000f, 4000f, 4000f); - - protected override void OnLoad() - { - var assets = Context.Services.Get(); - var input = this.UseInput(); - var actions = SampleInput.CreateActions(input); - - var renderer = this.UseRenderer2D(); - SampleLayers.EnsureRegistered(renderer); - - var shapesTexture = assets.Load(GameAssets.Textures.Shapes); - var regions = new[] - { - new Texture2DRegion(shapesTexture, new Rectangle(0, 0, 32, 32)), - new Texture2DRegion(shapesTexture, new Rectangle(32, 0, 32, 32)), - new Texture2DRegion(shapesTexture, new Rectangle(0, 32, 32, 32)), - new Texture2DRegion(shapesTexture, new Rectangle(32, 32, 32, 32)), - }; - - var random = new Random(7); - for (var i = 0; i < SpriteCount; i++) - { - var sprite = new Sprite(regions[random.Next(regions.Length)]); - sprite.CenterOrigin(); - var angle = random.NextSingle() * MathF.Tau; - Store.CreateEntity( - new Transform2D( - new Vector2( - random.NextSingle() * WorldBounds.Width + WorldBounds.Left, - random.NextSingle() * WorldBounds.Height + WorldBounds.Top), - scale: new Vector2(0.4f + random.NextSingle() * 0.6f)), - sprite, - new Velocity { Value = new Vector2(MathF.Cos(angle), MathF.Sin(angle)) * (30f + random.Next(150)) }); - } - - 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; - - this.UseDevConsole(); // поверх UI - - UpdateSystems.Add(new BounceSystem(WorldBounds)); - UpdateSystems.Add(new StressCameraSystem(camera, input)); - UpdateSystems.Add(new SceneHotkeysSystem(Context, actions, () => new MainScene(), Transition.Wipe(0.8f, Color.DarkSlateBlue))); - UpdateSystems.Add(new StatsSystem(Context, renderer, $"Stress {SpriteCount:N0}", statsLabel)); - } - - /// Только зум колесом — чтобы регулировать число видимых спрайтов. - private sealed class StressCameraSystem(Entity cameraEntity, InputManager input) - : Friflo.Engine.ECS.Systems.BaseSystem - { - protected override void OnUpdateGroup() - { - ref var camera = ref cameraEntity.GetComponent(); - camera.Zoom = Math.Clamp(camera.Zoom * (1f + input.WheelDelta * 0.001f), 0.05f, 5f); - } - } -}