#Unity
Unity's WebGL build works on the arcade, with two export settings you must change from their defaults. Read shipping an engine export first for the rules every engine shares.
#Build settings
File → Build Settings → WebGL, then in Player Settings → Publishing Settings:
| Setting | Value | Why |
|---|---|---|
| Compression Format | Disabled | The deploy sets no Content-Encoding, so Brotli/Gzip files arrive undecodable |
| Decompression Fallback | on, if you keep compression | Makes Unity decompress in JS instead of relying on server headers |
| Enable Exceptions | None or Explicitly Thrown | Full exception support roughly doubles the build |
Leave threads off. WebAssembly threads need cross-origin isolation, which the arcade doesn't provide.
Build into games/<your-slug>/public/, so you get public/index.html plus
public/Build/. Then:
"build": { "command": null, "outputDir": "public" }
Commit the whole Build/ folder. It's large; see the note on
sizes.
#Calling the arcade from C#
Unity needs a .jslib shim: C# can't reach window directly, but it can call
into functions you register on Unity's JS library.
// Assets/Plugins/Arcade.jslib
mergeInto(LibraryManager.library, {
ArcadeInit: function () {
if (typeof Arcade !== "undefined") Arcade.init();
},
ArcadeSubmitScore: function (score) {
if (typeof Arcade === "undefined") return;
// Catch: the SDK rejects when signed out or offline, and an unhandled
// rejection is just noise in the player's console.
Arcade.leaderboard.submit(score).catch(function () {});
},
ArcadeUnlock: function (idPtr) {
if (typeof Arcade === "undefined") return;
Arcade.achievements.unlock(UTF8ToString(idPtr)).catch(function () {});
},
ArcadeGuard: function (onFlag, reasonPtr) {
if (typeof Arcade === "undefined") return;
var reason = UTF8ToString(reasonPtr);
Arcade.nav.guard(onFlag ? (reason || true) : false);
},
});
// Assets/Scripts/ArcadeSDK.cs
using System.Runtime.InteropServices;
using UnityEngine;
public static class ArcadeSDK {
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")] private static extern void ArcadeInit();
[DllImport("__Internal")] private static extern void ArcadeSubmitScore(int score);
[DllImport("__Internal")] private static extern void ArcadeUnlock(string id);
[DllImport("__Internal")] private static extern void ArcadeGuard(bool on, string reason);
public static void Init() => ArcadeInit();
public static void SubmitScore(int score) => ArcadeSubmitScore(score);
public static void Unlock(string id) => ArcadeUnlock(id);
public static void Guard(bool on, string reason = "") => ArcadeGuard(on, reason);
#else
// The editor has no browser, so these are no-ops. Without this the game
// can't run in the editor at all.
public static void Init() { }
public static void SubmitScore(int score) => Debug.Log($"[arcade] score {score}");
public static void Unlock(string id) => Debug.Log($"[arcade] unlock {id}");
public static void Guard(bool on, string reason = "") { }
#endif
}
Then:
void Start() => ArcadeSDK.Init();
void GameOver() {
ArcadeSDK.SubmitScore(score);
ArcadeSDK.Unlock("first-run");
}
UTF8ToString(ptr) is required: strings arrive from C# as pointers into the heap,
and using the pointer directly gives you a number.
#Getting a value back
.jslib functions are synchronous and can't return a promise, so pass results
back through SendMessage:
ArcadeFetchTop: function () {
if (typeof Arcade === "undefined") return;
Arcade.leaderboard.top(10)
.then(function (rows) {
// "Leaderboard" is the GameObject name, "OnTopScores" a public method.
unityInstance.SendMessage("Leaderboard", "OnTopScores", JSON.stringify(rows));
})
.catch(function () {});
},
Unity's default template stores the instance as unityInstance; confirm the name
in your index.html if you use a custom template, since it's a template variable
rather than a guarantee.
#Leave room for the top bar
The simplest answer for a full-viewport export is to collapse the bar. Add to
game.json:
"navBar": "collapsed"
It then sits off-screen behind a small handle the player can pull down, and
--arcade-nav-h becomes the handle's height rather than the bar's.
If you'd rather keep it expanded: the arcade already reserves the bar's strip as
padding-top on <body>, so a template that lays out in normal flow — Unity's
default centred canvas included — clears it with no work from you.
A full-window template is the exception, because a height of 100%/100vh
ignores that padding and runs under the bar. In a custom WebGL template, subtract
the strip instead:
#unity-container { height: calc(100vh - var(--arcade-nav-h, 0px)); }
The 0px fallback matters: outside the arcade there is no bar, and the container
should fill the window.
#Common problems
| What you see | Cause |
|---|---|
| Loads in the editor, blank when deployed | Compression on with no decompression fallback |
| "Unable to parse Build/…" | Same: the browser got compressed bytes it couldn't decode |
Arcade is not defined |
Guard every .jslib function; the SDK may not have loaded yet in a game's own dev server |
| A number instead of your string | Missing UTF8ToString |
The arcade side of this is verified: the injected bar,
window.Arcade, and.wasmserving are all exercised against an export-shaped game. The.jsliband C# above follow Unity's documented interop but aren't run by this repo's CI, since CI has no engine installed. Check it on your first build and please correct this page if anything differs.
See also: shipping an engine export · the arcade nav bar · SDK reference