@uptimizr/unity
v0.2.4
Published
Unity (WebGL export) connector for Uptimizr — JS-only pointer/perf capture plus a bridged camera-pose / world-space tier via a thin engine-side shim.
Maintainers
Readme
@uptimizr/unity
The Unity (WebGL export) connector for Uptimizr (ADR 0045). Unity compiles to
WebAssembly and renders into a <canvas>, so there is no live JS scene to read — this
connector is built on @uptimizr/web-export and works in two
tiers:
| Tier | Engine code? | Captures |
| ----------- | ----------------------------------------------- | --------------------------------------------------------------- |
| JS-only | none | pointer move/click heatmaps, FPS / long frames, JS errors |
| Bridged | a thin copy-in shim (see bridge/) | camera pose → view-direction heatmap, world-space picks, replay |
Unity's native world frame is left-handed, y-up, meters — already Uptimizr's canonical wire frame (ADR 0018), so world-space payloads need no axis conversion.
Status. The JS-only tier is covered end to end by the playground's Playwright suite. The bridged tier is preview: the
.jslibshim is sanity-tested undernode:vmon everypnpm test, and the full round trip through a real Unity WebGL export is driven by a Playwright spec against a local build of the sample project inexamples/unity-web-export/— it is labelled verified against a local build once a maintainer has run that spec. See Verifying against a real export.
Install
npm install @uptimizr/unityThe engine-side bridge is a copy-in asset (a .jslib plugin + a small
MonoBehaviour), not an npm dependency — see bridge/.
Usage
import { trackUnity } from "@uptimizr/unity";
const { client, bridge } = trackUnity({
projectId: "your-project",
endpoint: "https://collect.example.com",
canvas: () => document.querySelector("#unity-canvas"),
});
// ... later, on teardown
await client.stop("manual");trackUnity creates the client, registers the JS-only tier collector, exposes the
engine bridge (default window.__uptimizr_unity__), and starts the session with
Unity's connector provenance. The JS-only tier captures immediately; wire the
engine-side shim to bridge to add camera pose, picks, and replay.
Advanced: wire it up yourself
import { UptimizrClient } from "@uptimizr/sdk-core";
import { unityCollector, UNITY_FRAME } from "@uptimizr/unity";
const client = new UptimizrClient({ projectId: "your-project", endpoint: "..." });
client.use(unityCollector({ canvas: () => document.querySelector("#unity-canvas") }));
client.start();Engine-side bridge
The bridged tier needs a thin copy-in shim that pushes world-space pose / picks / FPS
across Unity's JS interop boundary. It ships in bridge/ as two copy-in
files:
Uptimizr.jslib→ copy toAssets/Plugins/WebGL/Uptimizr.jslib.UptimizrUnityBridge.cs→ copy underAssets/and add theUptimizrUnityBridgecomponent to a GameObject (it samples the activeCamera, raycast picks, and FPS).
Make sure trackUnity(...) runs on the host page before the export starts, so the bridge
global (window.__uptimizr_unity__) exists. The shim does no coordinate math — it
pushes Unity's native-frame values and the connector normalizes them. On start it asserts
the bridge protocol version matches BRIDGE_PROTOCOL_VERSION (1). See
bridge/README.md for the full contract and the JS API table.
Verifying against a real export
Unity is not part of the JS toolchain, so verification is split in two:
- Always on (CI):
src/__tests__/jslib.test.tsloadsbridge/Uptimizr.jslibas text, evaluates it in anode:vmsandbox with mocked Emscripten globals (mergeInto,LibraryManager.library,UTF8ToString,window), resolves the$UptimizrUnityBridgedependency the way Emscripten does, and asserts every export forwards towindow.__uptimizr_unity__— plus that each export declares its__deps(a missing dep is a silent link-time drop) and that the export set matches the[DllImport]s inUptimizrUnityBridge.cs. - One manual step (maintainer): the sample Unity 6 project in
examples/unity-web-export/carries copies of both bridge files (a lint script fails if they drift), a camera, and three named cubes with colliders. Open it in Unity Hub and File → Build Profiles → Web → Build intoexamples/unity-web-export/dist/with Compression Format: Disabled. Thenexamples/playground/e2e/unity-export.spec.tsserves the build, startstrackUnitybeforecreateUnityInstance, clicks the centre cube, and assertssession_start.connector.name === "unity",camera_sample,mesh_interaction, andframe_perfreach the collector. Without a build the spec skips (the default in CI).
Privacy
No client-side persistent IDs and no PII by default (ADR 0003). client.stop() tears
down every listener, timer, and animation-frame callback.
License
Apache-2.0.
