Search documentation

Find pages, sections, and content across all docs.

WavedashDocs

MonoGame

MonoGame doesn't officially support web builds. For the browser, use KNI — the community-maintained MonoGame-compatible fork with a Blazor WebAssembly target.

View example project on GitHub

MonoGame itself does not officially support web or WebAssembly targets (issue #8102, closed as "not planned"). To ship a MonoGame-style game to the browser, the community path is KNInkast/kni, a MonoGame-compatible fork with a BlazorGL web target. Your Game subclass, SpriteBatch, GraphicsDeviceManager, and input APIs work unchanged — you swap MonoGame.Framework.* package references for nkast.Xna.Framework.* and publish as a Blazor WebAssembly project.

KNI is actively maintained and used in the community by projects like SadConsole, FlatRedBall, and Apos.Gui.

Prerequisites

  • .NET 8 SDK (.NET 9 works too)
  • The wasm-tools workload if you want AOT compilation:
    dotnet workload install wasm-tools
    

Build for web

Scaffold a new project with nkast's template:

dotnet new install nkast.Kni.Templates
dotnet new kni-blazor-gl -n MyGame

Then publish:

dotnet publish -c Release

The Blazor output lands in bin/Release/net8.0/publish/wwwroot/. Point upload_dir at that directory.

SDK integration

Wavedash injects Wavedash into the page before your game loads. Call it from C# via IJSRuntime. A small wavedash-bridge.js exposes thin wrappers on window, and Blazor invokes them:

// wwwroot/wavedash-bridge.js
(function () {
  const Wavedash = window.Wavedash;

  window.wavedashInit = function () {
    Wavedash.init({ debug: true });
  };

  window.wavedashUpdateLoadProgress = function (value) {
    Wavedash.updateLoadProgressZeroToOne(Math.max(0, Math.min(1, Number(value) || 0)));
  };
})();

Reference it from wwwroot/index.html:

<script src="wavedash-bridge.js"></script>

Then call it from your Blazor component (typically Pages/Index.razor.cs) once the page renders:

protected override async void OnAfterRender(bool firstRender)
{
    base.OnAfterRender(firstRender);
    if (!firstRender) return;

    await JsRuntime.InvokeVoidAsync("wavedashUpdateLoadProgress", 1.0);
    await JsRuntime.InvokeVoidAsync("wavedashInit");

    // hand control to the KNI render loop
    await JsRuntime.InvokeAsync<object>("initRenderJS", DotNetObjectReference.Create(this));
}

To expose more SDK methods, add a handler to wavedash-bridge.js and invoke it with JsRuntime.InvokeVoidAsync("yourMethod", args...).

Load progress

KNI's Blazor template boots the game from TickDotNet inside the Razor page. By the time OnAfterRender(firstRender: true) fires, the runtime is live and the canvas is ready, so reporting 1.0 there is the simplest "first playable" signal. If you want intermediate progress, hook Blazor.start({ loadBootResource }) in index.html to observe resource downloads and call wavedashUpdateLoadProgress() as each one resolves.

wavedash.toml

game_id = "YOUR_GAME_ID_HERE"
upload_dir = "./bin/Release/net8.0/publish/wwwroot"
entrypoint = "index.html"

Notes

  • AOT compilation dramatically speeds up startup. Add <RunAOTCompilation>true</RunAOTCompilation> and <WasmStripILAfterAOT>false</WasmStripILAfterAOT> to your .csproj. First build takes several minutes; subsequent builds are fast. Leaving strip on causes MONO interpreter assertions at runtime.
  • Routing inside iframes: if wavedash dev serves your game from a subpath, add @page "/{*path:nonfile}" to Pages/Index.razor and a [Parameter] public string Path { get; set; } to absorb the matched route.
  • Content Pipeline (.mgcb files) requires Windows or Wine on macOS. For simple examples, skip it and use Texture2D.SetData on a 1×1 pixel at runtime.

Other options

  • FNA, the other XNA reimplementation, has a community-driven Emscripten path via FNA-WASM-Build — used by Celeste-WASM. Separate ecosystem from MonoGame proper.
  • NativeAOT-LLVM is a theoretical alternative but only supported on Windows x64 and Linux x64 build hosts, and is not what MonoGame devs use in practice.