DEV Community

Cover image for Shipping single-file HTML5 playable ads: how my Unity 2022 WebGL pipeline hit 4.7 MiB under ad-network constraints
LUMENFORM
LUMENFORM

Posted on Edited on

Shipping single-file HTML5 playable ads: how my Unity 2022 WebGL pipeline hit 4.7 MiB under ad-network constraints

Last month I set out to answer a simple question: how small and how self-contained can a Unity WebGL playable ad actually be? The short answer, after three builds: 4.7 MiB for a match-3 in one file, ~8.2 MiB for an idle-growth game in one file, and a 9.61 MiB 18-file 3D microsite — and the microsite is deliberately not single-file, because it doesn't ship inside an ad network.

Playable ads live inside ad network SDKs (MRAID containers), and networks impose hard caps: file size, single-file delivery, no external requests, muted audio until first interaction, and a CTA that fires mraid.open(). Those constraints rule out the default Unity WebGL export, which ships as index.html + Build/*.unityweb + TemplateData/*. My pipeline became: Unity 2022 → WebGL export → repack.

1. Export settings that matter

  • Brotli/gzip compression on the build payload
  • Strip engine code, high managed stripping level
  • No exceptions, no stack traces (smaller + faster startup)
  • IL2CPP + code stripping

2. Repack to a single file

The default export ships as index.html + Build/*.unityweb + TemplateData/*. For playable delivery this gets repacked into one inlined index.html:

  • base64-inline the .wasm, .data, and .framework.js payloads
  • inline the loader
  • no external requests for the two playable builds; in DevTools, the network panel shows exactly one document request

This applies to the two playable builds (Prism Vein and Core Forge). The 3D microsite (Atlas Relay) stays as 18 files — 9.61 MiB — because it is not a playable ad and does not need the single-file constraint.

3. Audio discipline

Network rules require muted audio until first tap. I keep an AudioContext suspended until the first pointer event, then resume it — sounds only fire after real interaction.

4. CTA through MRAID

The end-card CTA checks for mraid availability:

  • inside an MRAID container → mraid.open(url)
  • outside (plain web demo) → window.open(url) fallback

I wrapped the match-3 build in a MRAID 2.0 harness locally (getVersion() returns 2.0) to verify the bridge works.

Size results

Build Size Languages Single file?
Match-3 (Prism Vein) 4.7 MiB EN / ZH (two separate builds) Yes
Idle-growth (Core Forge) ~8.2 MiB EN+ZH runtime switch in one build Yes
3D microsite (Atlas Relay) 9.61 MiB (18 files) EN+ZH No

Most of the match-3 savings came from texture discipline (palette-swapped sprites) and audio compression, not from hacking the engine.

What I'd tell anyone building playables

  • Budget the size cap first, design second
  • Test inside a real MRAID harness, not just a browser tab
  • Keep one inlined file as the single source of truth for playable delivery — and don't force single-file on things that don't need it

A question for you

I'm a solo developer building these end-to-end (Unity 2022 → WebGL → repack). What's the smallest single-file Unity WebGL payload you've shipped to an ad network, and how do you handle a cap under 5 MiB? If this helped, leave a reaction or ask below — I'll answer in the comments. All three demos are live at https://www.lumenformlab.com

Top comments (0)