Unity WebGL Game Tutorial
Build Unity Bounce Relay from a Unity 2022 LTS WebGL export when you want Unity-authored gameplay while TPG owns room lifecycle, controller identity, state sync, and publishing.
What You Build
Unity Bounce Relay is a compact host-display plus controller example that mirrors a Unity WebGL export without requiring the Unity editor in CI:
- the host display boots
@tpgames/unity-bridgeand exposeswindow.TPG_UNITY - Unity WebGL plug-ins call the bridge through
Assets/Plugins/WebGL/TpgBridge.jslib - Unity C# scripts call plug-in functions with
DllImport("__Internal") - TPG shared state owns round-level facts such as active zone and total bounces
- TPG player state owns each controller zone, power, and bounce count
- transient bounce effects travel through
runtime.broadcast("unity:bounce", ...) - the bundle includes the web surface files plus
unity-export/project metadata - the registration script uploads, reviews, publishes, and verifies the game through the registry API
The tutorial below includes the complete fixture source. The browser bridge is published as @tpgames/unity-bridge.
Supported Unity Path
Target Unity 2022 LTS or newer WebGL exports. The current TPG package is a browser-side bridge, not a Unity editor package that exports or uploads your project for you.
Use this path when your Unity project can:
- export WebGL artifacts that can run inside a TPG host-display iframe
- include a WebGL JavaScript plug-in under
Assets/Plugins/WebGL/ - keep authoritative room facts in TPG shared/player state instead of Unity-only globals
- keep controller input in a DOM controller surface, or explicitly build a second Unity controller export
- serve the Unity loader, framework,
.data,.wasm, and streaming assets from the same bundle root
The checked-in example keeps the CI payload lightweight by including unity-export/ProjectSettings/ProjectVersion.txt, a reference TpgBridge.jslib, and a reference TpgBridge.cs. A production project should export the real Unity WebGL build artifacts into the same unity-export/ folder.
Create the Project
Start with the canonical TP Games project so the game inherits the tested scripts, CI workflow, validation, and bundled AI development skill:
If the author CLI is already installed, tpgames init unity-bounce-relay
replaces the first scaffold command.
This tutorial intentionally replaces the starter surface layout with the checked-in
Unity example. Copy the example's root host.html and controller.html in place of
host.html and controller.html, then remove vite.config.mjs before creating
the vite.config.ts shown below. Do not keep both Vite config files.
Use the checked-in example as the reference layout:
Install the normal game kit, the Unity bridge, and the authoring CLI:
For a real Unity game, export your WebGL build into unity-export/, then adapt host.html to load the generated Unity loader. Keep controller.html as a normal DOM surface unless you specifically need a second Unity WebGL export for controller UI.
Configure the Author Tools
Add the device, bundle, and registry commands to package.json:
Configure vite.config.ts with the wrapper surfaces and workbench:
Create tpgames.json; the copy rule carries the real Unity WebGL export into
a registry bundle:
Boot the Bridge
Both host and controller pages boot a normal TPG runtime and install the Unity bridge:
createUnityBridgeGame() exposes a browser runtime API on window.TPG_UNITY. The default GameObject name is TPGBridge, so Unity SendMessage callbacks can target methods such as OnTpgLifecycle, OnTpgSharedState, OnTpgPlayerState, and OnTpgMessage.
After the generated Unity loader creates an instance, attach it to the bridge:
The checked-in host shim uses a minimal SendMessage-compatible object so CI can verify the bridge without launching the Unity editor.
Add the WebGL Plug-In
Create Assets/Plugins/WebGL/TpgBridge.jslib in the Unity project:
The plug-in is the browser-to-Unity seam. It should stay thin: convert Unity calls into TPG runtime calls, and keep game rendering plus scene-local animation inside Unity.
Map Unity Lifecycle Events
Create a TPGBridge GameObject with a C# script that imports the plug-in functions and receives TPG callbacks:
Use ReportReady() when the Unity scene can receive runtime events. Use durable state methods for facts that should survive reconnect, and use broadcast() or sendToParticipant() for effects that are safe to miss.
Keep State in TPG
Unity can own rendering, animation, physics, and scene-local effects. TPG should own synchronized facts that need to reach host, controller, spectator, or future dedicated-runtime surfaces.
The example models shared state separately from Unity scene objects:
Each controller writes its own player state:
The host display listens for unity:bounce messages and updates its local feed, while durable shared/player snapshots remain available after refresh or reconnect.
Generate the Manifest
Use createUnityManifest() when rendering the bundle manifest:
The helper creates a display-plus-controller manifest aligned with the public TPG manifest contract. The public tpgames bundle command follows tpgames.json, copies the built assets, manifest, and unity-export/ into dist/, then zips the archive for registry upload.
Test on Multiple Devices Without Uploading
After exporting the current Unity WebGL build, the example's
@tpgames/sdk-dev-kit Vite plugin exposes its display and controller to the
normal TPG room flow. Install cloudflared, then run:
The command starts Vite and a temporary Cloudflare Quick Tunnel, then opens the
host room on https://play.tp.games. Scan the room QR with two or more phones.
No registry upload, API key, or bundle build is required, but the current Unity
WebGL export must already exist locally.
To use a tunnel you already manage, keep its origin port aligned with the CLI:
A Quick Tunnel exposes the local Vite server and source modules publicly. Treat
the generated host link as private because it contains the temporary
development-manifest descriptor; share only the room QR or controller invite.
Keep .env files, credentials, and private assets outside Vite's served root,
and stop the command and tunnel when finished. Use --no-open when you only
want the private host link printed.
Export, Bundle, and Register
In Unity, use WebGL export settings that are compatible with iframe delivery:
- keep compression disabled or configure server headers for the chosen compression format
- avoid threaded WebGL unless the deployed origin is cross-origin isolated
- keep the generated loader, framework, data, wasm, and streaming assets together
- test the exported canvas at mobile and desktop widths before packaging
Render the TPG bundle:
The output should include:
For local interactive publishing, start the registry and browser approval shell from a TPG repository checkout in two terminals:
Then sign in and register from the Unity example repo:
The registration script uploads the archive to /creator/uploads, submits the
version, creator-publishes it with the selected credential, fetches
/games/{gameId}/manifest, and verifies a published Unity asset under
/published-assets/{gameId}/{version}/unity-export/Assets/Plugins/WebGL/TpgBridge.jslib.
Registration delivers a built game version; it does not create a developer
account. Its default upload mode stops after upload, --mode submit requests
review, and --mode publish --yes deliberately completes creator publication.
Browser login stores a session only for the exact registry origin. Use
TPG_API_KEY only for CI or another unattended environment, keep it in the
environment rather than arguments or project configuration, and grant only the
scopes required by the selected mode.
Launch Verification
After publishing, open the web shell, select Unity Bounce Relay, join with at least two controllers, and start the game. Verify:
- the host iframe shows the Unity stage shell and bridge feed
window.TPG_UNITYexists inside the host and controller iframes- the Unity instance is attached and receives
SendMessagecallbacks - controller zone buttons and power input update player state
- Bounce broadcasts a
unity:bounceruntime message and records an analytics milestone - refreshing a controller preserves the participant seat and can continue play
- returning to the lobby stops the surface without leaving a runaway render loop
For browser QA, test mobile, tablet, laptop, and desktop widths. Unity exports often have fixed canvas sizes, so verify no horizontal overflow, readable controller controls, and visible keyboard focus on every button.
Troubleshooting
window.TPG_UNITYis undefined: confirm the host/controller page callsbootIframeGame(createUnityBridgeGame(), ...)before Unity scripts call the WebGL plug-in.- Unity callbacks never fire: confirm the bridge uses the same
gameObjectNameas your UnityTPGBridgeGameObject and that method names match the defaults or configured overrides. - Plug-in calls fail in the editor: WebGL plug-ins only run in WebGL builds; guard editor-only code and test the exported build in a browser.
- Controller effects do not reach the host: verify the controller calls
broadcast("unity:bounce", payload)and the host listens formessageevents with the same message type. - Published assets 404: inspect
dist/manifest.jsonand confirm every entry path is relative to the bundle root, not an absolute/assets/...path. - The Unity canvas overflows on phones: use a responsive wrapper around the exported canvas and keep critical controller actions in the DOM controller surface.
- CI cannot run the Unity editor: keep editor export as a local author step, then commit or upload only the generated WebGL artifacts needed by TPG packaging.
Current Limitations
@tpgames/unity-bridgeis a browser-side bridge and manifest helper, not a full Unity editor package.- The checked-in example does not include generated
.wasmor.dataartifacts; it is shaped for CI-verifiable TPG packaging. - Unity projects still need a WebGL plug-in plus a GameObject with callback methods.
- Spectator-specific Unity surfaces are not included.
- Registry review and publish are API-driven for automation, so product creator/reviewer flows should still be tested separately for production submissions.