C# games target the browser through .NET's managed WebAssembly runtime using the [JSImport]/[JSExport] interop pattern. The output is a _framework/ directory plus your game.js and index.html.
If you're using Unity, see the Unity guide instead — it has its own SDK package and build pipeline. If you're using MonoGame, see the MonoGame guide.
Setup
You need the .NET 9+ SDK with the wasm-tools workload:
dotnet workload install wasm-tools-net9
Your .csproj should target browser-wasm:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
</Project>
SDK integration
Wavedash injects WavedashJS into the page before your .NET runtime loads. Use [JSImport] on partial methods to call the SDK, and [JSExport] so game.js can call into your C#:
using System.Runtime.InteropServices.JavaScript;
public static partial class Interop
{
[JSImport("globalThis.wavedashInit")]
public static partial void WavedashInit();
[JSImport("globalThis.wavedashProgress")]
public static partial void WavedashProgress(double p);
}
In your game's init, call WavedashProgress(1.0) to fill the progress bar, then WavedashInit() which signals load completion:
Interop.WavedashProgress(1.0);
Interop.WavedashInit();
Call WavedashProgress(...) with intermediate values during async asset loading. WavedashInit() automatically signals load completion, so call it last.
The web/game.js entrypoint defines the globalThis bridge functions and boots the .NET runtime:
const sdk = await window.WavedashJS;
globalThis.wavedashInit = function () {
sdk.init({ debug: true });
};
globalThis.wavedashProgress = function (p) {
sdk.updateLoadProgressZeroToOne(p);
};
import { dotnet } from "./_framework/dotnet.js";
const { getAssemblyExports } = await dotnet.create();
const exports = await getAssemblyExports("Pong.dll");
exports.Interop.WdInit(width, height);
Build script
#!/usr/bin/env sh
set -eu
mkdir -p build/web
dotnet publish src/Pong.csproj -c Release
cp -R src/bin/Release/net9.0/browser-wasm/AppBundle/_framework build/web/_framework
cp web/index.html build/web/index.html
cp web/game.js build/web/game.js
wavedash.toml
game_id = "YOUR_GAME_ID_HERE"
upload_dir = "./build/web"
Other SDK features
Once initialized, WavedashJS also exposes leaderboards, achievements, stats, and user data. Add more [JSImport] bindings or call them from game.js:
[JSImport("globalThis.wavedashSubmitScore")]
public static partial void SubmitScore(string id, int score, bool keepBest);
[JSImport("globalThis.wavedashSetAchievement")]
public static partial void SetAchievement(string id);
With corresponding globalThis implementations in game.js that call sdk.uploadLeaderboardScore(...), sdk.setAchievement(...), and sdk.storeStats(). See the SDK reference for the full API.