Search Results

Your Game on Your Website

Put a Clayground game on your own website at your own URL — as plain static files, with no build step. You write QML; the Clayground Web Runtime (a prebuilt WASM bundle) does the rest.

Quick start

  1. Download clayground-starter.zip from the latest release (attached from v2026.3 on).
  2. Unzip it and serve it locally:

    unzip clayground-starter.zip -d mygame && cd mygame
    python3 -m http.server 8080
    
  3. Open http://localhost:8080, then edit Main.qml, save, reload. That is the whole development loop.

Opening index.html directly from the file system (file://) does not work — the runtime needs HTTP.

What’s in the folder

file what it is
Main.qml your game — edit this
index.html minimal page that boots the runtime and loads Main.qml
clayground.wasm + clayground.js the Clayground Web Runtime (Qt inside)
qtloader.js Qt’s WASM loader
coi-serviceworker.js header shim for hosts that can’t set COOP/COEP
LICENSES/ + RUNTIME-MANIFEST.json license texts + exact versions — keep them with the runtime

Everything referenced from Main.qml — images, sounds, more QML files — is fetched relative to it: put files next to it and use relative paths. One wrinkle: QML directory imports over HTTP need a qmldir file listing the sibling components.

3D models and animation

Qt Quick 3D is in the runtime, and two more modules since v2026.7: QtQuick.Timeline (what balsam emits for skeletal animation clips) and QtQuick3D.AssetUtils (RuntimeLoader, glTF/GLB parsed at load time).

Not in the runtime: QtQuick3D.Physics, so ClayWorld3d and Box3DBody are desktop-only. 3D scenes for the web use Canvas3D directly.

One wrinkle: Qt opens meshes, textures, .qad keyframe files and GLBs with QFile, which cannot read from a URL - a Model { source: "meshes/x.mesh" } served over HTTP silently shows nothing. So the app shell preloads them: keep such files under assets/ next to Main.qml and run the script that ships in the folder

python3 make-assets-manifest.py        # -> assets-manifest.json, one entry per file under assets/

whenever you add, rename or remove an asset. index.html fetches the listed files into the in-memory filesystem under /game/ before your game starts; a file that cannot be fetched is logged in the console and skipped rather than blocking the start. Reference them with file:///game/<path>:

Loader3D { source: "file:///game/assets/orc/Orc.qml" }        // balsam output, relative
                                                             // meshes/maps/animations resolve
RuntimeLoader { source: "file:///game/assets/orc.glb" }       // or the GLB itself

balsam model.glb -o assets/orc (Qt’s importer, part of any Qt install) produces the QML, .mesh, textures and .qad files. Everything else - QML, images, sounds - keeps loading by relative URL as before. A .qml referenced only by URL is not compiled, so keep its imports to modules the runtime has; the console reports module "X" is not installed otherwise.

Deploying

Copy the folder to any static host — that is the whole deployment.

GitHub Pages — push the folder to a repository, enable Pages. Pages cannot set response headers, so keep coi-serviceworker.js.

Netlify — drag the folder into the dashboard. Netlify supports real headers; add a _headers file and you can delete the shim:

/*
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp

nginx — serve the directory and set the headers in the location block:

location /mygame/ {
    add_header Cross-Origin-Opener-Policy same-origin;
    add_header Cross-Origin-Embedder-Policy require-corp;
}

Cross-origin isolation, explained once

The runtime uses multithreaded WebAssembly, which requires the page to be cross-origin isolated (the two headers above). Without isolation you get a black canvas or a SharedArrayBuffer error in the console.

  • On hosts that can’t set headers, the bundled coi-serviceworker.js injects them via a service worker — expect one automatic page reload on the first visit. If your host sets real headers, delete the shim (one line in index.html).
  • Isolation means assets from other domains need CORS/CORP headers — keep your game’s assets in the game folder (same origin) and you never have to think about it.
  • Embedding the game in an <iframe> on another site requires that site to be cross-origin isolated too.

All evergreen browsers (Chrome 92+, Firefox 79+, Safari 15.2+) support this; some in-app webviews do not.

Versions and updates

The bundle you download is a specific runtime version — deployed games never change behind your back. Check which version you’re running in the browser console: the runtime prints Clayground Web Runtime v2026.7 (Qt 6.10.1) on startup, and RUNTIME-MANIFEST.json carries the same data.

To update (e.g. after a security patch release — see SECURITY.md): download the new bundle and re-copy everything except your own files (Main.qml and your assets). QML written against a runtime version keeps working on that version forever.

Licensing in two minutes

Clayground itself is MIT. The runtime binary additionally contains Qt, and one of the bundled modules — Qt Quick 3D — is GPL-3.0 for open-source users. What that means for you:

your game your QML/assets
2D only yours, any license — closed source is fine
uses the 3D API (Clayground.Canvas3d etc.) needs a GPL-compatible license (MIT, BSD, Apache-2.0 all qualify)
commercial/closed 3D needs Qt commercial licensing — or build without Quick3D

Hosting the runtime is redistribution, so keep LICENSES/ and RUNTIME-MANIFEST.json next to it — already done if you copy the whole folder. These constraints come from Qt’s licensing of Qt Quick 3D, not from Clayground. Details and sources: LICENSES/NOTICE.md in the bundle. (This is a good-faith summary, not legal advice.)

Troubleshooting

  • Black canvas / nothing renders — the page isn’t cross-origin isolated: check that coi-serviceworker.js is present (or your headers are set) and reload once. window.crossOriginIsolated in the console must be true.
  • Main.qml changes don’t show — hard-reload; some hosts cache aggressively. For local work python3 -m http.server doesn’t cache.
  • Which runtime version is this? — console banner on startup, or clayground.claygroundVersion() in the browser console.
  • Images/sounds don’t load from another domain — that’s isolation (see above); host them next to Main.qml instead.