Backends
The engine core (src/MicroEngine) never references a windowing, graphics or audio library. Everything
platform-specific sits behind IPlatform and four small interfaces. The official implementations:
| Platform | Project | Used for |
|---|---|---|
| SDL 3 | src/MicroEngine.Sdl |
The default on Windows, Linux, macOS, Android and iOS |
| raylib | src/MicroEngine.Raylib |
Browsers (WebAssembly), and an alternative desktop platform |
| Headless | src/MicroEngine (HeadlessPlatform) |
Tests and CI |
A game is an ordinary .NET program and does not change between them. Which platform runs is the host’s choice (a command-line argument, an environment variable, the phone app), never the game’s. The decisions are recorded in ADR 0016 and ADR 0017.
IPlatform
public interface IPlatform : IDisposable{ IGraphicsDevice Graphics { get; } IAudioDevice Audio { get; } IInputSource Input { get; } bool CloseRequested { get; } /// <summary>Monotonic time in seconds.</summary> double Now { get; } /// <summary>Whether time advances with the wall clock (desktop) or one step per frame (headless).</summary> bool RealTime { get; } /// <summary>Whether the system took the game away (a phone app sent to the background) ...</summary> bool Suspended => false; void PollEvents(); IRenderer BeginFrame(); void EndFrame(); /// <summary>Writes the most recently completed virtual frame as a PNG.</summary> void SaveScreenshot(string path); void ToggleFullscreen();}Source: src/MicroEngine/Core/Platform.cs
| Interface | Responsibility | SDL | raylib | Headless |
|---|---|---|---|---|
IRenderer |
Immediate-mode drawing into the virtual canvas (Rendering) | SdlRenderer |
RaylibRenderer |
HeadlessRenderer |
IGraphicsDevice |
CreateTexture(PixelImage) |
SdlGraphicsDevice |
RaylibGraphicsDevice |
HeadlessGraphicsDevice |
IAudioDevice |
Sounds, looping and streamed music (Audio) | SdlAudioDevice |
RaylibAudioDevice |
SilentAudioDevice |
IInputSource |
Key, pad button and left-stick state (Input) | SdlInput (with TouchPad) |
RaylibInput |
HeadlessInput |
While Suspended is true the loop neither simulates nor renders, pauses music, saves achievement progress and calls
the game’s OnSuspended/OnResumed. See Lifecycle for the frame order.
Choosing a platform
Desktop games start through MicroEngine.Desktop.DesktopLauncher:
var saveDir = Environment.GetEnvironmentVariable("RIFT_SAVE_DIR");return DesktopLauncher.Run(new RiftGame(LaunchOptions.FromEnvironment(), saveDirectory: string.IsNullOrWhiteSpace(saveDir) ? null : saveDir), args);Source: games/NeonRift/Program.cs
Its catalog offers, in order of preference: sdl, raylib, sdl-software and headless. The first that can run
is the default; --platform NAME or the MICROENGINE_PLATFORM variable picks another, and --list-platforms prints
them:
$ MeadowRun --list-platformssdl: SDL 3: window, 2D renderer (Direct3D, Metal, OpenGL, Vulkan or software), input, touch and audioraylib: raylib 6.0: GLFW window, OpenGL renderer, input and miniaudio soundsdl-software: SDL 3 with its software rendererheadless: no window, devices or sound; one step per frame, for tests and CI| Argument | Effect |
|---|---|
--platform NAME |
Run on that platform of the catalog |
--list-platforms |
Print the catalog, with the reason a platform cannot run here, and exit |
--script FILE |
Play an input script; implies a fixed step |
--captures DIR |
Where script captures go (default ./captures); saved at 3× scale |
--headless |
The same as --platform headless (captures are recorded but not written) |
--hidden |
Render in a hidden window (captures still work); implies a fixed step |
--frames N |
Stop after N updates |
--seed N, --scale N, --mute |
Random seed, window scale, silence |
Exit codes: 0 success, 1 game or platform error (an unknown or unavailable platform, no display), 2 bad arguments.
Unknown arguments are rejected, which is why the games read developer shortcuts from environment variables. Sources:
src/MicroEngine/Core/GameLauncher.cs,
src/MicroEngine.Desktop/DesktopLauncher.cs.
MicroEngine.Raylib.DesktopLauncher is the same launcher with only raylib and headless.
Replacing a part
A profile says which parts a host may supply itself. The official profiles allow the audio device and extra input sources; the window, renderer, graphics device and frame presentation share one graphics context, so they are replaced together by choosing another profile.
// raylib's window and renderer with SDL's audio device instead of raylib's.return DesktopLauncher.Run(game, args, DesktopLauncher.CreateCatalog(), request => request with { Audio = SdlAudioDevice.TryCreate });
// An extra input source (a network peer, an accessibility switch) read together with the keyboard and gamepad.return DesktopLauncher.Run(game, args, DesktopLauncher.CreateCatalog(), request => request with { ExtraInput = [myInput] });When audio is supplied the backend does not open its own device, and the platform disposes the replacement. A request
the profile cannot honour fails with PlatformConfigurationException (for example “Platform ‘browser’ does not allow
replacing its audio device.”). Source: src/MicroEngine/Core/PlatformProfiles.cs
To offer a platform of your own, wrap it in a profile and add it to the catalog:
var catalog = DesktopLauncher.CreateCatalog() .Add(new PlatformProfile("console", "my console SDK", request => new ConsolePlatform(request)) { CheckSupport = () => ConsoleSdk.IsAvailable ? null : "the console SDK is not installed", Replaceable = PlatformParts.Input, });return DesktopLauncher.Run(game, args, catalog); // --platform consoleSDL 3
SdlPlatform opens a resizable window of VirtualWidth × Scale by VirtualHeight × Scale (full screen on phones),
draws through SDL’s renderer into a texture of the virtual size and scales it by whole factors with letterboxing.
SDL chooses the graphics API for the system; SdlPlatform.CreateProfile("vulkan") (or direct3d11, direct3d12,
metal, opengl, opengles2, software) fixes one, and SdlPlatform.RenderDrivers lists what this build offers.
Keyboard, the first gamepad and touch are read; F11 and Alt+Enter toggle fullscreen on desktops. Audio uses SDL audio
streams: wav and ogg music, silence when no device exists. On touch screens TouchPad draws a d-pad, A, B, X, a
shoulder button and Start over the frame (never into captures); they press the gamepad buttons games already bind.
MICROENGINE_SDL_EVENT_LOGGING=1 makes SDL log the events it delivers. The package is
ppy.SDL3-CS with SDL 3.5. If the system cannot load SDL (on Linux, a glibc older
than 2.34), the launcher ends with error: platform 'sdl' could not load its native library and the loader’s reason,
exit code 1. A desktop game run from a macOS application bundle reads its content from Contents/Resources/Content
(DesktopLauncher.BundleContentRoot), so a signed bundle keeps only code in Contents/MacOS.
raylib and browsers
RaylibPlatform behaves like the SDL platform on desktops (the raylib profile). raylib cannot run on iOS, and on
macOS it needs a GPU with accelerated OpenGL. Its main role is the browser: the same backend compiled to WebAssembly
and linked into the .NET browser runtime. A page cannot block its only thread in a game loop, so the browser launcher
returns a GameSession that the page drives:
public static GameSession CreateSession(Func<Game> createGame){ ArgumentNullException.ThrowIfNull(createGame); SaveStore.DefaultStorage = gameId => new BrowserSaveStorage(gameId); return new GameSession(createGame, game => new RaylibPlatform(game.Settings, new DesktopOptions()));}Source: src/MicroEngine.Raylib/BrowserLauncher.cs
The shared web host calls Frame once per requestAnimationFrame. In a browser the window never reports
CloseRequested, the page paces frames and owns fullscreen, and saves go to localStorage
(Persistence). Embedding a package is described in Embedding.
Phones
The hosts in platforms/android and platforms/ios run one game each on the SDL platform with touch controls,
suspend it in the background and ship its Content files (unpacked from the APK on Android, bundle resources on
iOS, both through ContentFolder.DefaultRoot). See platforms/README.md.
Headless platform
HeadlessPlatform(width, height) has deterministic time (one step per frame), no window, silent audio and
keyboard state you set from code. HeadlessPlatform.For(settings) sizes it from a game. Tests use it two ways.
Stepping a game directly:
var game = new MyGame();var platform = new HeadlessPlatform();game.InitializeForTest(platform);platform.Keys.Set(Key.Right, true);for (int i = 0; i < 60; i++) game.StepForTest();Source: Making a game
Running the real loop for a fixed number of updates:
var game = new MeadowGame(TempDir(), new LaunchOptions(start, Level: 4));var platform = new HeadlessPlatform();var result = GameRunner.Run(game, platform, new RunOptions { MaxUpdates = 240 });Assert.IsNull(result.Error, $"{start}: {result.Error}");Source: tests/MeadowRun.Tests/PlaythroughTests.cs
Useful probes: platform.Renderer.DrawCalls, platform.SilentAudio.Played, platform.Screenshots (capture
paths requested), HeadlessGraphicsDevice.TexturesCreated, and CloseRequested (settable).
Writing a new backend
A backend for another library would:
- Implement
IRendererover the library’s 2D drawing, honouringDrawOptions(flip, rotation, origin, scale), blend modes, clipping and the camera offset stack, and drawing into a virtual-size target. - Implement
IGraphicsDevice.CreateTexturefromPixelImage.ToRgba()(point filtering keeps pixels crisp). - Implement
IAudioDevice, becoming silent rather than failing when no hardware is present, and opening nothing when the request supplies a device (PlatformRequest.OpensOwnAudio). - Map the library’s key and pad codes onto
KeyandPadButtonin anIInputSource. - Implement
IPlatform(time, events, frame begin/end, screenshots, fullscreen, andSuspendedif the system can take the app away) and expose it as aPlatformProfile.
tests/MicroEngine.Desktop.Tests shows how to check a backend against the others: one probe frame exercising every
renderer operation, compared pixel by pixel.
Limitations
- The on-screen controls are one layout for every game, without labels.
- No published NuGet packages yet: the engine is used from its repository; one package per backend is prepared (SDK status).
- Packaging a game for players is documented for the four demos (packaging), not yet as a tool for any game.
- What each platform has been shown to do: Platforms.