Skip to content

MRAID 3.0 playable example: a short skeleton for Unity Ads

A short MRAID 3.0 playable skeleton for Unity Ads: wait for ready, react to viewableChange, start and pause, call mraid.open only on a CTA tap.

Published
6 min read
6 min read
Русский
Українська
On this page
  1. What MRAID is, in plain words
  2. The order of events
  3. Boot code: wait for ready, then follow viewability
  4. startPlayable and pausePlayable
  5. Open the store only from a CTA tap
  6. Check that the build also runs without MRAID
  7. What the docs don’t say

A Unity Ads playable talks to the SDK through MRAID 3.0, and the skeleton is short. Wait for ready, listen for viewableChange, start or pause the game from that handler, and call mraid.open only when the player taps a CTA after playing. The boot code below is adapted from Unity’s example on the playable specifications page. The start, pause and CTA functions are our own small implementation.

What MRAID is, in plain words

MRAID (Mobile Rich Media Ad Interface Definitions) is the protocol between an ad and the webview that shows it inside an SDK. The webview exposes a global mraid object. The ad asks it things (“are we viewable?”) and gets events from it (“now we are”). Per Unity’s docs, the Unity Ads webview injects MRAID itself, so your playable doesn’t need an MRAID script tag, and Unity Ads supports only MRAID 3.0 full-screen ads.

Several MRAID features are unsupported: expand, resize, custom close buttons, sms, tel, calendar and storePicture. For a playable you need only four calls: getState, isViewable, addEventListener and open.

The order of events

Unity’s docs set the order:

  1. Wait for the ready event before you set up the playable.
  2. Start the game only if the ad is viewable, and again when viewableChange reports that it became viewable.
  3. Pause gameplay and mute audio when the ad isn’t viewable.
  4. Open the store only from a tap on a CTA, after the player has interacted with gameplay.

Starting on page load is the common mistake. The docs say content that runs before the ad is viewable uses up the player’s first seconds and looks broken.

Boot code: wait for ready, then follow viewability

This is adapted from Unity’s example. The typeof mraid branch is our addition so the same file runs in a desktop browser (more on that below).

function boot() {
  if (typeof mraid === "undefined") { // desktop browser: no MRAID, just run
    startPlayable();
    return;
  }
  if (mraid.getState() === "loading") {
    mraid.addEventListener("ready", onReady);
  } else {
    onReady();
  }
}

function onReady() {
  mraid.addEventListener("viewableChange", onViewableChange);
  if (mraid.isViewable()) startPlayable();
}

function onViewableChange(viewable) {
  if (viewable) startPlayable(); // start, or resume if already started
  else pausePlayable();
}

boot(); // last line of the inline script

Unity’s example calls startPlayable() and pausePlayable() without defining them, and the docs we read don’t define them either. Here is one version.

startPlayable and pausePlayable

A paused flag plus an animation-frame loop is enough. startPlayable() can be called many times (first start, every resume), so it must not create a second loop.

let paused = true;
let raf = 0;
let played = false; // true after the first gameplay input

function startPlayable() {
  paused = false;
  if (!raf) raf = requestAnimationFrame(frame);
  // Unmute through your Web Audio path here.
}

function pausePlayable() {
  paused = true;
  cancelAnimationFrame(raf);
  raf = 0;
  // Mute through your Web Audio path here.
}

function frame(now) {
  raf = requestAnimationFrame(frame);
  update(now); // your game step
  render();    // your drawing
}

function onGameplayInput() {
  if (paused) return; // ignore taps while not viewable
  played = true;
  // ...your game input
}

Two details. First, keep your own last-timestamp variable inside update and reset it when you resume, or the first frame after a pause sees a huge time step. Second, cancelAnimationFrame plus resetting raf is what stops a quick pause-then-start from running two loops.

For audio, Unity’s docs say to use the Web Audio API as the primary path, with HTMLAudioElement only as a fallback because it ignores the iOS mute switch, and to mute and resume from your viewableChange handler. Mute and unmute through that path in the two marked places. We leave the audio code out because it depends on how your game builds its audio graph.

Open the store only from a CTA tap

According to the same page, the store opens through mraid.open, triggered by a player tap on a CTA after the player has interacted with gameplay. The first tap can’t open the store, and a gameplay element that opens it counts as a deceptive redirect. Show the CTA button once the player has played, for example on your own end screen.

function onCtaTap() {
  if (paused || !played) return; // the first tap can't open the store
  const ua = navigator.userAgent || navigator.vendor;
  let url = "https://apps.apple.com/app/idyourgame"; // placeholder
  if (/android/i.test(ua)) {
    url = "https://play.google.com/store/apps/details?id=yourgame"; // placeholder
  }
  if (typeof mraid === "undefined") window.open(url, "_blank"); // desktop test only
  else mraid.open(url);
}

The platform check follows Unity’s example: newer iPadOS versions report Macintosh in the user agent, so test for Android and treat every other platform as iOS. Replace both URLs with your real store pages.

Check that the build also runs without MRAID

Unity’s playable best practices ask you to make sure the final build also runs without MRAID, so you can test the complete playable in a desktop browser. View it at mobile resolution and confirm the download button opens the right store page. The typeof mraid branches above are what make that possible.

A desktop browser is a partial check only. The same page says a desktop browser or simulator doesn’t run your playable in the real ad unit, and many common rejection reasons only appear on a real device. Use the Ad Testing app: select Show QR code on a creative pack in the User Acquisition dashboard, scan it, and rotate the device while the playable runs. The pre-submission list also asks you to confirm that the playable pauses and resumes on viewableChange and that the CTA opens the right store on iOS and Android.

If the build shows a blank screen in the ad unit, see playable blank screen on Unity Ads. For the full set of file, layout and moderation rules, see the Unity Ads playable requirements checklist. DarkCore Ramet also has a Playable Builder; whatever tool makes the file, run it through these checks.

What the docs don’t say

  • The definitions of startPlayable() and pausePlayable(). Unity’s example calls them; the code above is ours.
  • Which SDK or webview versions inject MRAID.
  • How Unity’s moderation tests a playable.
  • How loading time is measured. The best practices page asks for the first interaction in less than 2 s on a mid-tier device but doesn’t define that device.
  • Audio and video codecs, frame rates and duration limits.

We did not run this skeleton inside Unity’s ad unit. Treat it as a starting point, and test it on a device with the Ad Testing app before you submit.

In DarkCore Ramet

Test the playable on a real device before upload

DarkCore Ramet runs bulk launch for Unity Ads and includes a Playable Builder. Check any build against Unity's spec and on a device first.

All articles