WavedashDocs

C#

Run C# games in the browser via .NET's managed WebAssembly runtime.

View the example on GitHub

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.