# C#

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

Source: https://docs.wavedash.com/engines/csharp

<GithubLink href="https://github.com/wvdsh/examples/tree/main/example-csharp" />
<PlaytestLink href="https://wavedash.com/playtest/csharp-example/b06cd71a-e2bd-484b-be0a-7c4fcd44e253" />

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`.

<Tip>
If you're using **Unity**, see the [Unity guide](/engines/unity) instead — it has its own SDK package and build pipeline. If you're using **MonoGame**, see the [MonoGame guide](/engines/monogame).
</Tip>

## Setup

You need the .NET 9+ SDK with the `wasm-tools` workload:

```bash
dotnet workload install wasm-tools-net9
```

Your `.csproj` should target `browser-wasm`:

```xml
<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 `Wavedash` 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#:

```csharp
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:

```csharp
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:

```javascript
const Wavedash = window.Wavedash;

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

globalThis.wavedashProgress = function (p) {
  Wavedash.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

```bash
#!/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

```toml
game_id = "YOUR_GAME_ID_HERE"
upload_dir = "./build/web"
```

## Other SDK features

Once initialized, `Wavedash` also exposes leaderboards, achievements, stats, and user data. Add more `[JSImport]` bindings or call them from `game.js`:

```csharp
[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 `Wavedash.uploadLeaderboardScore(...)` and `Wavedash.setAchievement(id, true)`. See the [SDK reference](/sdk/overview) for the full API.
