Docs / Engines
Unity
Build a Unity Web (WebGL) game for Fradiation Games with the Unity kit, or set it up by hand, and call the SDK from C#.
A Unity web build is a wasm file with a canvas attached. Fradiation serves it as is, once it has the right compression, a template that fills the frame and relative paths.
The Unity kit sets all of that with one menu item. The kit is tested with Unity 6 (6000.x). The APIs it uses exist in Unity 2021.3 and later.
Quick path
-
Download the Unity kit and unzip it into your project folder, so the kit's
Assetsfolder merges with yours. The files land here:Path What it is Assets/Fradiation/Editor/FradiationBuild.csThe build menu item and the batch method. Assets/Fradiation/Runtime/Fradiation.csThe C# API. Assets/Plugins/WebGL/Fradiation.jslibThe bridge from C# to window.fradiation.Assets/WebGLTemplates/Fradiation/A WebGL template that fills the frame. -
Put
fradiation.jsonin the project root, next toAssets/. See fradiation.json for every field.json{ "$schema": "https://www.fradiation.games/fradiation.schema.json", "viewport": { "width": 1280, "height": 720 }, "threads": false, "mutations": [ { "key": "boss-down", "name": "Boss Down", "description": "Defeat the first boss.", "tier": "alpha", "icon": "skull" } ], "boards": [ { "key": "fastest-clear", "name": "Fastest clear", "order": "asc", "format": "time" } ] } -
Build. In the editor, choose Fradiation → Build for Fradiation. From the command line, with the project closed in the editor:
bashUnity -batchmode -quit -projectPath . -buildTarget WebGL -executeMethod FradiationBuild.BuildUnitystands for the path to the editor executable of the version your project uses, for example"C:\Program Files\Unity\Hub\Editor\<version>\Editor\Unity.exe". The editor needs its Web (WebGL) build support module installed.- The build goes to
Builds/Fradiation. Add-fradiationOut <folder>to change that. fradiation.jsonfrom the project root is copied into the build if it exists.- The process exits with code 1 if the build fails.
An agent driving the open editor through an editor-automation tool should call
FradiationBuild.BuildDeferred()instead. It returns at once, builds on the next editor tick (so a request timeout can't cut the build off part-way), and writesBuilds/Fradiation.status.jsonto poll:queued,building, thensucceededorfailed, with the output path and any error. - The build goes to
-
Check it, then upload. Run
node check-build.mjs Builds/Fradiation(download the script), then upload theBuilds/Fradiationfolder or a zip of it on/dev/<slug>. See Packaging and Publish a game.
What the kit sets, and doing it by hand
If you skip the kit, set these yourself.
| Setting | Value | Where |
|---|---|---|
| Build target | Web | Unity 6: File → Build Profiles → Web. Earlier versions: File → Build Settings → WebGL. |
| WebGL Template | A template that fills the window | Player Settings → Resolution and Presentation |
| Compression Format | Brotli (or Gzip) | Player Settings → Publishing Settings |
| Decompression Fallback | Off | Player Settings → Publishing Settings |
| Data Caching | On | Player Settings → Publishing Settings |
| Name Files As Hashes | Not needed | Player Settings → Publishing Settings |
The same settings from an editor script, as the kit sets them:
PlayerSettings.WebGL.template = "PROJECT:Fradiation";
PlayerSettings.WebGL.compressionFormat = WebGLCompressionFormat.Brotli;
PlayerSettings.WebGL.decompressionFallback = false;
PlayerSettings.WebGL.dataCaching = true;Compression
The uploader accepts pre-compressed files ending in .br or .gz, such as Fradiation.wasm.br. The play server sends them with the matching Content-Encoding, so the browser unpacks them itself. Brotli and Gzip builds both work.
Unity's own description of Compression Format says it doesn't affect development builds. A development build is uncompressed. Its files are allowed but larger.
Decompression Fallback
Leave it off. Unity describes the option as decompression code for hosts that can't set response headers to match the compression. Fradiation sets them, so the fallback adds loader code you don't use.
Data Caching
On. Unity keeps the data file in the browser's IndexedDB, which belongs to your game's own origin.
Name Files As Hashes
Not needed. Every build is served from its own URL, so file names don't have to change to defeat caches.
The template
Unity's default template gives the canvas a fixed size. On desktop it sets the width and height from Player Settings (960×600 by default), and it adds a footer with a logo and a fullscreen button. That doesn't fit the cabinet, which letterboxes to your viewport and expects the game to fill its window.
Create Assets/WebGLTemplates/Fradiation/index.html, copy fradiation-sdk.js into the same folder, and pick the template in Player Settings. Unity copies every file in the template folder into the build.
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>{{{ PRODUCT_NAME }}}</title>
<style>
html, body { margin: 0; height: 100%; background: #000; overflow: hidden; }
#unity-canvas { display: block; width: 100%; height: 100%; }
#loading { position: fixed; inset: 0; display: grid; place-items: center; color: #999; font: 12px monospace; }
</style>
<script src="fradiation-sdk.js"></script>
</head>
<body>
<canvas id="unity-canvas" tabindex="-1"></canvas>
<div id="loading">Loading</div>
<script>
var canvas = document.querySelector("#unity-canvas");
var loading = document.querySelector("#loading");
var config = {
arguments: [],
dataUrl: "Build/{{{ DATA_FILENAME }}}",
frameworkUrl: "Build/{{{ FRAMEWORK_FILENAME }}}",
#if USE_THREADS
workerUrl: "Build/{{{ WORKER_FILENAME }}}",
#endif
#if USE_WASM
codeUrl: "Build/{{{ CODE_FILENAME }}}",
#endif
#if SYMBOLS_FILENAME
symbolsUrl: "Build/{{{ SYMBOLS_FILENAME }}}",
#endif
streamingAssetsUrl: "StreamingAssets",
companyName: {{{ JSON.stringify(COMPANY_NAME) }}},
productName: {{{ JSON.stringify(PRODUCT_NAME) }}},
productVersion: {{{ JSON.stringify(PRODUCT_VERSION) }}},
};
var script = document.createElement("script");
script.src = "Build/{{{ LOADER_FILENAME }}}";
script.onload = function () {
createUnityInstance(canvas, config, function (progress) {
loading.textContent = "Loading " + Math.round(progress * 100) + "%";
}).then(function () {
loading.remove();
canvas.focus();
window.fradiation && window.fradiation.ready();
}).catch(function (message) {
loading.textContent = String(message);
});
};
document.body.appendChild(script);
</script>
</body>
</html>This does what the kit's template does, without the segmented loading bar. All URLs are relative. Set the viewport in fradiation.json to your game's aspect ratio.
Calling the SDK from C#
The kit's Fradiation class is static and fire-and-forget. It returns nothing, so a call can't tell you whether a mutation unlocked or what rank a score got. In the editor and on other platforms it only logs.
| Call | What it does |
|---|---|
Fradiation.UnlockMutation(string key) | Unlocks a mutation declared in fradiation.json. Repeat calls are harmless. |
Fradiation.SubmitScore(string board, double value) | Submits a score to a declared board. The site keeps each player's best. |
Fradiation.Track(string name, string json = null) | Marks a moment on the playtest timeline. Extra data goes in as a JSON string. |
Fradiation.AskForFeedback(string prompt = null) | Opens the feedback panel in playtests. The prompt is up to 200 characters. |
using UnityEngine;
public class RunEvents : MonoBehaviour
{
// Unlock on an event.
public void OnBossDefeated()
{
Fradiation.UnlockMutation("boss-down");
}
// Submit a time score. A board with "format": "time" takes milliseconds.
public void OnLevelComplete(float seconds)
{
Fradiation.SubmitScore("fastest-clear", System.Math.Round(seconds * 1000.0));
Fradiation.Track("level-complete", "{\"level\":3}");
}
// Ask for feedback at game over.
public void OnGameOver()
{
Fradiation.AskForFeedback("How was that boss?");
}
}- Declare every mutation and board key in
fradiation.json. Scores above a board'smaxare rejected as implausible. Trackis ignored outside playtests.AskForFeedbackopens the panel only in playtests.- While the game is a draft, calls are checked but not kept, and a toast says so. Developers get no rads from their own games.
- Outside the site, for example a build opened from your own disk, every call does nothing. The SDK page has the full behavior.
Without the kit
The bridge is a .jslib file and a small C# wrapper. Skip this if you installed the kit.
// Assets/Plugins/WebGL/FradiationBridge.jslib
mergeInto(LibraryManager.library, {
Fradiation_UnlockMutation: function (keyPtr) {
var key = UTF8ToString(keyPtr);
if (window.fradiation) window.fradiation.unlockMutation(key);
},
Fradiation_SubmitScore: function (boardPtr, value) {
var board = UTF8ToString(boardPtr);
if (window.fradiation) window.fradiation.submitScore(board, value);
},
});using System.Runtime.InteropServices;
using UnityEngine;
public static class FradiationBridge
{
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")] private static extern void Fradiation_UnlockMutation(string key);
[DllImport("__Internal")] private static extern void Fradiation_SubmitScore(string board, double value);
#endif
public static void UnlockMutation(string key)
{
#if UNITY_WEBGL && !UNITY_EDITOR
Fradiation_UnlockMutation(key);
#else
Debug.Log("[Fradiation] mutation: " + key);
#endif
}
public static void SubmitScore(string board, double value)
{
#if UNITY_WEBGL && !UNITY_EDITOR
Fradiation_SubmitScore(board, value);
#else
Debug.Log("[Fradiation] score " + board + ": " + value);
#endif
}
}Multithreading
Leave it off. Unity 6 has an experimental option in Player Settings, labelled Enable Native C/C++ Multithreading in 6000.5 (Unity's description says it doesn't multithread C# code). A build made with it needs SharedArrayBuffer, and that isn't available inside the cabinet yet.
The kit leaves the setting alone, and warns in the console if it's on while fradiation.json doesn't set "threads": true.
What the build folder looks like
Builds/Fradiation/
index.html
fradiation-sdk.js
fradiation.json
Build/
Fradiation.data.br
Fradiation.framework.js.br
Fradiation.loader.js
Fradiation.wasm.br
StreamingAssets/ only if your project has any- Unity names the files in
Build/after the output folder, so a folder calledWebgivesWeb.data.br. index.htmlsits at the top. Every path in it is relative.- Limits: 1,000 files, 500 MB in total, 200 MB per file. The data file is usually the largest. One shipped Unity game has a 93 MB
.data.br. - Files in
StreamingAssetsfollow the same file type rules as everything else. An extension that isn't on the allowed list is refused. See Builds and uploads.
Packaging
Pick one.
-
The uploader. On
/dev/<slug>, drop a.zipor pick theBuilds/Fradiationfolder. The browser reads and checks it before anything goes up. -
check-build.mjs. Download the script (Node 20 or later). This checks the folder and zips it the way the uploader expects:
bashnode check-build.mjs pack Builds/Fradiation Fradiation.zip -
tar. Windows 10 and 11 ship a
tarthat writes zips. Run it in PowerShell or cmd from the project root:powershelltar -a -c -f Fradiation.zip -C Builds FradiationThe zip holds one top-level folder,
Fradiation/, and the uploader drops it. In Git Bash,taris GNU tar and doesn't write zips. On macOS and Linux, runzip -r Fradiation.zip Fradiationfrom insideBuilds.
Zip the build folder or its contents. Don't zip Builds itself: the uploader drops one top-level folder, so index.html would end up one level too deep.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Blank frame, or 404s in the network tab | A template with root-absolute URLs such as /Build/.... Builds are served from https://<slug>.containment.cloud/<buildId>/, so the root is the wrong place. | Use relative URLs (Build/...). Use the Fradiation template. |
| "No index.html at the top of the build" on upload | The zip has an extra folder level, or index.html has another name. | Zip the build folder or its contents. If the entry file has another name, set entry in fradiation.json. |
| "Unable to parse Build/..." or a content-encoding error | A .br or .gz file that isn't in that format, because it was renamed or re-compressed by another tool. Or files from builds with different compression settings mixed in one folder. | Rebuild with Compression Format Brotli or Gzip and Decompression Fallback off. Upload the folder Unity wrote, without re-compressing anything. |
| The game doesn't fill the frame | The default template's fixed canvas size. | Use the Fradiation template, or set the canvas to 100% width and height. Set viewport in fradiation.json. |
| SDK calls do nothing | The template didn't load fradiation-sdk.js, so window.fradiation doesn't exist. Or the game runs outside the site. Or the key isn't declared in fradiation.json. | Check that fradiation-sdk.js is in the build root and the template has its script tag. On the site, open DevTools, switch the console to the game's frame and run await fradiation.ready(). connected: true means the handshake worked. In a draft, a successful call shows a toast. No toast means the site rejected the call. |
| An upload is refused with "file type not allowed" | A file with an extension off the allowed list, often under StreamingAssets. | Convert or remove it. Run check-build.mjs to list every problem at once. |
| An old version shows up | You're looking at a different build. An upload made with Upload only doesn't replace the live build. | Make the build live from the builds list. Each build has its own URL, and index.html is revalidated on every load, so a hard reload rarely matters. |
See also
- SDK for the full API and modes.
- fradiation.json for
viewport,threads, mutations and boards. - Builds and uploads for limits, paths and file types.
- Publish a game for the upload steps.