Extend the Lighting module with a per-cell lightmap. LightmapBuilder (pure, tested) fills an ambient base, shades occluder cells, and adds point lights that attenuate with distance and are blocked by occluders between source and cell (grid-traced soft shadows). Lightmap holds the grid plus a greyscale texture (Upload) and a bilinear SampleAt for the simulation; a PointLight component places lights in the world. LightmapSystem rebuilds the grid a few times a second (ambient from the day/night cycle with a night floor, occluders from a game-supplied grid, point lights from the ECS); LightmapRenderSystem multiplies the lightmap over the world after the sprite flush and under the HUD. Wired via scene.UseLighting(...), sampled through Lighting.SampleAt. Docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.8 KiB
mrgameeng
2D game engine built on MonoGame 3.8.4 (DesktopGL), .NET 8, C#. ECS-first: Friflo.Engine.ECS 3.6 is tightly integrated into the core — all gameplay state lives in components, all logic in systems.
Design docs and developer documentation live in docs/ and are written in Russian.
Keep them up to date when architecture or conventions change.
Solution layout
src/ MrGameEng.* engine libraries (grouped by role, not one-per-feature)
tests/ xUnit test projects, one per engine library
tools/ CLI tools (atlas packer)
docs/ architecture, conventions, roadmap (Russian)
Libraries are grouped by architectural role rather than split per feature, to avoid a
sprawl of two-file projects. A feature lives in a subfolder of its host library and
keeps its own MrGameEng.<Feature> namespace (namespaces are independent of the
assembly), so using MrGameEng.Tilemaps; etc. still resolve after a merge.
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 libraries (each feature is a namespaced subfolder of its host):
Core— game loop, ECS world, scenes, time (GameClockwithTimeScale;GameSpeedfor discrete pause/1×/3×/6× speed control over the clock,context.UseGameSpeed(...);Calendarturning scaled time into in-game days,context.UseCalendar(secondsPerDay);Climate— continuous seasonal/daily temperature and season over the calendar,context.UseClimate(settings)), plus Input (MrGameEng.Input:InputManager,ActionMap,InputSystem, inCore/Input/). Depends only on MonoGame and Friflo.Engine.ECS.Graphics— custom batched renderer, camera, sprites, plus Tilemaps (MrGameEng.Tilemaps: code-built tile grids rendered through the batcher,scene.UseTilemaps()afterUseRenderer2D(), inGraphics/Tilemaps/) and Lighting (MrGameEng.Lighting,Graphics/Lighting/: day/night ambient over theCalendardrivingRenderer2D.AmbientLighton world layers,scene.UseDayNight(renderer); plus a per-cell lightmap —LightmapBuilder(ambient × occlusion + point lights with grid-traced shadows),PointLightcomponent, multiplied over the world viascene.UseLighting(...), sampleable withLighting.SampleAtfor the simulation). →Core.Audio— ogg playback (NVorbis);AudioManagerwithSoundVolume/MasterVolume(one knob for effects + music). →Core.Content— the asset/content pipeline: Assets (MrGameEng.Assets: runtime loading, no MGCB pipeline; FontStash), Atlases (MrGameEng.Atlases: texture-atlas builder + runtime loader, CLI wrapper intools/MrGameEng.AtlasTool; StbImage), and Mods (MrGameEng.Mods: mod discovery + load order fromAbout/About.json, JSONDefs/with parent inheritance and later-mod override,Languages/<code>/localization, merged texture content trees; the game ships its content as theCoremod). →Core,Graphics(Atlases needsTexture2DRegion).Simulation— deterministic gameplay primitives that own no world data: Pathfinding (MrGameEng.Pathfinding: grid A*/Dijkstra/BFS and flow fields over a game-implementedIPathGrid), AI (MrGameEng.AI: utility-AI primitives —ResponseCurve,Consideration<TContext>,UtilityAction<TContext>,UtilityAi<TContext>selector,Blackboard; generic over a game context), and Collisions (MrGameEng.Collisions:Collidercomponent, spatial hash rebuilt per tick, pairs/queries/raycast,scene.UseCollisions()after movement systems). →Core,Graphics(Collisions needsTransform2D,RectF).UI— Myra integration (scene.UseUI()afterUseRenderer2D()), DevConsole (MrGameEng.DevConsole: in-game console capturingCore.Log,scene.UseDevConsole()last in OnLoad) and Inspector (MrGameEng.Inspector: a Chrome-DevTools-style ECS debugger overlay — entity tree by archetype, component/field view with simple-field editing, world-pick with selection highlight, renderer perf tab;scene.UseInspector(renderer), toggle F1). →Core,Graphics(the inspector picks entities and reads renderer timings; Myra renders with its own SpriteBatch internally).Assets.Generator— Roslyn source generator for typed asset handles; standalone netstandard2.0 analyzer.
Dependency rule: a library may depend only on Core and Graphics; Core depends only
on MonoGame and Friflo.Engine.ECS. Features grouped into one library share its package
set (e.g. Content carries both FontStash and StbImage) — keep optional/heavy deps
(Myra, NVorbis) in their own library so the rest of the engine stays free of them.
Commands
dotnet build MrGameEng.sln
dotnet test MrGameEng.sln
Building requires the .NET 10+ SDK (Roslyn 5.3 packages used by the asset-handles generator do not load under the SDK 8 compiler); the target framework stays net8.0.
Architecture rules
- ECS-first: components are plain data (
structimplementingIComponent), behavior goes into Friflo systems (QuerySystem), wired throughSystemRoot. NoUpdate()methods on game objects, no inheritance-based entities. - Hot paths (per-frame systems) must be allocation-free below the renderer's parallel threshold; above it Parallel.For scheduler overhead is the accepted trade. A Friflo chunk holds a whole archetype — parallelize by slicing chunks into segments, never by chunk alone. Measure in Release only, using the Renderer2D phase timings.
- Rendering: custom batcher in
Graphics(vertex buffers, layer→depth→texture sort, atlas support);SpriteBatchis not used in engine code. Draw systems write vertices directly from Friflo chunk iteration. Orthographic camera (one active per scene), registered render layers (World or Screen space, optional Y-sort), AABB culling against the camera rect before vertices are written. - No MGCB content pipeline. Assets are raw files under
Assets/, loaded at runtime (textures viaTexture2D.FromFile, fonts via FontStashSharp, ogg via NVorbis, shaders precompiled bydotnet-mgfxcat build time). Game code references assets only through generated typed handles (AssetRef<T>), never string paths. - New engine functionality goes into the host library for its role (a namespaced
subfolder, e.g. a new render feature under
Graphics/), not a fresh project. Add a newMrGameEng.<Area>library only when the area is a genuinely new role or needs an isolated heavy/optional dependency — never intoCoreby default, never a two-file project per feature. - 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
- Nullable reference types enabled, warnings as errors, file-scoped namespaces.
- Public engine API requires XML doc comments (English).
- Tests: xUnit, named
Method_Scenario_Expectation. - Formatting: all C# code is formatted with CSharpier. Match its output —
run
csharpier format .(or let the editor's format-on-save handle it) before committing; never hand-format against it.
Memory (echovault MCP)
- At session start, call
memory_contextto load prior decisions for this project; callmemory_searchbefore working on a topic that may have prior context (e.g. "renderer", "transitions", "generator"). - Before ending a session where you decided, fixed or learned something, save it with
memory_save: decisions (X over Y + why), bug root causes, non-obvious gotchas (e.g. Friflo/Myra API traps). Write for a future agent with zero context. - Don't save what the repo already records (code, docs/, git history) or trivia.