SKILL DETAIL
levelplay-unity-integration
unity-technologies/skills/levelplay-unity-integration
This skill guides developers through integrating the LevelPlay mediation SDK into a Unity game, installed via the Ads Mediation UPM package. LevelPlay is Unity's ad mediation platform that connects your game to multiple ad networks simultaneously, running a unified auction to maximize competition for each impression. The skill covers the full integration workflow: verifying the Unity environment, understanding business goals, installing the SDK via UPM, resolving native dependencies for Android and iOS, obtaining the App Key and ad unit IDs, configuring AdMob keys (if used), initializing the SDK, and implementing rewarded, interstitial, and banner ads. It also includes platform-specific setup for iOS (ATT, SKAdNetwork) and Android (API 33+ permissions), as well as guidance for migrating from older SDK versions or Unity Ads, and troubleshooting common issues like Gradle or CocoaPods dependency errors, LevelPlay namespace errors, and privacy compliance settings.
Installation
npx skills add https://github.com/unity-technologies/skills --skill levelplay-unity-integration
Skill files
SKILL.md
Last synced · Sep 13, 2026
CHANGELOG.md›
# Changelog
## v0.10.0 — 2026-08-21 — Workflow spine, and a hard install gate
Moves reference material out of `SKILL.md` and makes the SDK install a verified step rather than an assumed one.
**Changed:**
- `SKILL.md` is now the workflow spine only, going from about 1,100 lines to 435. The dependency-resolution, testing-and-validation and troubleshooting material that was inlined in it moves into `references/`. Nothing was dropped; it is read on demand instead. `SKILL.md` is loaded in full on every invocation while a reference file is read only when a step links it, so this is a saving on every run that does not need the detail.
- The reference set grows from nine files to twelve: `dependency-resolution.md`, `testing-and-validation.md` and `troubleshooting.md` are now separate files.
**Added:**
- **A hard install-verification gate at Step 3.** No LevelPlay code is written until `com.unity.services.levelplay` is confirmed present in `Packages/packages-lock.json`, read from the project rather than taken from the Package Manager window or an earlier turn. The package is easy to believe is installed: its display name is **Ads Mediation** while the recorded id is `com.unity.services.levelplay`, two similarly named packages are the wrong ones, and the install prompts for a second package partway through. Code written before the id resolves fails with `CS0246` on every LevelPlay symbol, which reads as a code problem rather than an install problem. The gate also distinguishes "the install never happened" from "Unity has not resolved it yet", because the fix differs.
- The deprecated-APIs section now states explicitly that `SetGDPRConsents(Dictionary)` is **not** deprecated on SDK 9.4.x, where it is the correct call, and only becomes `[Obsolete]` on 9.5.0+. It is kept out of the deprecated list rather than listed with a caveat, so it cannot be read the wrong way round.
## v0.9.0 — 2026-08-17 — SDK 9.x migration support
Adds guided migration to the LevelPlay 9.x SDK and the current Ad Unit (MADU) APIs.
**New:**
- "New Integration or Migration?" routing step at the start of the workflow
- Migration reference guide covering five scenarios: SDK upgrade (.unitypackage or UPM, including switching from .unitypackage to UPM), init API migration (IronSource.Agent to LevelPlay.Init), ad unit API migration (rewarded, interstitial, banner, and the ILRD handler), Maven Central dependency build failures, and Unity Ads (Advertisement Legacy) migration
- Upgrade safety flow: Developer Settings values and installed adapters are inventoried, and the user confirms, before any folder deletion; post-upgrade steps cover adapter reinstall, settings re-entry, and removal of the stale LEVELPLAY_DEPENDENCIES_INSTALLED scripting define when switching from .unitypackage to UPM
- Migration completeness checklist covering requirements that a line-by-line translation misses: placement capping checks when dashboard placements are used, an explicit rewarded load trigger, the version API mappings, correct ILRD event names, preserved logging, HideAd vs DestroyAd intent for legacy destroyBanner calls, and removal of onApplicationPause
- Compilation check after migration edits, with errors fixed before presenting results
- Skill description now also triggers on SDK upgrades, deprecated IronSource.Agent APIs, and Unity Ads migration
**Fixed:**
- Banner adaptive-size example used a constructor form that does not compile on any 9.x version; now configured through Config.Builder (verified against 9.0.0, 9.4.0, and 9.5.0 source)
- API mapping corrections: validateIntegration maps to LevelPlay.ValidateIntegration (not LaunchTestSuite); pluginVersion maps to LevelPlay.PluginVersion (distinct from UnityVersion); onApplicationPause is removed in 9.x with no replacement; the legacy ILRD subscription maps to LevelPlay.OnImpressionDataReady on SDK 9.4.x and earlier or per-instance OnAdImpressionDataReady on 9.5.0+
- Unity Ads migration now surfaces that LevelPlay.Init has no test-mode parameter (Test Suite or dashboard test mode are the equivalents) instead of dropping the flag silently
- Package edits during upgrades touch only manifest.json; packages-lock.json is never hand-edited
- Maven Central migration is mentioned only when the project actually needs it
- Corrected a consent-callback name mismatch in the privacy reference, and a banner troubleshooting example that called a method banners do not have
## v0.8.0 — 2026-08-05 — Version-aware ILRD (SDK 9.5.0), rewarded load lifecycle, and improved activation
Accuracy and activation updates reflecting current LevelPlay SDK behavior.
**Impression-level revenue (ILRD) — SDK 9.5.0 API change**
- ILRD now documents both delivery mechanisms: the single global `LevelPlay.OnImpressionDataReady` event (SDK 9.4.x and earlier) and the per-ad-instance `OnAdImpressionDataReady` events on each ad object (SDK 9.5.0+), which replace the global event.
- The global event still exists but is deprecated on SDK 9.5.0+ and generates a compiler warning.
- Updated the initialization step and the rewarded/interstitial/banner references to direct SDK 9.5.0+ users to the per-instance approach.
**Rewarded ad load lifecycle**
- Clarified that `LoadAd()` must be called explicitly; the SDK does not auto-manage rewarded loading (unlike the legacy IronSource API).
- Reframed the guidance so explicit, publisher-triggered loading is the default, with eager preloading documented as an optional pattern. Applies to `references/rewarded-api.md` and the loading-strategy guidance in `SKILL.md`.
**Description and activation**
- Reworked the skill description to increase activation on general ad and monetization requests, not only when a developer names LevelPlay.
- Added guidance at the top of the skill directing the agent to run it as an interactive, step-by-step workflow and use the reference files, rather than answering from general knowledge.
## v0.7.0 — 2026-06-12 — Initial public beta release
First release of the LevelPlay Unity integration skill, released as public beta.
**Features:**
- Step-by-step installation of the LevelPlay SDK using the Ads Mediation package in Unity Package Manager
- Native dependency resolution for Android and iOS
- SDK initialization with three code organization options
- Ad unit strategy recommendations based on business goals (revenue-focused, UX-focused, or balanced)
- Implementation guides for rewarded ads, interstitials, and banner ads
- Privacy compliance support (GDPR, CCPA, COPPA)
- iOS setup (App Tracking Transparency, SKAdNetwork)
- Impression-level revenue tracking (ILRD)
- Testing guidance using mock ads and the LevelPlay Test Suite
## Feedback
This skill is currently in beta. [Share your feedback here](https://docs.google.com/forms/d/e/1FAIpQLSe7WvWozJ67KjgOLglSBvLug8JdgEYk895nn_BHZs0HS_bWJA/viewform).
README.md›
# LevelPlay Unity Integration Skill
  
> 🧪 **Note:** This skill is in beta and will be shaped by your feedback. Try it out and [let us know what you think](https://docs.google.com/forms/d/e/1FAIpQLSe7WvWozJ67KjgOLglSBvLug8JdgEYk895nn_BHZs0HS_bWJA/viewform)!
A skill that guides Unity developers through integrating the LevelPlay SDK using the Ads Mediation package in Unity Package Manager: from installation to fully working rewarded ads, interstitials, and banners.
Compatible with Claude Code, GitHub Copilot, Cursor, Cline, and [50+ other agents](https://skills.sh).
## What it does
When you activate this skill, your agent walks you step by step through the complete LevelPlay integration:
1. **Installing the SDK** via the Ads Mediation package in Unity Package Manager
2. **Resolving native dependencies** for Android and iOS builds
3. **Collecting credentials** from the LevelPlay dashboard
4. **Configuring privacy compliance** (GDPR, CCPA, COPPA) if needed
5. **Initializing the SDK** in your project, with three code organization options to fit your existing setup
6. **Recommending an ad unit strategy** based on your goals (revenue-focused, UX-focused, or balanced)
7. **Implementing ad formats** — rewarded ads, interstitials, and banners — with production-ready C# code
8. **Testing and validating** using mock ads in the Unity Editor and the LevelPlay Test Suite on device
The skill also covers iOS-specific setup (App Tracking Transparency, SKAdNetwork), impression-level revenue tracking (ILRD) for analytics platforms, bid floors, and common troubleshooting.
## Requirements
- A Unity project using an LTS or actively developed version of the Unity Editor
- LevelPlay Unity package and SDK version 9.4.0+
- A LevelPlay account: [get started here](https://platform.ironsrc.com/)
Documentation for setting up the LevelPlay Unity package: see the [Unity Package Integration guide](https://docs.unity.com/en-us/grow/levelplay/sdk/unity/package-integration).
## Installation
```bash
npx skills add Unity-Technologies/skills
```
Then activate the `levelplay-unity-integration` skill in your agent.
## Using the skill
Type `/levelplay-unity-integration` to activate the skill, then describe what you want to do:
- *"I want to add rewarded ads to my Unity game"*
- *"Help me integrate LevelPlay into my project"*
- *"I need to add interstitial ads between levels"*
- *"I have LevelPlay installed, help me implement banner ads"*
You can jump in at any step. If the Unity package and SDK are already installed, your agent will pick up from where you are.
## Privacy & Legal
> **Note:** This skill provides technical integration guidance, including for LevelPlay's privacy APIs. It is not legal advice, and it does not determine which laws apply to your app — that depends on your users, your data practices, and your distribution. Consult your own legal counsel, and refer to [Regulation Advanced Settings for Unity](https://docs.unity.com/en-us/grow/levelplay/sdk/unity/regulation-advanced-settings) for the authoritative LevelPlay documentation.
## What's in this folder
```
levelplay-unity-integration/
├── SKILL.md # The workflow spine: decisions, checkpoints, questions
├── references/ # Detail read on demand, linked from the step that needs it
│ ├── initialization-api.md
│ ├── rewarded-api.md
│ ├── interstitial-api.md
│ ├── banner-api.md
│ ├── ilrd-api.md
│ ├── privacy-settings.md
│ ├── ios-setup.md
│ ├── dependency-resolution.md
│ ├── testing-and-validation.md
│ ├── troubleshooting.md
│ ├── migration-sdk-9.md
│ └── best-practices.md
├── CHANGELOG.md
└── README.md
```
references/banner-api.md›
# Banner Ads API Reference
## Contents
- [Overview](#overview)
- [Key Characteristics](#key-characteristics)
- [Implementation Pattern](#implementation-pattern)
- [API Reference](#api-reference)
- [Data Types](#data-types)
- [Best Practices](#best-practices)
- [Common Issues](#common-issues)
- [Testing Checklist](#testing-checklist)
## Overview
Banner ads are rectangular ads that occupy a portion of the screen. They can be displayed at the top or bottom of the screen and remain visible while users interact with your app.
## Key Characteristics
- **Persistent**: Remain on screen until explicitly destroyed
- **Always visible**: Can distract from gameplay if poorly positioned
- **Best for**: Menu screens, waiting periods, low-attention moments
## Implementation Pattern
### Basic Banner Implementation
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class BannerAdManager : MonoBehaviour
{
private LevelPlayBannerAd bannerAd;
private string adUnitId = "YOUR_BANNER_AD_UNIT_ID";
void Start()
{
// Create the banner ad object using constructor
bannerAd = new LevelPlayBannerAd(adUnitId);
// ILRD (SDK 9.5.0+): if impression-revenue tracking is enabled, also subscribe here —
// bannerAd.OnAdImpressionDataReady += OnImpressionDataReady; // see references/ilrd-api.md
// Register event listeners
bannerAd.OnAdLoaded += OnAdLoaded;
bannerAd.OnAdLoadFailed += OnAdLoadFailed;
bannerAd.OnAdDisplayed += OnAdDisplayed;
bannerAd.OnAdDisplayFailed += OnAdDisplayFailed;
bannerAd.OnAdClicked += OnAdClicked;
bannerAd.OnAdExpanded += OnAdExpanded;
bannerAd.OnAdCollapsed += OnAdCollapsed;
bannerAd.OnAdLeftApplication += OnAdLeftApplication;
// Load and show banner
LoadBanner();
}
void OnDestroy()
{
// Unregister event listeners
if (bannerAd != null)
{
bannerAd.OnAdLoaded -= OnAdLoaded;
bannerAd.OnAdLoadFailed -= OnAdLoadFailed;
bannerAd.OnAdDisplayed -= OnAdDisplayed;
bannerAd.OnAdDisplayFailed -= OnAdDisplayFailed;
bannerAd.OnAdClicked -= OnAdClicked;
bannerAd.OnAdExpanded -= OnAdExpanded;
bannerAd.OnAdCollapsed -= OnAdCollapsed;
bannerAd.OnAdLeftApplication -= OnAdLeftApplication;
// ILRD (9.5.0+): bannerAd.OnAdImpressionDataReady -= OnImpressionDataReady;
}
// Destroy banner
DestroyBanner();
}
public void LoadBanner()
{
Debug.Log("Loading banner ad...");
bannerAd.LoadAd();
}
public void ShowBanner()
{
Debug.Log("Showing banner ad");
bannerAd.ShowAd();
}
public void HideBanner()
{
Debug.Log("Hiding banner ad");
bannerAd.HideAd();
}
public void DestroyBanner()
{
if (bannerAd != null)
{
Debug.Log("Destroying banner ad");
bannerAd.DestroyAd();
bannerAd = null;
}
}
// Event Callbacks
private void OnAdLoaded(LevelPlayAdInfo adInfo)
{
Debug.Log($"Banner ad loaded - network: {adInfo.AdNetwork}, revenue: ${adInfo.Revenue}");
// Banner is ready, can call ShowAd() if needed
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Banner ad load failed: {error.ErrorMessage}");
// Retry loading after delay
Invoke(nameof(LoadBanner), 60f);
}
private void OnAdDisplayed(LevelPlayAdInfo adInfo)
{
Debug.Log($"Banner ad displayed - network: {adInfo.AdNetwork}, placement: {adInfo.PlacementName}");
}
private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
{
Debug.LogError($"Banner ad display failed: {error.ErrorMessage}");
// Retry loading
Invoke(nameof(LoadBanner), 60f);
}
private void OnAdClicked(LevelPlayAdInfo adInfo)
{
Debug.Log($"Banner ad clicked - network: {adInfo.AdNetwork}");
}
private void OnAdExpanded(LevelPlayAdInfo adInfo)
{
Debug.Log($"Banner ad expanded - network: {adInfo.AdNetwork}");
// Optionally pause game if banner expands to full screen
}
private void OnAdCollapsed(LevelPlayAdInfo adInfo)
{
Debug.Log($"Banner ad collapsed - network: {adInfo.AdNetwork}");
// Resume game if paused
}
private void OnAdLeftApplication(LevelPlayAdInfo adInfo)
{
Debug.Log($"Banner ad left application - network: {adInfo.AdNetwork}");
}
}
```
### Banner with Custom Configuration
Use the Config Builder pattern to customize banner size, position, and behavior:
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class CustomBannerAdManager : MonoBehaviour
{
private LevelPlayBannerAd bannerAd;
private string adUnitId = "YOUR_BANNER_AD_UNIT_ID";
void Start()
{
// Create config using Builder pattern
var configBuilder = new LevelPlayBannerAd.Config.Builder();
// Set banner size (UPPERCASE!)
configBuilder.SetSize(LevelPlayAdSize.LARGE); // 320x90
// Set position on screen
configBuilder.SetPosition(LevelPlayBannerPosition.BottomCenter);
// Auto-display when loaded
configBuilder.SetDisplayOnLoad(true);
// Respect safe area (Android only)
configBuilder.SetRespectSafeArea(true);
// Build the config
var bannerConfig = configBuilder.Build();
// Create banner with config
bannerAd = new LevelPlayBannerAd(adUnitId, bannerConfig);
// Register callbacks
bannerAd.OnAdLoaded += OnAdLoaded;
bannerAd.OnAdLoadFailed += OnAdLoadFailed;
bannerAd.OnAdDisplayed += OnAdDisplayed;
// Load banner
bannerAd.LoadAd();
}
void OnDestroy()
{
if (bannerAd != null)
{
bannerAd.OnAdLoaded -= OnAdLoaded;
bannerAd.OnAdLoadFailed -= OnAdLoadFailed;
bannerAd.OnAdDisplayed -= OnAdDisplayed;
bannerAd.DestroyAd();
}
}
private void OnAdLoaded(LevelPlayAdInfo adInfo)
{
Debug.Log("Custom banner loaded");
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Banner load failed: {error.ErrorMessage}");
}
private void OnAdDisplayed(LevelPlayAdInfo adInfo)
{
Debug.Log("Custom banner displayed");
}
}
```
### Advanced: Context-Aware Banner Management
Show banners only in appropriate scenes:
```csharp
using UnityEngine;
using UnityEngine.SceneManagement;
using Unity.Services.LevelPlay;
public class ContextAwareBannerManager : MonoBehaviour
{
private LevelPlayBannerAd bannerAd;
private string adUnitId = "YOUR_BANNER_AD_UNIT_ID";
// Scenes where banner should be shown
private string[] bannerEnabledScenes = { "MainMenu", "LevelSelect", "Shop" };
private bool isBannerLoaded = false;
void Start()
{
// Make this manager persist across scenes
DontDestroyOnLoad(gameObject);
// Create banner ad
bannerAd = new LevelPlayBannerAd(adUnitId);
// Register callbacks
bannerAd.OnAdLoaded += OnAdLoaded;
bannerAd.OnAdLoadFailed += OnAdLoadFailed;
// Register for scene changes
SceneManager.sceneLoaded += OnSceneLoaded;
// Load banner
bannerAd.LoadAd();
// Check initial scene
OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single);
}
void OnDestroy()
{
// Unregister callbacks
if (bannerAd != null)
{
bannerAd.OnAdLoaded -= OnAdLoaded;
bannerAd.OnAdLoadFailed -= OnAdLoadFailed;
}
SceneManager.sceneLoaded -= OnSceneLoaded;
// Destroy banner
if (bannerAd != null)
{
bannerAd.DestroyAd();
}
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (ShouldShowBannerInScene(scene.name))
{
ShowBanner();
}
else
{
HideBanner();
}
}
private bool ShouldShowBannerInScene(string sceneName)
{
foreach (string enabledScene in bannerEnabledScenes)
{
if (sceneName == enabledScene)
{
return true;
}
}
return false;
}
private void ShowBanner()
{
if (isBannerLoaded && bannerAd != null)
{
bannerAd.ShowAd();
}
}
private void HideBanner()
{
if (bannerAd != null)
{
bannerAd.HideAd();
}
}
private void OnAdLoaded(LevelPlayAdInfo adInfo)
{
Debug.Log("Banner ad loaded");
isBannerLoaded = true;
// Show banner if we're in an enabled scene
if (ShouldShowBannerInScene(SceneManager.GetActiveScene().name))
{
ShowBanner();
}
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Banner ad load failed: {error.ErrorMessage}");
isBannerLoaded = false;
Invoke(nameof(LoadBanner), 60f);
}
private void LoadBanner()
{
if (bannerAd != null)
{
isBannerLoaded = false; // Reset flag when loading
bannerAd.LoadAd();
}
}
}
```
## API Reference
### Constructor
#### `new LevelPlayBannerAd(string adUnitId)`
Create a banner ad object with default configuration.
**Parameters:**
- `adUnitId`: The ad unit identifier from your LevelPlay dashboard
**Returns:** `LevelPlayBannerAd` object
**Usage:**
```csharp
LevelPlayBannerAd bannerAd = new LevelPlayBannerAd("your_ad_unit_id");
```
**Default behavior:**
- Size: Standard banner (320x50)
- Position: Bottom center
- DisplayOnLoad: true (banner shows automatically when loaded)
**Important:** Call this only after `LevelPlay.Init()` has completed successfully.
#### `new LevelPlayBannerAd(string adUnitId, Config config)`
Create a banner ad object with custom configuration.
**Parameters:**
- `adUnitId`: The ad unit identifier
- `config`: Configuration object built using Config.Builder()
**Usage:**
```csharp
var configBuilder = new LevelPlayBannerAd.Config.Builder();
configBuilder.SetSize(LevelPlayAdSize.LARGE);
configBuilder.SetPosition(LevelPlayBannerPosition.TopCenter);
var config = configBuilder.Build();
LevelPlayBannerAd bannerAd = new LevelPlayBannerAd("your_ad_unit_id", config);
```
### Configuration Builder
#### `LevelPlayBannerAd.Config.Builder()`
Create a configuration builder for customizing banner behavior.
**Methods:**
**`SetSize(LevelPlayAdSize size)`**
Set the banner size.
**Usage:**
```csharp
configBuilder.SetSize(LevelPlayAdSize.BANNER); // 320x50
configBuilder.SetSize(LevelPlayAdSize.LARGE); // 320x90
configBuilder.SetSize(LevelPlayAdSize.MEDIUM_RECTANGLE); // 300x250
configBuilder.SetSize(LevelPlayAdSize.LEADERBOARD); // 728x90
configBuilder.SetSize(LevelPlayAdSize.CreateAdaptiveAdSize()); // Adaptive
configBuilder.SetSize(LevelPlayAdSize.CreateCustomBannerSize(300, 150)); // Custom size
```
**`SetPosition(LevelPlayBannerPosition position)`**
Set the banner position on screen.
**Usage:**
```csharp
configBuilder.SetPosition(LevelPlayBannerPosition.TopCenter);
configBuilder.SetPosition(LevelPlayBannerPosition.BottomCenter);
// Also: TopLeft, TopRight, BottomLeft, BottomRight
```
**`SetDisplayOnLoad(bool displayOnLoad)`**
Whether to automatically show banner when loaded.
**Usage:**
```csharp
configBuilder.SetDisplayOnLoad(true); // Auto-show (default)
configBuilder.SetDisplayOnLoad(false); // Manual show
```
**`SetRespectSafeArea(bool respectSafeArea)`**
Whether to respect device safe area (Android only).
**Usage:**
```csharp
configBuilder.SetRespectSafeArea(true); // Avoid notches/cutouts
```
**`SetPlacementName(string placementName)`**
Set the placement name for analytics tracking.
**Usage:**
```csharp
configBuilder.SetPlacementName("main_menu_banner");
```
**When to use:** When tracking multiple banner placements for analytics and reporting.
**Set minimum bid price:**
```csharp
configBuilder.SetBidFloor(1.0); // Minimum bid price in USD ($1.00 CPM)
```
**When to use:** Set a minimum bid price in USD for ad requests to ensure revenue floor.
**`Build()`**
Build the configuration object.
**Usage:**
```csharp
var config = configBuilder.Build();
```
### Core Methods
#### `LoadAd()`
Load a banner ad.
**Usage:**
```csharp
bannerAd.LoadAd();
```
**Note:** After loading succeeds, you can call `ShowAd()` to display it (unless `SetDisplayOnLoad(true)` was used).
#### `ShowAd()`
Show the banner ad.
**Usage:**
```csharp
bannerAd.ShowAd();
```
**Note:** Banner must be loaded first.
#### `HideAd()`
Hide the banner ad without destroying it.
**Usage:**
```csharp
bannerAd.HideAd();
```
**When to use:** Temporarily hide banner (e.g., during gameplay) while keeping it loaded for quick re-display.
#### `DestroyAd()`
Destroy the banner ad and free resources.
**Usage:**
```csharp
bannerAd.DestroyAd();
```
**When to use:** When leaving a scene where banner was shown, or when switching to a different banner size.
**Note on IsAdReady():** Unlike Rewarded and Interstitial ads, Banner ads do NOT have an `IsAdReady()` method. Banners are ready to show immediately after `OnAdLoaded` fires. Simply call `ShowAd()` in the `OnAdLoaded` callback or afterward.
#### `PauseAutoRefresh()`
Pause automatic banner refresh.
**Usage:**
```csharp
bannerAd.PauseAutoRefresh();
```
**When to use:** When banner is not visible but still loaded (e.g., during gameplay).
#### `ResumeAutoRefresh()`
Resume automatic banner refresh.
**Usage:**
```csharp
bannerAd.ResumeAutoRefresh();
```
**When to use:** When banner becomes visible again after pausing.
### Banner Sizes
Available banner sizes from `LevelPlayAdSize` (note: **UPPERCASE**):
| Size | Description | Typical Dimensions |
|------|-------------|-------------------|
| `LevelPlayAdSize.BANNER` | Standard banner | 320x50 |
| `LevelPlayAdSize.LARGE` | Large banner | 320x90 |
| `LevelPlayAdSize.MEDIUM_RECTANGLE` | Medium rectangle | 300x250 |
| `LevelPlayAdSize.LEADERBOARD` | Leaderboard banner | 728x90 |
| `LevelPlayAdSize.CreateAdaptiveAdSize()` | Adaptive banner | Adapts to screen width |
| `LevelPlayAdSize.CreateCustomBannerSize(int width, int height)` | Custom size | Custom dimensions |
**Usage:**
```csharp
// Standard banner (most common)
LevelPlayAdSize.BANNER
// Large banner
LevelPlayAdSize.LARGE
// Medium rectangle
LevelPlayAdSize.MEDIUM_RECTANGLE
// Leaderboard
LevelPlayAdSize.LEADERBOARD
// Adaptive banner (method call)
LevelPlayAdSize.CreateAdaptiveAdSize()
// Custom size
LevelPlayAdSize.CreateCustomBannerSize(300, 150)
```
**Recommendation:** Use `BANNER` (320x50) for most cases. Use `CreateAdaptiveAdSize()` for adaptive sizing across devices.
**LevelPlayAdSize Properties (SDK 8.8.0+):**
After setting a banner size, you can query the actual dimensions:
```csharp
LevelPlayAdSize adSize = LevelPlayAdSize.BANNER;
int width = adSize.Width; // e.g., 320
int height = adSize.Height; // e.g., 50
```
**Properties:**
- `Width` (int): Banner width in pixels
- `Height` (int): Banner height in pixels
**When to use:** When calculating UI layout offsets or positioning other UI elements around the banner.
### Banner Positions
Available positions from `LevelPlayBannerPosition`:
| Position | Description |
|----------|-------------|
| `TopLeft` | Top-left corner |
| `TopCenter` | Top-center |
| `TopRight` | Top-right corner |
| `CenterLeft` | Center-left |
| `Center` | Center |
| `CenterRight` | Center-right |
| `BottomLeft` | Bottom-left corner |
| `BottomCenter` | Bottom-center (most common) |
| `BottomRight` | Bottom-right corner |
**Usage:**
```csharp
LevelPlayBannerPosition.BottomCenter
LevelPlayBannerPosition.TopCenter
LevelPlayBannerPosition.Center
// etc.
```
**Additional:** Custom positioning can be achieved using a Vector2 constructor.
**Recommendation:** `BottomCenter` is less intrusive and more commonly used.
### Events
All events are properties of the `LevelPlayBannerAd` object.
**Threading:** All ad callbacks run on the Unity main thread, so you can safely call Unity APIs (update UI, access GameObjects, etc.) directly in these callbacks. This is different from `LevelPlay.OnImpressionDataReady` which runs on a background thread.
#### `OnAdLoaded`
Fired when a banner ad is loaded.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
bannerAd.OnAdLoaded += (adInfo) =>
{
Debug.Log("Banner loaded");
bannerAd.ShowAd();
};
```
#### `OnAdLoadFailed`
Fired when a banner ad fails to load.
**Signature:** `event Action<LevelPlayAdError>`
**Usage:**
```csharp
bannerAd.OnAdLoadFailed += (error) =>
{
Debug.LogWarning($"Banner load failed: {error.ErrorMessage}");
};
```
#### `OnAdDisplayed`
Fired when banner ad is displayed.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
bannerAd.OnAdDisplayed += (adInfo) =>
{
Debug.Log("Banner displayed");
};
```
#### `OnAdDisplayFailed`
Fired when banner ad fails to display.
**Signature:** `event Action<LevelPlayAdInfo, LevelPlayAdError>`
**Usage:**
```csharp
bannerAd.OnAdDisplayFailed += (adInfo, error) =>
{
Debug.LogError($"Banner display failed: {error.ErrorMessage}");
};
```
#### `OnAdClicked`
Fired when user clicks on the banner.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
bannerAd.OnAdClicked += (adInfo) =>
{
Debug.Log("Banner clicked");
};
```
#### `OnAdExpanded`
Fired when banner ad expands to full screen (e.g., after click).
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
bannerAd.OnAdExpanded += (adInfo) =>
{
Debug.Log("Banner expanded");
// Pause game if needed
};
```
#### `OnAdCollapsed`
Fired when expanded banner collapses back.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
bannerAd.OnAdCollapsed += (adInfo) =>
{
Debug.Log("Banner collapsed");
// Resume game if paused
};
```
#### `OnAdLeftApplication`
Fired when banner click causes user to leave the application.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
bannerAd.OnAdLeftApplication += (adInfo) =>
{
Debug.Log("Banner left application");
};
```
## Data Types
### LevelPlayAdInfo
Contains information about the ad.
**Properties:**
- `AdId` (string): Unique identifier for this specific ad instance
- `AdUnitId` (string): The ad unit identifier
- `AdUnitName` (string): The ad unit name
- `AdSize` (LevelPlayAdSize): The ad size (may be null; banner-relevant)
- `AdFormat` (string): The ad format (e.g., "BANNER")
- `PlacementName` (string): Placement name where ad was shown
- `AuctionId` (string): Unique auction identifier
- `CreativeId` (string): Creative identifier
- `Country` (string): User's country code (ISO 3166-1)
- `Ab` (string): A/B test segment identifier
- `SegmentName` (string): User segment name
- `AdNetwork` (string): Ad network that served the ad
- `InstanceName` (string): Ad network instance name
- `InstanceId` (string): Ad network instance identifier
- `Revenue` (double?): Estimated revenue in USD (nullable - check for null)
- `Precision` (string): Revenue precision level
- `EncryptedCPM` (string): Encrypted CPM value
### LevelPlayAdError
Contains error information when ad operations fail.
**Properties:**
- `ErrorCode` (int): Numeric error code
- `ErrorMessage` (string): Human-readable error description
- `AdUnitId` (string): The ad unit identifier where the error occurred
- `AdId` (string): Unique identifier for the specific ad instance
## Best Practices
### Loading
- Load banners when entering scenes where they'll be displayed
- Destroy banners when leaving those scenes to free memory
- Only one banner per ad unit can be active at a time
- If you need to change size/position, destroy and recreate
### Placement
- **Bottom position** is generally less intrusive than top
- **Avoid during gameplay** - show in menus, waiting screens
- **Consider UI overlap** - ensure banner doesn't cover important UI
- **Test on various screen sizes** - especially important for mobile
### Visibility Management
- Use `HideAd()` / `ShowAd()` for temporary visibility changes
- Use `DestroyAd()` when permanently done with banner (frees memory)
- Hide banners during gameplay for UX-focused strategy
- Show banners persistently for revenue-focused strategy
### Auto-Refresh Management
- Use `PauseAutoRefresh()` when banner is hidden
- Use `ResumeAutoRefresh()` when banner is shown again
**Example:**
```csharp
void OnGameplayStart()
{
bannerAd.HideAd();
bannerAd.PauseAutoRefresh();
}
void OnGameplayEnd()
{
bannerAd.ShowAd();
bannerAd.ResumeAutoRefresh();
}
```
### Memory Management
- Always call `DestroyAd()` in `OnDestroy()` or when leaving scene
- Don't keep banners loaded in scenes where they're not displayed
- Unsubscribe from events properly
```csharp
void OnDestroy()
{
if (bannerAd != null)
{
bannerAd.DestroyAd();
}
}
```
### UX Considerations
- Banners can be distracting during gameplay
- Best practice: Show in menus, hide during gameplay
- For revenue-focused: Show persistently but position carefully
- Test with actual users to gauge impact on experience
## Common Issues
### Issue: Repeated LoadAd() calls
**Cause:** Calling `LoadAd()` more than once on the same banner object — in `Update()`, on scene re-entry, or to manually refresh the creative.
**Common mistake:**
```csharp
void Update()
{
// WRONG - never call LoadAd() every frame.
// Banners load once and then auto-refresh on their own.
bannerAd.LoadAd();
}
```
**Note:** Banner ads have no `IsAdReady()` method (unlike Rewarded and Interstitial ads) — a banner is ready to show once `OnAdLoaded` fires, so there is nothing to poll for here.
**Why to avoid:** A banner object takes exactly one `LoadAd()` call for its lifetime, whether or not auto-refresh is enabled. New creatives come from auto-refresh (based on your platform settings), and visibility is controlled with `ShowAd()`/`HideAd()` — never by reloading. Banner ads don't throw errors for repeated loads like interstitial/rewarded ads do, but extra `LoadAd()` calls waste resources and can cause unexpected behavior.
**Solution:** Call `LoadAd()` once after creating the banner:
```csharp
void Start()
{
bannerAd = new LevelPlayBannerAd(adUnitId);
bannerAd.OnAdLoaded += OnAdLoaded;
bannerAd.OnAdLoadFailed += OnAdLoadFailed;
bannerAd.LoadAd(); // The only LoadAd() for this object's lifetime
}
void OnAdLoadFailed(LevelPlayAdError error)
{
// Optionally retry after delay
Invoke(nameof(RetryLoad), 5f);
}
void RetryLoad()
{
bannerAd.LoadAd();
}
```
**Note:** The failure retry above is the only case where calling `LoadAd()` again on the same object is correct. After `DestroyAd()`, create a new banner object and give it its own single `LoadAd()`.
### Issue: Banner overlaps UI elements
**Solutions:**
- Calculate banner height and offset UI elements
- Use `Canvas.offsetMin` or `RectTransform.anchoredPosition` to create spacing
- Test on different screen sizes
- Use `SetRespectSafeArea(true)` to avoid notches
### Issue: Banner not showing
**Possible causes:**
- SDK not initialized before creating banner
- Banner not loaded before calling `ShowAd()`
- Banner is hidden with `HideAd()`
- `SetDisplayOnLoad(false)` used without manual `ShowAd()`
**Solutions:**
- Create banner only after `OnInitSuccess` callback
- Ensure `LoadAd()` is called and `OnAdLoaded` fires before `ShowAd()`
- Check banner visibility state
### Issue: Banner shows in wrong scenes
**Solutions:**
- Implement scene-based visibility management
- Destroy banner when leaving scenes where it shouldn't appear
- Use `SceneManager.sceneLoaded` event for automatic management
### Issue: Wrong banner size
**Possible causes:**
- Using lowercase enum values (e.g., `.Banner` instead of `.BANNER`)
- Not using Config Builder to set size
**Solutions:**
- Always use UPPERCASE: `LevelPlayAdSize.BANNER`, not `.Banner`
- Use Config Builder to explicitly set size:
```csharp
var config = new LevelPlayBannerAd.Config.Builder()
.SetSize(LevelPlayAdSize.LARGE)
.Build();
```
## Testing Checklist
- [ ] Banner loads and displays correctly
- [ ] Banner appears in correct position (top/bottom)
- [ ] Banner size is appropriate for screen
- [ ] Banner doesn't overlap important UI
- [ ] Banner hides/shows correctly
- [ ] Banner destroys properly when leaving scene
- [ ] Banner works on different screen sizes/orientations
- [ ] Retry logic works if banner fails to load
- [ ] Auto-refresh pauses/resumes correctly
- [ ] Safe area respected on devices with notches (Android)
references/best-practices.md›
# LevelPlay Ad Unit Implementation Best Practices
## Contents
- [Overview](#overview)
- [Strategic Framework: Revenue vs UX Optimization](#strategic-framework-revenue-vs-ux-optimization)
- [Ad Format Strategy by Goal](#ad-format-strategy-by-goal)
- [Placement Strategy](#placement-strategy)
- [Implementation Patterns](#implementation-patterns)
- [Frequency Management](#frequency-management)
- [Error Handling and Graceful Degradation](#error-handling-and-graceful-degradation)
- [A/B Testing Considerations](#ab-testing-considerations)
- [Common Mistakes to Avoid](#common-mistakes-to-avoid)
- [Platform-Specific Considerations](#platform-specific-considerations)
- [Success Metrics by Strategy](#success-metrics-by-strategy)
- [Final Recommendations](#final-recommendations)
- [Code Generation Guidelines (Step 9)](#code-generation-guidelines-step-9)
## Overview
This reference compiles best practices from the LevelPlay enablement playbook, covering strategy selection, placement optimization, and common implementation patterns that drive both revenue and user satisfaction.
## Strategic Framework: Revenue vs UX Optimization
### Understanding the Trade-off
Every ad implementation involves a fundamental trade-off between revenue generation and user experience. Your optimization goal should guide every decision.
| Optimization Goal | Philosophy | Typical Outcome |
|-------------------|------------|-----------------|
| **Revenue-Focused** | Maximize impression opportunities | Higher revenue, potential UX impact |
| **UX-Focused** | Protect user experience at all costs | Better retention, lower short-term revenue |
| **Balanced** | Optimize for sustainable long-term value | Moderate revenue with good UX |
### Choosing Your Optimization Goal
**Ask yourself:**
1. What stage is your app in? (Early = UX focus, Mature = revenue optimization)
2. What's your business model? (Ads-only = revenue, Hybrid = balanced)
3. What's your competitive landscape? (High competition = UX focus)
4. What are your user expectations? (Casual games = more ads tolerated)
## Ad Format Strategy by Goal
### Revenue-Focused Implementation
**Goal**: Maximize ad revenue and impression opportunities
**Rewarded Ads** (Primary):
- Implement 3-5 placements across high-value moments
- Examples: Hints, extra lives, power-ups, bonus currency, skip levels
- Always keep a rewarded ad loaded and ready
- Reload immediately after showing
**Interstitial Ads** (Secondary):
- Show at every natural transition point
- Frequency cap: Every 3-5 minutes of active gameplay
- Placements: Level complete, game over, returning to menu
- Load proactively before transitions
**Banner Ads** (Tertiary):
- Show persistently during gameplay
- Position: Bottom (less intrusive than top)
- Keep loaded throughout session for continuous impressions
- Accept some visual clutter for revenue
**Expected Outcomes:** *(Industry benchmarks, not LevelPlay-specific)*
- ✅ 30-50% higher ad revenue
- ⚠️ 5-15% increase in early abandonment
- ⚠️ Lower user satisfaction scores
- ✅ Strong monetization from engaged users
### UX-Focused Implementation
**Goal**: Maintain excellent user experience while monetizing thoughtfully
**Rewarded Ads** (Only or Primary):
- Single placement or 2 maximum
- User-initiated only (player chooses to watch)
- High-value rewards that feel generous
- Never force ads
**Interstitial Ads** (Optional, Rare):
- Only at major session boundaries (returning to menu, closing app)
- Frequency cap: 10+ minutes or once per session
- Skip during active gameplay entirely
**Banner Ads** (Avoid or Minimize):
- Show only in menus, never during gameplay
- If used, small sizes (standard 320x50, not rectangles)
- Hide during any active user engagement
**Expected Outcomes:** *(Industry benchmarks, not LevelPlay-specific)*
- ⚠️ 40-60% lower ad revenue vs revenue-focused
- ✅ Better user retention and satisfaction
- ✅ Higher organic growth and word-of-mouth
- ✅ Premium brand perception
### Balanced Implementation (Recommended for Most Apps)
**Goal**: Sustainable monetization with good user experience
**Rewarded Ads** (Primary):
- 2-3 well-chosen placements
- Focus on moments of high user engagement
- Make rewards feel valuable but not exploitative
- Examples: Extra attempts, time skips, premium currency
**Interstitial Ads** (Moderate):
- Natural transition points only (level complete, game over)
- Frequency cap: Every 5-7 minutes
- Never interrupt active gameplay
- Respect user flow
**Banner Ads** (Selective):
- Show in menus and low-attention moments
- Hide during intense or immersive gameplay
- Position at bottom with proper UI offset
- Rotation: Show/hide based on context
**Expected Outcomes:** *(Industry benchmarks, not LevelPlay-specific)*
- ✅ Strong ad revenue (70-85% of revenue-focused approach)
- ✅ Good user retention
- ✅ Sustainable long-term monetization
- ✅ Positive user sentiment
## Placement Strategy
### Identifying Strong Placement Moments
**High-value moments** (Best for rewarded ads):
- Post-victory celebrations
- Level progression milestones
- Unlock opportunities
- User feels positive emotion
**Natural transitions** (Good for interstitials):
- Level complete
- Game over
- Returning to main menu
- Session boundaries
**Low-attention periods** (Acceptable for banners):
- Waiting screens
- Loading periods
- Menu browsing
- Non-interactive moments
### Placement Anti-Patterns (Avoid These)
❌ **Mid-gameplay interruptions**: Never show interstitials during active play
❌ **Bait-and-switch**: Don't offer rewards then show unrewarded interstitials
❌ **Excessive frequency**: Showing ads every 1-2 minutes frustrates users
❌ **Punitive ads**: Using ads as punishment for failure feels manipulative
❌ **Blocking progression**: Making ads mandatory to continue playing
❌ **Poor timing**: Showing ads right before key moments or cliffhangers
## Implementation Patterns
> **Note:** These patterns build on the manager classes defined in `rewarded-api.md` and `interstitial-api.md` (`RewardedAdManager`, `InterstitialAdManager`). They use only those managers' public members — `LoadAd()`, `ShowAd()`, `IsAdReady()`, and (for rewarded) the `OnRewardGranted` hook. Generate the manager classes from those references first so these examples compile.
### Pattern 1: Rewarded Hints System
**Use case**: Puzzle or strategy games where hints add value
```csharp
public class HintSystem : MonoBehaviour
{
[SerializeField] private RewardedAdManager adManager;
[SerializeField] private int hintsAvailable = 3;
public void RequestHint()
{
if (hintsAvailable > 0)
{
// User has free hints remaining
UseHint();
hintsAvailable--;
}
else
{
// Offer ad-based hint
if (adManager.IsAdReady())
{
ShowHintAdOffer();
}
else
{
ShowHintNotAvailableMessage();
}
}
}
private void ShowHintAdOffer()
{
// Show dialog: "Watch an ad to get a hint?"
// If user accepts, subscribe to the reward hook and show the ad.
adManager.OnRewardGranted = OnHintAdRewardEarned;
adManager.ShowAd();
}
private void OnHintAdRewardEarned()
{
// RewardedAdManager invokes this only after the user earns the reward
UseHint();
}
private void UseHint()
{
// Show hint to player
Debug.Log("Showing hint");
}
private void ShowHintNotAvailableMessage()
{
// Inform user that hints aren't available right now
Debug.Log("Hints not available at the moment");
}
}
```
**Why this works:**
- User has free hints first (generous)
- Ad is optional, not forced
- Clear value exchange (ad for hint)
- Graceful handling when ads unavailable
### Pattern 2: Level Transition Interstitial (Frequency Capped)
**Use case**: Showing interstitials between levels without annoying users
```csharp
public class LevelTransitionAds : MonoBehaviour
{
[SerializeField] private InterstitialAdManager adManager;
private int levelsCompletedSinceAd = 0;
private int levelsRequiredBetweenAds = 3; // Show ad every 3 levels
public void OnLevelComplete()
{
levelsCompletedSinceAd++;
if (levelsCompletedSinceAd >= levelsRequiredBetweenAds)
{
ShowInterstitialOpportunistically();
levelsCompletedSinceAd = 0;
}
ProceedToNextLevel();
}
private void ShowInterstitialOpportunistically()
{
// Try to show, but don't wait if not ready
if (adManager.IsAdReady())
{
adManager.ShowAd();
}
}
private void ProceedToNextLevel()
{
// Load next level regardless of ad status
// Never block user flow waiting for ads
}
}
```
**Why this works:**
- Frequency capping prevents ad fatigue
- Opportunistic showing (doesn't block if not ready)
- User flow never interrupted
- Predictable pattern users can adapt to
### Pattern 3: Context-Aware Banner Management
**Use case**: Showing banners in menus but hiding during gameplay
```csharp
public class ContextAwareBanners : MonoBehaviour
{
[SerializeField] private BannerAdManager bannerManager;
public enum AppContext
{
MainMenu,
Playing,
Paused,
GameOver
}
private AppContext currentContext;
public void SetContext(AppContext newContext)
{
currentContext = newContext;
UpdateBannerVisibility();
}
private void UpdateBannerVisibility()
{
switch (currentContext)
{
case AppContext.MainMenu:
case AppContext.GameOver:
// Show banners in menu contexts
bannerManager.ShowBanner();
break;
case AppContext.Playing:
// Hide during active gameplay
bannerManager.HideBanner();
break;
case AppContext.Paused:
// Optional: Show or hide based on your preference
// For UX-focused: Hide
// For revenue-focused: Show
bannerManager.ShowBanner();
break;
}
}
}
```
**Why this works:**
- Banners shown only in appropriate contexts
- Gameplay experience uninterrupted
- Easy to adjust strategy by changing visibility logic
- Clean separation of concerns
## Frequency Management
### Time-Based Frequency Capping
Implement minimum time intervals between ads:
```csharp
public class FrequencyManager : MonoBehaviour
{
private float minSecondsBetweenInterstitials = 300f; // 5 minutes
private float lastInterstitialTime = -300f; // Allow first ad immediately
public bool CanShowInterstitial()
{
float timeSinceLast = Time.realtimeSinceStartup - lastInterstitialTime;
return timeSinceLast >= minSecondsBetweenInterstitials;
}
public void RecordInterstitialShown()
{
lastInterstitialTime = Time.realtimeSinceStartup;
}
}
```
**Recommended intervals:**
- Revenue-focused: 3-5 minutes
- Balanced: 5-7 minutes
- UX-focused: 10+ minutes
### Count-Based Frequency Capping
Implement minimum actions between ads:
```csharp
private int actionsRequiredBetweenAds = 3;
private int actionsSinceLastAd = 0;
public void OnUserAction()
{
actionsSinceLastAd++;
if (actionsSinceLastAd >= actionsRequiredBetweenAds)
{
// Eligible to show ad
TryShowAd();
actionsSinceLastAd = 0;
}
}
```
**Examples of "actions":**
- Levels completed
- Games played
- Sessions started
- Feature uses
## Error Handling and Graceful Degradation
### Always Have a Fallback
Never let ad failures block user flow:
```csharp
public void OnAdLoadFailed()
{
// Log for debugging
Debug.LogWarning("Ad failed to load");
// Continue with user flow
ProceedWithoutAd();
// Schedule retry
Invoke(nameof(RetryLoadAd), 30f);
}
private void ProceedWithoutAd()
{
// Your app continues normally
// Never block users because ads aren't available
}
```
### Reward Users Even When Ads Fail
For rewarded ads, consider granting rewards even if ad fails:
```csharp
public void OnRewardedAdShowFailed()
{
// User intended to watch ad, but it failed
// Consider granting reward anyway (generous approach)
if (shouldBeGenerousOnFailure)
{
GrantReward();
ShowMessage("Reward granted! (Ad unavailable)");
}
else
{
ShowMessage("Ad unavailable, please try again later");
}
}
```
**When to be generous:**
- UX-focused strategy
- High-value users
- Failure is on your end (not user's fault)
## A/B Testing Considerations
### What to Test
**High-impact variables:**
1. **Frequency caps**: 3min vs 5min vs 7min between interstitials
2. **Placement timing**: Immediate vs delayed interstitials
3. **Reward generosity**: 1x vs 2x multiplier for rewarded ads
4. **Banner visibility**: Always vs context-aware vs never during gameplay
5. **Interstitial presence**: With vs without interstitials
**How to test:**
- Split users into cohorts
- Track both revenue AND retention
- Run tests for 7-14 days minimum
- Focus on long-term value (LTV), not just day-1 revenue
### Metrics to Track
**Revenue metrics:**
- ARPDAU (Average Revenue Per Daily Active User)
- Impression per DAU
- eCPM (effective Cost Per Mille)
**UX metrics:**
- D1, D7, D30 retention
- Session length
- Session frequency
- Organic virality (shares, referrals)
**Balance metrics:**
- LTV (Lifetime Value)
- ARPU / Retention ratio
- User satisfaction scores
## Common Mistakes to Avoid
1. **Over-monetizing early**: Don't show tons of ads before users are engaged
2. **Ignoring retention**: High revenue means nothing if users quit
3. **Blocking without ads**: Never require watching ads to progress
4. **Poor frequency capping**: Ads every minute frustrate users
5. **Forcing rewarded ads**: Rewarded ads should always be optional
6. **Ignoring load failures**: Always handle ad failures gracefully
7. **One-size-fits-all**: Different user segments may need different strategies
8. **Neglecting testing**: Always A/B test major monetization changes
## Platform-Specific Considerations
### iOS
- Lower opt-in rates post-ATT (iOS 14.5+)
- Users who deny tracking see lower-value ads
- More privacy-conscious user base
- Consider more UX-focused approach
### Android
- Higher ad fill rates
- More permissive user expectations
- Diverse hardware requires testing
- Can push frequency slightly higher
## Success Metrics by Strategy
*(Example benchmarks from industry typical ranges, not LevelPlay-specific targets)*
### Revenue-Focused Success
- ARPDAU > $0.15
- Impression/DAU > 8
- D7 retention > 20%
### UX-Focused Success
- D7 retention > 35%
- Session length > 15 minutes
- Low uninstall rate (<5% weekly)
- ARPDAU > $0.05
### Balanced Success
- ARPDAU > $0.10
- D7 retention > 28%
- Impression/DAU > 5
- High user satisfaction
## Final Recommendations
1. **Start UX-focused**: Better to under-monetize early than drive users away
2. **Increase gradually**: Add monetization as users become engaged
3. **Always test**: Data beats assumptions
4. **Watch retention closely**: It's easier to add ads than win back users
5. **Be transparent**: Users appreciate honesty about ad-supported models
6. **Reward patience**: Give users free options before pushing ads
7. **Optimize continuously**: Ad strategy should evolve with your product
## Code Generation Guidelines (Step 9)
Read this before generating ad implementation code in Step 9. It contains the general ad lifecycle,
per-organization-approach guidance, the always-include requirements, and bid-floor wiring.
### General Implementation Pattern
All ad formats follow a similar lifecycle:
1. **Load**: Request an ad from LevelPlay
2. **Listen**: Register callbacks for ad events (loaded, failed, shown, clicked, closed)
3. **Check readiness**: Verify ad is ready before showing
4. **Show**: Display the ad to the user
5. **Handle callbacks**: Respond to user interactions and ad lifecycle events
### Adapt Output to the Chosen Organization Approach
**If the user mentions they have existing code**, ask to see it before providing implementation
guidance. This allows you to provide targeted fixes rather than generating new code from scratch.
**Option 1: Separate manager scripts**
- Create complete, production-ready `.cs` files for each ad format
- Name them clearly: `RewardedAdManager.cs`, `InterstitialAdManager.cs`, `BannerAdManager.cs`
- Include full class structure with proper namespaces
- Each manager handles one ad format completely
**Option 2: Unified AdManager**
- Create a single `AdManager.cs` file
- Include methods and callbacks for all requested ad formats in one class
- Use clear method naming to distinguish between formats (e.g., `LoadRewardedAd()`, `LoadInterstitial()`)
- Keep code organized with regions or comments separating each ad format
**Option 3: Code snippets only**
- Provide focused code blocks without full class wrappers
- Clearly label each snippet (e.g., "Rewarded Ad Initialization", "Interstitial Event Callbacks")
- Explain where/how to integrate each snippet
- Note any dependencies between snippets
### Regardless of Chosen Approach, Always Include
- All manager classes must inherit from `MonoBehaviour` — required for Unity lifecycle methods (`Start()`, `OnDestroy()`) and to attach the script to a GameObject
- After generating any manager script, instruct the user to attach it to a persistent GameObject in their scene (the same one as the initializer, with `DontDestroyOnLoad`)
- For banner and interstitial managers, call `DestroyAd()` in `OnDestroy()` so ads are destroyed and memory is freed when the manager is destroyed
- Rewarded and interstitial show paths check `IsAdReady()` before `ShowAd()`. If the game uses dashboard placements, also check the static `IsPlacementCapped(placementName)` — capping is configured per placement in the LevelPlay dashboard, and showing a capped placement fails (error codes 524/526)
- Proper event subscription and unsubscription (to avoid memory leaks)
- Null checks and defensive programming
- Debug logs for troubleshooting
- Clear variable names that match LevelPlay conventions
- Error handling and graceful degradation
### Bid Floor Handling (per-format)
- If the user provided a bid floor value for a format, wrap construction in `Config.Builder().SetBidFloor(value).Build()` and pass the config to the constructor.
- If the user said 'skip' for that format, use the basic constructor (`new LevelPlayRewardedAd(adUnitId)`).
- Apply per-format: a publisher may set a floor on rewarded but skip banner.
Example (with bid floor):
```csharp
var config = new LevelPlayRewardedAd.Config.Builder()
.SetBidFloor(0.80)
.Build();
rewardedAd = new LevelPlayRewardedAd(adUnitId, config);
```
Example (skipped):
```csharp
rewardedAd = new LevelPlayRewardedAd(adUnitId);
```
## Additional Resources
For detailed API implementation, see:
- `rewarded-api.md`: Rewarded ad implementation
- `interstitial-api.md`: Interstitial ad implementation
- `banner-api.md`: Banner ad implementation
- `initialization-api.md`: SDK setup and configuration
references/dependency-resolution.md›
# LevelPlay Native Dependency Resolution
Detailed guidance for Step 4 of SKILL.md. LevelPlay requires native Android/iOS libraries that Unity's
package manager alone doesn't handle; a dependency manager bridges this gap. Read this when resolving
native dependencies, when the user does not yet have a dependency manager, or when diagnosing gradle
(Android) / CocoaPods (iOS) build failures.
## Contents
- [Check for an Existing Dependency Manager](#check-for-an-existing-dependency-manager)
- [If They Have a Dependency Manager](#if-they-have-a-dependency-manager)
- [If They Don't Have a Dependency Manager](#if-they-dont-have-a-dependency-manager)
- [Verification](#verification)
- [Android Custom Main Gradle Template (Older Versions)](#android-custom-main-gradle-template-older-versions)
- [Android API 33+ Requirement](#android-api-33-requirement)
LevelPlay requires native Android/iOS libraries that Unity's package manager alone doesn't handle. A
dependency manager bridges this gap. If this is a new project without a dependency manager already set
up, the steps below cover installing one.
## Check for an Existing Dependency Manager
Ask: "Do you have a dependency manager like Mobile Dependency Resolver (MDR), Unity External Dependency
Manager (UEDM), or External Dependency Manager for Unity (EDM4U) installed? Check your Assets folder for
these tools."
**If unsure**: Check Assets folder in Unity for folders named 'Mobile Dependency Resolver', 'External
Dependency Manager', or 'EDM4U'. If you see any folder with these names, answer 'Yes'. If not, answer 'No'.
## If They Have a Dependency Manager
**For Android:**
- With newer versions of Mobile Dependency Resolver (shipped with the Ads Mediation package): Dependencies auto-resolve on build (no manual action needed). If unsure, try building first — if it fails, manually resolve via the Android Resolver menu.
- With older MDR or other managers: Go to Assets > [Your Dependency Manager] > Android Resolver > Resolve
- Example paths:
- MDR: `Assets > Mobile Dependency Resolver > Android Resolver > Resolve`
- EDM4U: `Assets > External Dependency Manager > Android Resolver > Resolve`
- Menu paths may vary depending on dependency manager version. Look for 'Android Resolver' under your dependency manager's menu.
**For iOS:**
- All dependency managers require manual CocoaPods installation:
- MDR: `Assets > Mobile Dependency Resolver > iOS Resolver > Install Cocoapods`
- EDM4U: `Assets > External Dependency Manager > iOS Resolver > Install Cocoapods`
- UEDM: Similar path under Unity External Dependency Manager
Ask: "Have you run the dependency resolution for your target platform(s)?"
**If targeting both Android and iOS**, complete dependency resolution for both platforms before proceeding.
## If They Don't Have a Dependency Manager
If the user didn't see the Mobile Dependency Resolver prompt during installation (see Step 3), restart
Unity Editor — the prompt may appear after restart. If it still doesn't appear, they may already have a
dependency manager installed.
When prompted:
1. Click **Import** on the prompt to install Mobile Dependency Resolver
2. After installation:
- **Android**: Dependencies will auto-resolve on build (newer MDR versions)
- **iOS**: Go to Assets > Mobile Dependency Resolver > iOS Resolver > Install Cocoapods
**Alternative**: Install another dependency manager like EDM4U if preferred (search for installation instructions in their documentation).
**Note**: Unity is transitioning to Unity External Dependency Manager (UEDM). If available in your Unity version, prefer UEDM over MDR.
## Verification
After resolution, verify:
- **Android**: Gradle dependencies in `Assets/Plugins/Android/`
- **iOS**: Podfile references or CocoaPods installation confirmation in console
**If dependency resolution fails**: Check the Unity console for specific error messages and share them for troubleshooting — or see `references/troubleshooting.md` for gradle and CocoaPods error guidance.
## Android Custom Main Gradle Template (Older Versions)
For older LevelPlay Unity package versions, manually enable the Custom Main Gradle Template:
1. Go to Edit > Project Settings > Player
2. Select Android tab
3. Expand Publishing Settings
4. Under Build, check Custom Main Gradle Template
In newer LevelPlay Unity package versions (with newer Mobile Dependency Resolver), this is enabled
automatically by default.
## Android API 33+ Requirement
If targeting Android API level 33 or higher, declare the AD_ID permission in AndroidManifest.xml:
```xml
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
```
This permission is required for advertising ID access on Android 13+.
**If you skip this step and target Android API 33+:** advertising ID access will fail on Android 13+ devices.
references/ilrd-api.md›
# Impression Level Revenue (ILR) Integration
## Contents
- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Key Characteristics](#key-characteristics)
- [Implementation](#implementation)
- [API Reference](#api-reference)
- [Thread Safety](#thread-safety)
- [Integration with Third-Party Analytics](#integration-with-third-party-analytics)
- [Best Practices](#best-practices)
- [Common Issues](#common-issues)
- [Testing Checklist](#testing-checklist)
## Overview
Use the Impression Level Revenue (ILR) solution to track ad revenue at both device and impression levels, integrating with third-party analytics tools for deeper insights.
## Prerequisites
- LevelPlay SDK 7.0.3+ correctly integrated
- Refer to Unity Package integration guide
For more information about the Impression Level Revenue (ILR) SDK feature and pre-requisites, refer to the Impression-level revenue server-side API documentation.
## Key Characteristics
- **Real-time data**: Fires postbacks to inform you about displayed ads
- **Background thread**: Callback runs on a background thread, not the Unity main thread
- **Optional listener**: Provides information about all ad units
- **All ad formats**: Works with Rewarded, Interstitial, and Banner ads
## Implementation
ILRD is delivered differently depending on your SDK version. Check your version in **Ads Mediation > Network Manager**.
| SDK version | ILRD delivery | Where you subscribe |
|-------------|---------------|---------------------|
| **9.5.0+ (current)** | Per ad instance — `OnAdImpressionDataReady` on each ad object (`LevelPlayRewardedAd`, `LevelPlayInterstitialAd`, `LevelPlayBannerAd`) | When you create each ad |
| **9.4.x and earlier** | Single global event — `LevelPlay.OnImpressionDataReady` | Once, before `LevelPlay.Init()` |
On SDK 9.5.0+, the global `LevelPlay.OnImpressionDataReady` still exists but is **deprecated and generates a compiler warning** — use the per-instance `OnAdImpressionDataReady` event instead.
### SDK 9.5.0+ (current): Per-Instance Setup
Subscribe to `OnAdImpressionDataReady` on each ad object right after you create it — typically inside the ad manager classes from `rewarded-api.md` / `interstitial-api.md` / `banner-api.md`. There is no "before Init" ordering concern with the per-instance event; you subscribe when the ad is created.
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class RewardedAdManager : MonoBehaviour
{
private LevelPlayRewardedAd rewardedAd;
private string adUnitId = "YOUR_REWARDED_AD_UNIT_ID";
void Start()
{
rewardedAd = new LevelPlayRewardedAd(adUnitId);
// ILRD (SDK 9.5.0+): subscribe per ad instance.
// Fires on a BACKGROUND thread — do not call Unity APIs directly here.
rewardedAd.OnAdImpressionDataReady += OnImpressionDataReady;
// ... register other event listeners and call LoadAd() as usual
}
void OnDestroy()
{
if (rewardedAd != null)
{
rewardedAd.OnAdImpressionDataReady -= OnImpressionDataReady;
// ... unsubscribe other events
}
}
private void OnImpressionDataReady(LevelPlayImpressionData impressionData)
{
// Runs on a background thread — see "Integration with Third-Party Analytics".
if (impressionData == null) return;
Debug.Log($"ILRD - {impressionData.AdNetwork} / {impressionData.AdFormat} / ${impressionData.Revenue}");
}
}
```
Repeat the same `OnAdImpressionDataReady` subscription on your interstitial and banner ad objects. Every ad format reports its own impressions through its own instance event.
### SDK 9.4.x and earlier (legacy): Global Event Setup
On older SDKs, use the single global `LevelPlay.OnImpressionDataReady` event, registered **before** `LevelPlay.Init()`:
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class ImpressionRevenueManager : MonoBehaviour
{
void Start()
{
// Register impression data callback BEFORE SDK initialization
// This event is triggered on a background thread, not the Unity main thread.
LevelPlay.OnImpressionDataReady += ImpressionDataReadyEvent;
// Register initialization callbacks
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
// Initialize SDK
LevelPlay.Init("YOUR_APP_KEY");
}
void OnDestroy()
{
// Unregister callbacks
LevelPlay.OnImpressionDataReady -= ImpressionDataReadyEvent;
LevelPlay.OnInitSuccess -= OnInitSuccess;
LevelPlay.OnInitFailed -= OnInitFailed;
}
private void ImpressionDataReadyEvent(LevelPlayImpressionData impressionData)
{
// IMPORTANT: This runs on a background thread, not the main thread
// Do not call Unity APIs directly from here
Debug.Log($"ILR - Ad Network: {impressionData.AdNetwork}");
Debug.Log($"ILR - Revenue: ${impressionData.Revenue}");
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay initialized");
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"Init failed: {error.ErrorMessage}");
}
}
```
**Key points (global event — SDK 9.4.x and earlier only):**
- Declare the listener **BEFORE** initializing the LevelPlay SDK to avoid any loss of information
- Callback runs on a **background thread**
- Don't call Unity APIs directly in the callback
- On SDK 9.5.0+ this global event is deprecated — use the per-instance `OnAdImpressionDataReady` shown above instead
### Accessing Impression Data
You can refer to each field separately or get all information by using the AllData property:
```csharp
private void ImpressionDataReadyEvent(LevelPlayImpressionData impressionData)
{
string allData = impressionData.AllData;
string adNetwork = impressionData.AdNetwork;
double? revenue = impressionData.Revenue;
}
```
**Important:** The returned data might include null values. To avoid potential crashes, ensure that you add protections before assigning the data.
## API Reference
### Events
#### `adObject.OnAdImpressionDataReady` (SDK 9.5.0+ — current)
Per-instance event on each ad object (`LevelPlayRewardedAd`, `LevelPlayInterstitialAd`, `LevelPlayBannerAd`). Fired when that ad reports an impression with revenue data.
**Signature:** `event Action<LevelPlayImpressionData>`
**Usage:**
```csharp
// Subscribe after creating the ad object. Fires on a background thread.
rewardedAd.OnAdImpressionDataReady += ImpressionDataReadyEvent;
```
**Important:**
- Subscribe when you create the ad object; unsubscribe in `OnDestroy()`
- Callback runs on **background thread**
- Each ad format reports through its own instance event
#### `LevelPlay.OnImpressionDataReady` (SDK 9.4.x and earlier — deprecated in 9.5.0+)
Single global event. Fired when any impression occurs and revenue data is available.
**Signature:** `static event Action<LevelPlayImpressionData>`
**Usage:**
```csharp
// This event is triggered on a background thread, not the Unity main thread.
LevelPlay.OnImpressionDataReady += ImpressionDataReadyEvent;
```
**Important:**
- Register **BEFORE** `LevelPlay.Init()`
- Callback runs on **background thread**
- Fires for all ad formats (Rewarded, Interstitial, Banner)
- **Deprecated in SDK 9.5.0+** (generates a compiler warning) — use the per-instance event above instead
### Data Type
#### `LevelPlayImpressionData`
Contains detailed information about an ad impression.
**Properties:**
| Property | Type | Description |
|----------|------|-------------|
| `AllData` | string/dictionary | All impression data as a structured object |
| `AuctionId` | string | Unique auction identifier |
| `MediationAdUnitName` | string | Mediation ad unit name |
| `MediationAdUnitId` | string | Mediation ad unit identifier |
| `AdFormat` | string | Ad format (e.g., "REWARDED", "INTERSTITIAL", "BANNER") |
| `AdNetwork` | string | Ad network that served the ad |
| `InstanceName` | string | Ad network instance name |
| `InstanceId` | string | Ad network instance identifier |
| `Country` | string | User's country code |
| `Placement` | string | Placement name where ad was shown |
| `Revenue` | double? | Estimated revenue in USD (nullable - check for null) |
| `Precision` | string | Revenue precision level |
| `Ab` | string | A/B test segment identifier |
| `SegmentName` | string | User segment name |
| `EncryptedCpm` | string | Encrypted CPM value |
| `ConversionValue` | number? | iOS SKAdNetwork conversion value (iOS only) |
| `CreativeId` | string | Creative identifier |
**Example:**
```csharp
private void ImpressionDataReadyEvent(LevelPlayImpressionData impressionData)
{
string allData = impressionData.AllData;
string adNetwork = impressionData.AdNetwork;
double? revenue = impressionData.Revenue;
}
```
**Note:** For the full list of available ILR data, including field description and types, refer to Impression-level revenue server-side API documentation.
## Thread Safety
**CRITICAL:** `OnImpressionDataReady` runs on a background thread. This means:
**❌ DO NOT:**
- Call Unity APIs directly (e.g., `GameObject.Find()`, `transform.position`)
- Access Unity components or game objects
- Update UI elements directly
- Call coroutines
**✅ DO:**
- Queue data for processing on main thread
- Use thread-safe operations
- Check for null values before using data
## Integration with Third-Party Analytics
### Firebase Analytics Example
The following example details how to integrate the Impression Level Revenue SDK API data with Google Analytics for Firebase. You can use it as-is or make any required changes to integrate with third-party reporting tools or your own proprietary optimization tools and databases.
**Important:** Ensure that the inside parameters aren't null.
```csharp
private void ImpressionDataReadyEvent(LevelPlayImpressionData impressionData)
{
Debug.Log("unity-script: ImpressionDataReadyEvent impressionData = " + impressionData);
if (impressionData != null)
{
Firebase.Analytics.Parameter[] AdParameters = {
new Firebase.Analytics.Parameter("ad_platform", "LevelPlay"),
new Firebase.Analytics.Parameter("ad_source", impressionData.AdNetwork),
new Firebase.Analytics.Parameter("ad_format", impressionData.AdFormat),
new Firebase.Analytics.Parameter("ad_unit_name", impressionData.InstanceName),
new Firebase.Analytics.Parameter("currency", "USD"),
new Firebase.Analytics.Parameter("value", impressionData.Revenue ?? 0) // Add protection for null values
};
Firebase.Analytics.FirebaseAnalytics.LogEvent("custom_ad_impression", AdParameters);
}
}
```
### Integration with Other Tools
After you implement the ImpressionDataListener, you can send the impression data to:
- Your own proprietary BI tools and data warehouses
- Third-party analytics platforms
- Attribution providers
- Custom backend services
## Best Practices
1. **Subscribe correctly for your SDK version**: On SDK 9.5.0+, subscribe to each ad's `OnAdImpressionDataReady` when you create the ad. On SDK 9.4.x and earlier, register the global `LevelPlay.OnImpressionDataReady` before calling `LevelPlay.Init()`
2. **Handle background thread**: Don't call Unity APIs directly in the callback
3. **Check for null values**: Revenue and other properties may be null - always check before using
4. **Protect against crashes**: Add null checks to avoid potential crashes
5. **Unregister on destroy**: Prevent memory leaks by unregistering in `OnDestroy()`
## Common Issues
### Issue: Callback never fires
**Possible causes:**
- **SDK 9.5.0+:** not subscribed to the ad instance's `OnAdImpressionDataReady`, or subscribed to a different ad object than the one shown
- **SDK 9.4.x and earlier:** global `LevelPlay.OnImpressionDataReady` not registered before `LevelPlay.Init()`
- SDK not initialized successfully
- No ads shown yet
**Solutions:**
- On 9.5.0+, subscribe to `OnAdImpressionDataReady` on the same ad object you load/show
- On 9.4.x and earlier, register the global callback before calling `Init()`
- Verify `OnInitSuccess` fires
- Show an ad and check if callback fires
### Issue: Unity APIs crash in callback
**Cause:** Calling Unity APIs from background thread
**Solution:** Don't call Unity APIs directly in the callback. Queue data for main thread processing if needed.
### Issue: Revenue is null
**Cause:** Some ad networks don't provide revenue data
**Solution:** Always check `impressionData.Revenue.HasValue` or use `impressionData.Revenue ?? 0` to provide a default value
## Testing Checklist
- [ ] Subscribed correctly for your SDK version (9.5.0+: per-instance `OnAdImpressionDataReady`; 9.4.x and earlier: global `OnImpressionDataReady` before `Init()`)
- [ ] Callback fires when ads are shown
- [ ] Revenue data is received (check for null)
- [ ] Null checks added to prevent crashes
- [ ] Analytics/backend integration works correctly
- [ ] No crashes from Unity API calls in callback
- [ ] Callback unregistered in `OnDestroy()`
- [ ] Tested with all ad formats (Rewarded, Interstitial, Banner)
references/initialization-api.md›
# LevelPlay SDK Initialization API
## Contents
- [Overview](#overview)
- [Code Organization Options (Step 7)](#code-organization-options-step-7)
- [Version-Aware ILRD Init Wiring (Step 7)](#version-aware-ilrd-init-wiring-step-7)
- [Basic Initialization](#basic-initialization)
- [Advanced Initialization](#advanced-initialization)
- [API Reference](#api-reference)
- [Initialization Timing](#initialization-timing)
- [Best Practices](#best-practices)
- [Testing](#testing)
- [Migration from IronSource.* APIs](#migration-from-ironsource-apis)
- [Error Code Reference](#error-code-reference)
## Overview
Proper SDK initialization is critical for LevelPlay to function correctly. This reference covers basic initialization, advanced options, and best practices.
## Code Organization Options (Step 7)
Step 7 of SKILL.md asks the user how they want to handle initialization and presents four options. Full
implementation for each option follows. Present the four option labels exactly as written in SKILL.md;
use the code here once the user has chosen.
### Option 1: Creating a New Script
**If you completed ATT in Step 6.5 (iOS):** Use the `LevelPlayInitializer.cs` from `references/ios-setup.md` (Part 3) instead of the template below — it already includes the `IEnumerator Start()` coroutine required for ATT and the `DontDestroyOnLoad` setup. Skip this code block and continue from the ILRD wiring below.
If creating a new script (e.g., `LevelPlayInitializer.cs`), use this complete class:
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class LevelPlayInitializer : MonoBehaviour
{
[SerializeField] private string appKey;
void Awake()
{
// Persist across scene loads so ads stay initialized
DontDestroyOnLoad(gameObject);
}
void Start()
{
// Register initialization callbacks
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
// Initialize the SDK with your App Key
LevelPlay.Init(appKey);
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay SDK initialized successfully");
// SDK is now ready to load ads
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
}
void OnDestroy()
{
LevelPlay.OnInitSuccess -= OnInitSuccess;
LevelPlay.OnInitFailed -= OnInitFailed;
}
}
```
**File location**: Save as `Assets/Scripts/LevelPlayInitializer.cs` (or `Assets/Scripts/Ads/LevelPlayInitializer.cs` if you have an Ads subfolder)
**After creating:**
1. Attach script to a GameObject in your first scene
2. In Unity Inspector, find the "App Key" field
3. Paste your App Key from Step 5 into that field
### Option 2: Adding to Existing Script
If adding to an existing script (e.g., `GameManager.cs`):
**1. Add namespace at top of file:**
```csharp
using Unity.Services.LevelPlay;
```
**2. In existing Start() or Awake() method, add initialization:**
```csharp
void Start()
{
// Register initialization callbacks
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
// Initialize the SDK - REPLACE with your actual App Key from Step 5
LevelPlay.Init("YOUR_APP_KEY_HERE");
// ... your other existing Start() code
}
```
**3. Add callback methods to class:**
```csharp
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay SDK initialized successfully");
// SDK is ready - you can now create ad objects
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
}
```
**4. In existing OnDestroy() (or create if it doesn't exist), add:**
```csharp
void OnDestroy()
{
// Unregister callbacks
LevelPlay.OnInitSuccess -= OnInitSuccess;
LevelPlay.OnInitFailed -= OnInitFailed;
}
```
Replace `"YOUR_APP_KEY_HERE"` with your actual App Key from Step 5 (the alphanumeric string copied from the LevelPlay dashboard).
**Note:** If your existing script doesn't already persist across scenes, add `DontDestroyOnLoad(gameObject);` to its `Awake()` method to prevent re-initialization when loading new scenes.
Before testing, double-check that you've replaced the placeholder with your actual App Key. The App Key should be an alphanumeric string, NOT 'YOUR_APP_KEY_HERE'.
### Option 3: Separate Script Referenced by Manager
If you want to keep ads code in its own script that your existing manager references:
**1. Create `LevelPlayInitializer.cs` using the complete class code from Option 1.**
**2. Add the `LevelPlayInitializer` component to a GameObject in the Unity Editor** (at design time — not at runtime). Use a dedicated persistent GameObject (e.g. an "Ads" object in your first scene), and set its **App Key** field in the Inspector, exactly as in Option 1's "After creating" steps.
**3. Reference it from your existing manager** via a serialized field, so `GameManager` can coordinate with the initializer without creating it at runtime:
```csharp
using UnityEngine;
public class GameManager : MonoBehaviour
{
// Assign the LevelPlayInitializer GameObject in the Inspector.
// Enter your App Key on the LevelPlayInitializer component itself (see step 2),
// NOT here — that keeps your ad credentials in one place where publishers expect them.
[SerializeField] private LevelPlayInitializer levelPlayInitializer;
void Awake()
{
// ... your other initialization code.
// LevelPlayInitializer initializes itself in its own Start();
// GameManager just holds a reference to it via the field above.
}
}
```
This keeps ad code isolated in `LevelPlayInitializer.cs` while giving `GameManager` a reference to it.
> **Why not `gameObject.AddComponent<LevelPlayInitializer>()` at runtime?** The initializer reads its App Key from a `[SerializeField]` Inspector field. A component added at runtime never receives that Inspector value, so the App Key would be empty and initialization would fail. Attaching the component in the Editor (step 2) is what makes the App Key field available to fill in.
**Note:** `LevelPlayInitializer.Awake()` calls `DontDestroyOnLoad(gameObject)`, so the GameObject you attach it to persists across scenes. Put it on a dedicated Ads GameObject rather than on your `GameManager`, unless you intend `GameManager` to persist too.
### Option 4: Just Show Me the Code
Provide the complete initialization code from Option 1 as a standalone snippet, without instructions for attaching to a GameObject or setting up the Inspector. Include a note: "This is the full initialization class — save it as `LevelPlayInitializer.cs`, attach it to a persistent GameObject in your first scene, and set the App Key field in the Inspector."
**Key points (all options):**
- Always register `OnInitSuccess` and `OnInitFailed` callbacks before calling `Init()`
- Call initialization early in app lifecycle (in `Awake()` or `Start()` of first scene)
- Only initialize once. If initialization script is in first scene and you have multiple scenes, add `DontDestroyOnLoad(gameObject);` in `Awake()` to prevent re-initialization when loading new scenes
- Wait for `OnInitSuccess` before creating ad objects
If initialization fails repeatedly, see `references/troubleshooting.md`. Common causes include incorrect App Key, no internet connection, or missing package dependencies.
## Version-Aware ILRD Init Wiring (Step 7)
**If the user answered Yes or Not Sure to the ILRD question in Step 7**, wire up the impression-revenue
callback. **The correct approach depends on the user's SDK version** (check in **Ads Mediation > Network
Manager**):
**SDK 9.5.0+ (current): do NOT add anything to the initializer here.** In 9.5.0+, ILRD is delivered per
ad instance via the `OnAdImpressionDataReady` event on each ad object — it is wired when you create each
ad in Step 9 (see `references/ilrd-api.md`). The global `LevelPlay.OnImpressionDataReady` event still
exists but is **deprecated in 9.5.0+ and generates a compiler warning** — do not use it on 9.5.0+.
**SDK 9.4.x and earlier:** subscribe to the global `LevelPlay.OnImpressionDataReady` event before
`LevelPlay.Init()`. Add these lines inside `Start()` (before `LevelPlay.Init(appKey)`):
```csharp
// SDK 9.4.x and earlier only. MUST be registered BEFORE LevelPlay.Init() to avoid
// losing early impressions. Callback fires on a BACKGROUND thread — see
// references/ilrd-api.md for thread-safe forwarding patterns and a Firebase example.
LevelPlay.OnImpressionDataReady += OnImpressionDataReady;
```
Add this stub method to the class:
```csharp
private void OnImpressionDataReady(LevelPlayImpressionData impressionData)
{
// See references/ilrd-api.md for full implementation.
// For now, just log so you can verify it fires:
Debug.Log($"ILRD: {impressionData.AdNetwork} / {impressionData.AdFormat} / ${impressionData.Revenue}");
}
```
And unsubscribe in `OnDestroy()`:
```csharp
LevelPlay.OnImpressionDataReady -= OnImpressionDataReady;
```
**iOS path (SDK 9.4.x and earlier only):** if you used the `LevelPlayInitializer.cs` from
`references/ios-setup.md` Part 3, note that `Start()` is a coroutine with **no `LevelPlay.Init(appKey)`
call inside it** — initialization happens in `InitializeLevelPlay()`. Place the
`LevelPlay.OnImpressionDataReady += ...` line inside `InitializeLevelPlay()`, immediately **before**
`LevelPlay.Init(appKey)`, so it registers before the first impression.
For Option 2 (existing script) and Option 3 (separate script referenced by a manager), apply the same
version-aware wiring: on 9.4.x and earlier add the global subscription/stub/unsubscribe shown above; on
9.5.0+ add nothing here (ILRD is wired per ad instance in Step 9). For Option 3, the wiring goes in
`LevelPlayInitializer.cs`, with no changes in `GameManager`.
**Next:** Once you confirm the log fires after your first ad impression, read `references/ilrd-api.md` to
wire it up to your actual analytics platform. Note: ILRD callbacks do not fire with mock ads in the
Unity Editor — you'll need a device build to verify this log fires (see Step 10).
## Basic Initialization
### Minimum Required Setup
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class LevelPlayInitializer : MonoBehaviour
{
[SerializeField] private string appKey = "YOUR_APP_KEY";
void Start()
{
// Register initialization callbacks
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
// Initialize SDK
LevelPlay.Init(appKey);
}
void OnDestroy()
{
// Unregister callbacks
LevelPlay.OnInitSuccess -= OnInitSuccess;
LevelPlay.OnInitFailed -= OnInitFailed;
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay SDK initialized successfully");
// SDK is ready - you can now create ad objects
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
}
}
```
**Key points:**
- Register `OnInitSuccess` and `OnInitFailed` callbacks before calling `Init()`
- The `appKey` is obtained from your LevelPlay dashboard
- Call `Init()` as early as possible (ideally in the first scene)
- Only create ad objects after `OnInitSuccess` fires
## Advanced Initialization
### With User ID
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class AdvancedInitializer : MonoBehaviour
{
[SerializeField] private string appKey = "YOUR_APP_KEY";
void Start()
{
// Register callbacks
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
// Initialize SDK with optional user ID
string userId = "user_12345"; // Your internal user ID
LevelPlay.Init(appKey, userId);
}
void OnDestroy()
{
LevelPlay.OnInitSuccess -= OnInitSuccess;
LevelPlay.OnInitFailed -= OnInitFailed;
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay initialized successfully");
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"Init failed: {error.ErrorMessage}");
}
}
```
**When to use:** When you need to track users across sessions or implement server-side verification for rewards.
### Complete Production-Ready Example
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class ProductionLevelPlayInitializer : MonoBehaviour
{
[Header("Configuration")]
[SerializeField] private string appKey = "YOUR_APP_KEY";
void Awake()
{
// Ensure this object persists across scenes
DontDestroyOnLoad(gameObject);
// Initialize as early as possible
InitializeLevelPlay();
}
private void InitializeLevelPlay()
{
// Register initialization callbacks
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
// Initialize SDK with optional user ID
if (HasUserId())
{
string userId = GetUserId();
LevelPlay.Init(appKey, userId);
Debug.Log($"LevelPlay SDK initialization started with user ID: {userId}");
}
else
{
LevelPlay.Init(appKey);
Debug.Log("LevelPlay SDK initialization started");
}
}
void OnDestroy()
{
// Unregister callbacks
LevelPlay.OnInitSuccess -= OnInitSuccess;
LevelPlay.OnInitFailed -= OnInitFailed;
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay SDK initialization completed");
// SDK is ready - create ad objects here
CreateAdObjects();
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
// Optionally retry initialization after delay
Invoke(nameof(RetryInitialization), 5f);
}
private void RetryInitialization()
{
Debug.Log("Retrying LevelPlay initialization...");
LevelPlay.Init(appKey);
}
private bool HasUserId()
{
// Check if user is logged in and has an ID
return !string.IsNullOrEmpty(PlayerPrefs.GetString("UserID", ""));
}
private string GetUserId()
{
// Get your internal user ID
return PlayerPrefs.GetString("UserID", "");
}
private void CreateAdObjects()
{
// Create your ad objects here after init succeeds
// Example:
// rewardedAd = new LevelPlayRewardedAd("rewarded_ad_unit_id");
// interstitialAd = new LevelPlayInterstitialAd("interstitial_ad_unit_id");
Debug.Log("Creating ad objects...");
}
}
```
## API Reference
### Core Methods
#### `LevelPlay.Init(string appKey, string userId = null)`
Initialize the LevelPlay SDK.
**Parameters:**
- `appKey`: Your application key from LevelPlay dashboard
- `userId` (optional): Your internal user ID for tracking and server-side reward verification
**Usage:**
```csharp
// Basic initialization
LevelPlay.Init("your_app_key");
// With user ID
LevelPlay.Init("your_app_key", "user_12345");
```
**Important:** Register `OnInitSuccess` and `OnInitFailed` callbacks before calling `Init()`.
**When to use user ID:**
- Server-side reward verification
- User-level analytics
- Cross-session tracking
#### `LevelPlay.SetSegment(LevelPlaySegment segment)`
Set user segmentation data for improved ad targeting and reporting.
**Parameters:**
- `segment`: A `LevelPlaySegment` object containing user segment information
**Usage:**
```csharp
// Create a segment with user data
var segment = new LevelPlaySegment();
segment.SegmentName = "high_spenders";
segment.Level = 25;
segment.UserCreationDate = 1609459200000; // Unix timestamp in milliseconds (Jan 1, 2021)
segment.IapTotal = 49.99;
segment.IsPaying = 1; // 0 or 1
// Set custom parameters
segment.SetCustom("vip_tier", "gold");
segment.SetCustom("gameplay_hours", "150");
// Apply segment before or after initialization
LevelPlay.SetSegment(segment);
```
**When to call:**
- Can be called before or after SDK initialization
- Update whenever user segment changes (level up, first purchase, etc.)
- Helps LevelPlay optimize ad delivery and reporting
**LevelPlaySegment properties:**
- `SegmentName` (string): Name of the segment (e.g., "high_spenders", "casual_players")
- `Level` (int): User's current level in the game
- `UserCreationDate` (long): Unix timestamp of when user created their account
- `IapTotal` (double): Total amount spent on in-app purchases
- `IsPaying` (int): Whether the user has made any purchases (0 or 1)
**LevelPlaySegment methods:**
- `SetCustom(string key, string value)`: Add custom key-value parameters for advanced segmentation
**Custom parameter limits:**
- Maximum 5 custom parameters per segment
**Best practices:**
- Update segments when significant user milestones occur
- Use meaningful segment names that describe user behavior
- Keep custom parameters descriptive and consistent
- Stay within the 5 custom parameter limit per segment
#### `LevelPlay.SetPauseGame(bool pause)`
Pause Unity 3D game activities (except ad callbacks) during ad presentation.
**Platform:** iOS only (SDK 8.5.0+)
**Parameters:**
- `pause`: Set to `true` to pause game activities during ads
**Usage:**
```csharp
LevelPlay.SetPauseGame(true);
```
**Notes:**
- iOS only
- Can be called once per session, before or after SDK initialization
- Game resumes automatically when ad closes
- Affects Rewarded and Interstitial ads
#### `LevelPlay.SetMetaData(string key, params string[] values)`
Set metadata for SDK configuration.
**Parameters:**
- `key`: The metadata key
- `values`: Array of values
**Use Cases:**
**1. Server-to-Server Rewarded Callbacks (SDK 8.11.0+)**
Pass custom parameters for rewarded ad callbacks:
```csharp
LevelPlay.SetMetaData("LevelPlay_Rewarded_Server_Params", new [] { "key1=value1", "key2=value2" });
```
**2. Test Suite Activation**
Enable the LevelPlay Test Suite:
```csharp
LevelPlay.SetMetaData("is_test_suite", "enable");
LevelPlay.Init(appKey);
```
**Note:** For Test Suite, must be called before `Init()`.
#### `LevelPlay.SetDynamicUserId(string userId)`
Set a dynamic user ID that can be changed during the session for server-side rewarded ad verification.
**Parameters:**
- `userId`: The dynamic user ID to set
**Returns:** `bool` - true if successfully set, false otherwise
**Usage:**
```csharp
string newUserId = "user_67890";
bool success = LevelPlay.SetDynamicUserId(newUserId);
if (success)
{
Debug.Log($"Dynamic user ID set to: {newUserId}");
}
```
**When to use:**
- Server-side reward verification for rewarded ads
- When user ID changes mid-session (e.g., user logs in after guest play)
- Must be set before showing rewarded ads that require verification
**Important:** This is different from the userId passed to `Init()`. The dynamic user ID can be changed during the session and is used specifically for server-to-server reward callbacks.
#### `LevelPlay.LaunchTestSuite()`
Launch the LevelPlay Test Suite for testing ad integrations on device.
**Usage:**
```csharp
void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("SDK initialized");
#if UNITY_EDITOR || DEVELOPMENT_BUILD
// Launch Test Suite for testing
LevelPlay.LaunchTestSuite();
#endif
}
```
**Requirements:**
- SDK must be initialized before calling this method
- Test Suite must be enabled via metadata before initialization:
```csharp
LevelPlay.SetMetaData("is_test_suite", "enable");
LevelPlay.Init(appKey);
```
**When to use:**
- Testing ad networks on real devices
- Verifying ad fill and display for different networks
- Debugging ad integration issues
- QA testing before release
**Notes:**
- Opens a UI overlay with test controls
- Allows testing each ad network individually
- Only available on device builds (not Editor)
- Should be removed or disabled in production builds
#### `LevelPlay.SetAdaptersDebug(bool enabled)`
Enable or disable debug logging for ad network adapters.
**Parameters:**
- `enabled`: Set to `true` to enable adapter debug logs, `false` to disable
**Usage:**
```csharp
void Awake()
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
// Enable adapter debug logs for development
LevelPlay.SetAdaptersDebug(true);
#endif
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.Init(appKey);
}
```
**When to use:**
- Debugging ad network integration issues
- Investigating ad load failures
- Understanding adapter behavior
- Development and QA testing
**Notes:**
- Increases console log verbosity
- Helps diagnose network-specific issues
- Should be disabled in production builds
- Can be called before or after SDK initialization
#### `LevelPlay.SetNetworkData(string networkKey, string networkData)`
Set custom data for specific ad networks.
**Parameters:**
- `networkKey`: The ad network identifier (e.g., "UnityAds", "AdMob")
- `networkData`: Custom data string for the network
**Usage:**
```csharp
// Set custom network-specific configuration
LevelPlay.SetNetworkData("UnityAds", "customValue");
LevelPlay.SetNetworkData("AdMob", "extraConfig");
```
**When to use:**
- Passing custom configuration to specific ad networks
- Advanced network-specific setup
- When network adapter requires additional data
**Notes:**
- Call before SDK initialization
- Consult individual network adapter documentation for supported data
- Not commonly needed for standard integrations
### Events
#### `LevelPlay.OnInitSuccess`
Fired when SDK initialization completes successfully.
**Signature:** `event Action<LevelPlayConfiguration>`
**Usage:**
```csharp
LevelPlay.OnInitSuccess += (config) =>
{
Debug.Log("SDK ready");
Debug.Log($"Unity Version: {LevelPlay.UnityVersion}");
Debug.Log($"Plugin Version: {LevelPlay.PluginVersion}");
// Create ad objects here
};
```
**Note:** `UnityVersion` and `PluginVersion` are static properties on the `LevelPlay` class, not on `LevelPlayConfiguration`.
#### `LevelPlay.OnInitFailed`
Fired when SDK initialization fails.
**Signature:** `event Action<LevelPlayInitError>`
**Usage:**
```csharp
LevelPlay.OnInitFailed += (error) =>
{
Debug.LogError($"Init failed: Code {error.ErrorCode}, Message: {error.ErrorMessage}");
// Optionally retry
};
```
**LevelPlayInitError properties:**
- `error.ErrorCode`: Numeric error code
- `error.ErrorMessage`: Description of the initialization error
## Initialization Timing
### Early Initialization (Recommended)
Initialize in your first scene, as early as possible:
```csharp
void Awake()
{
DontDestroyOnLoad(gameObject);
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey);
}
```
**Why:** Ads start loading immediately, maximizing availability when needed.
### Delayed Initialization (Advanced)
In some cases, you might delay initialization:
```csharp
void Start()
{
// Wait for user to complete onboarding first
StartCoroutine(InitializeAfterOnboarding());
}
IEnumerator InitializeAfterOnboarding()
{
yield return new WaitUntil(() => HasCompletedOnboarding());
// Now initialize
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey);
}
```
**When to use:**
- Waiting for user consent decisions
- Completing critical onboarding first
- Reducing initial app load time
**Trade-off:** Ads won't be ready as quickly.
## Best Practices
1. **Initialize early**: Call `Init()` in your first scene's `Awake()` or `Start()`
2. **Persist the initializer**: Use `DontDestroyOnLoad()` to prevent re-initialization
3. **Register callbacks first**: Always register `OnInitSuccess` and `OnInitFailed` before calling `Init()`
4. **Create ads after init**: Only create ad objects after `OnInitSuccess` fires
5. **Set user ID if available**: Helps with analytics and server-side verification
6. **Initialize only once**: Don't re-initialize the SDK across scenes
7. **Handle failures**: Implement `OnInitFailed` to catch and log initialization errors
## Testing
### Development Checklist
- [ ] SDK initializes successfully (check logs)
- [ ] `OnInitSuccess` callback fires
- [ ] Ads can be created after initialization
- [ ] User ID is set correctly (if used)
- [ ] Initialization happens early in app lifecycle
- [ ] Error handling works (`OnInitFailed` implemented)
### Common Issues
**Issue: Ads not loading after initialization**
- Check that App Key is correct
- Verify ad objects are created **after** `OnInitSuccess` fires
- Check console for initialization errors
- Enable test mode in LevelPlay dashboard
**Issue: Initialization fails**
- Verify App Key matches LevelPlay dashboard exactly
- Check internet connectivity
- Look at `LevelPlayInitError.ErrorMessage` for specific error
- Ensure LevelPlay SDK package is properly installed
**Issue: User ID not being tracked**
- Verify user ID is passed to `Init(appKey, userId)` as the second parameter
- Check that user ID is non-empty and unique
- Verify format meets LevelPlay requirements (alphanumeric, max 64 chars)
**Issue: Ads not ready when needed**
- Initialize earlier in the app lifecycle
- Create ad objects immediately in `OnInitSuccess`
- Load ads immediately after creating ad objects
## Migration from IronSource.* APIs
> **Full migration guide:** for SDK upgrade paths, per-format ad unit API migration, the migration completeness checklist, Unity Ads migration, and Maven Central build failures, see `references/migration-sdk-9.md`. The summary below covers initialization only.
If you're migrating from deprecated IronSource APIs:
**Old (Deprecated):**
```csharp
IronSource.Agent.validateIntegration();
IronSource.Agent.init(appKey);
```
**New (Current):**
```csharp
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey);
```
**Key differences:**
- Init errors are reported via `OnInitFailed`; for integration validation use the LevelPlay Test Suite (`LaunchTestSuite()`)
- Must register callbacks before calling `Init()`
- Ad objects created explicitly after initialization (not automatic background loading)
## Error Code Reference
When ad operations fail, `LevelPlayAdError` contains an `ErrorCode` property with one of these values:
> **Note:** Code **508** is an initialization/mediation-level error, surfaced via `LevelPlayInitError` in the `OnInitFailed` callback — not via a per-ad `LevelPlayAdError` (hence "N/A" in the Ad Formats column). All other codes below are per-ad errors delivered through `LevelPlayAdError` in the ad load/show failure callbacks.
| Code | Ad Formats | Description |
|------|-----------|-------------|
| **508** | N/A | Init failure of mediation/Network, Calling Demand Only API in non Demand Only mode, Calling non Demand Only API in Demand Only mode |
| **509** | Interstitial, Rewarded | Show Fail: No ads to show |
| **510** | Interstitial, Rewarded, Banner | Load Fail: Server response failed |
| **520** | Interstitial, Rewarded | Show Fail: No internet connection; ShouldTrackNetworkState is enabled |
| **524** | Interstitial, Rewarded | Show Fail: Placement has reached its limit as defined per pace or capping limit |
| **526** | Interstitial, Rewarded | Show Fail: Ad unit has reached its daily cap per session |
| **604** | Banner | Can't load because the placement is capped |
| **605** | Banner | Unexpected exception while loading the banner |
| **606** | Banner | No banner fill on all the networks on the first load |
| **1007** | Interstitial, Rewarded | Auction Fail: Auction request did not contain all required information |
| **1022** | Rewarded | Show Fail: Cannot show a rewarded ad while another rewarded ad is showing |
| **1023** | Rewarded | Show Fail: Show called when there are no available ads to show |
| **1035** | Interstitial | Empty Waterfall |
| **1036** | Interstitial | Show Fail: Cannot show an interstitial while another interstitial is showing |
| **1037** | Interstitial | Load Fail: Cannot load an interstitial while another interstitial is loading |
**Usage in error callbacks:**
```csharp
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Ad load failed: Code {error.ErrorCode}, Message: {error.ErrorMessage}");
// Handle specific errors
if (error.ErrorCode == 510)
{
// Server response failed - retry with backoff
}
else if (error.ErrorCode == 520)
{
// No internet connection - wait for connectivity
}
}
```
references/interstitial-api.md›
# Interstitial Ads API Reference
## Contents
- [Overview](#overview)
- [Key Characteristics](#key-characteristics)
- [Implementation Pattern](#implementation-pattern)
- [API Reference](#api-reference)
- [Data Types](#data-types)
- [Best Practices](#best-practices)
- [Common Issues](#common-issues)
- [Testing Checklist](#testing-checklist)
## Overview
Interstitial ads are full-screen video or static ads shown at natural transition points in your app. They provide good revenue while maintaining acceptable user experience when placed thoughtfully.
## Key Characteristics
- **Full-screen**: Takes over the entire screen
- **Skippable**: User can close after viewing
- **Best for**: Level transitions, game over, menu navigation, session breaks
## Implementation Pattern
### Basic Interstitial Implementation
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class InterstitialAdManager : MonoBehaviour
{
private LevelPlayInterstitialAd interstitialAd;
private string adUnitId = "YOUR_INTERSTITIAL_AD_UNIT_ID";
void Start()
{
// Create the interstitial ad object using constructor
interstitialAd = new LevelPlayInterstitialAd(adUnitId);
// ILRD (SDK 9.5.0+): if impression-revenue tracking is enabled, also subscribe here —
// interstitialAd.OnAdImpressionDataReady += OnImpressionDataReady; // see references/ilrd-api.md
// Register event listeners
interstitialAd.OnAdLoaded += OnAdLoaded;
interstitialAd.OnAdLoadFailed += OnAdLoadFailed;
interstitialAd.OnAdDisplayed += OnAdDisplayed;
interstitialAd.OnAdDisplayFailed += OnAdDisplayFailed;
interstitialAd.OnAdClicked += OnAdClicked;
interstitialAd.OnAdClosed += OnAdClosed;
interstitialAd.OnAdInfoChanged += OnAdInfoChanged; // Fires when the loaded ad updates after a new auction result
// Load the ad
LoadAd();
}
void OnDestroy()
{
// Unregister event listeners
if (interstitialAd != null)
{
interstitialAd.OnAdLoaded -= OnAdLoaded;
interstitialAd.OnAdLoadFailed -= OnAdLoadFailed;
interstitialAd.OnAdDisplayed -= OnAdDisplayed;
interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed;
interstitialAd.OnAdClicked -= OnAdClicked;
interstitialAd.OnAdClosed -= OnAdClosed;
interstitialAd.OnAdInfoChanged -= OnAdInfoChanged;
// ILRD (9.5.0+): interstitialAd.OnAdImpressionDataReady -= OnImpressionDataReady;
// Free the native ad and its resources when this manager is destroyed
interstitialAd.DestroyAd();
}
}
public void LoadAd()
{
Debug.Log("Loading interstitial ad...");
interstitialAd.LoadAd();
}
public void ShowAd()
{
if (interstitialAd.IsAdReady())
{
Debug.Log("Showing interstitial ad");
interstitialAd.ShowAd();
}
else
{
Debug.LogWarning("Interstitial ad is not ready yet");
}
}
// True once an interstitial ad has finished loading and can be shown.
// Use this to decide whether to show opportunistically before calling ShowAd().
public bool IsAdReady() => interstitialAd != null && interstitialAd.IsAdReady();
// Event Callbacks
private void OnAdLoaded(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial ad loaded successfully");
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Interstitial ad failed to load: {error.ErrorMessage}");
// Retry loading after a delay
Invoke(nameof(LoadAd), 30f);
}
private void OnAdDisplayed(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial ad displayed");
}
private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
{
Debug.LogError($"Interstitial ad failed to display: {error.ErrorMessage}");
// Load a new ad
LoadAd();
}
private void OnAdClicked(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial ad clicked");
}
private void OnAdClosed(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial ad closed");
// Load the next ad
LoadAd();
}
private void OnAdInfoChanged(LevelPlayAdInfo adInfo)
{
// Optional: log or update analytics with the latest revenue estimate
Debug.Log($"Interstitial ad info changed - network: {adInfo.AdNetwork}, revenue: ${adInfo.Revenue}");
}
}
```
### Advanced: Frequency Capping
Implement time-based frequency capping to avoid showing ads too frequently:
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class FrequencyCappedInterstitialManager : MonoBehaviour
{
private LevelPlayInterstitialAd interstitialAd;
private string adUnitId = "YOUR_INTERSTITIAL_AD_UNIT_ID";
// Frequency capping settings
private float minTimeBetweenAds = 300f; // 5 minutes in seconds
private float lastAdShownTime = -999f; // Time when last ad was shown
void Start()
{
// Create interstitial ad object
interstitialAd = new LevelPlayInterstitialAd(adUnitId);
// Register callbacks
interstitialAd.OnAdLoaded += OnAdLoaded;
interstitialAd.OnAdLoadFailed += OnAdLoadFailed;
interstitialAd.OnAdDisplayed += OnAdDisplayed;
interstitialAd.OnAdClosed += OnAdClosed;
interstitialAd.OnAdDisplayFailed += OnAdDisplayFailed;
// Load first ad
interstitialAd.LoadAd();
}
void OnDestroy()
{
if (interstitialAd != null)
{
interstitialAd.OnAdLoaded -= OnAdLoaded;
interstitialAd.OnAdLoadFailed -= OnAdLoadFailed;
interstitialAd.OnAdDisplayed -= OnAdDisplayed;
interstitialAd.OnAdClosed -= OnAdClosed;
interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed;
// Destroy the ad to free its native resources
interstitialAd.DestroyAd();
}
}
public void TryShowInterstitial()
{
// Check if enough time has passed since last ad
float timeSinceLastAd = Time.time - lastAdShownTime;
if (timeSinceLastAd < minTimeBetweenAds)
{
Debug.Log($"Frequency cap: {minTimeBetweenAds - timeSinceLastAd:F0}s until next ad");
return;
}
// Check if ad is ready
if (interstitialAd.IsAdReady())
{
interstitialAd.ShowAd();
}
else
{
Debug.LogWarning("Interstitial not ready, loading...");
interstitialAd.LoadAd();
}
}
private void OnAdLoaded(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial loaded");
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Interstitial load failed: {error.ErrorMessage}");
Invoke(nameof(LoadAd), 30f);
}
private void OnAdDisplayed(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial displayed");
lastAdShownTime = Time.time; // Record when ad was shown
}
private void OnAdClosed(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial closed");
// Load next ad
interstitialAd.LoadAd();
}
private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
{
Debug.LogError($"Interstitial display failed: {error.ErrorMessage}");
interstitialAd.LoadAd();
}
private void LoadAd()
{
if (interstitialAd != null)
{
interstitialAd.LoadAd();
}
}
}
```
### Advanced: Multiple Placements
Track different interstitial placements for analytics:
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class MultiPlacementInterstitialManager : MonoBehaviour
{
private LevelPlayInterstitialAd interstitialAd;
private string adUnitId = "YOUR_INTERSTITIAL_AD_UNIT_ID";
// Placement names
private const string PLACEMENT_LEVEL_COMPLETE = "level_complete";
private const string PLACEMENT_GAME_OVER = "game_over";
private const string PLACEMENT_MAIN_MENU = "main_menu";
// Frequency capping per placement
private float minTimeBetweenAds = 300f;
private float lastAdTime = -999f;
void Start()
{
interstitialAd = new LevelPlayInterstitialAd(adUnitId);
interstitialAd.OnAdLoaded += OnAdLoaded;
interstitialAd.OnAdLoadFailed += OnAdLoadFailed;
interstitialAd.OnAdDisplayed += OnAdDisplayed;
interstitialAd.OnAdClosed += OnAdClosed;
interstitialAd.OnAdDisplayFailed += OnAdDisplayFailed;
interstitialAd.LoadAd();
}
void OnDestroy()
{
if (interstitialAd != null)
{
interstitialAd.OnAdLoaded -= OnAdLoaded;
interstitialAd.OnAdLoadFailed -= OnAdLoadFailed;
interstitialAd.OnAdDisplayed -= OnAdDisplayed;
interstitialAd.OnAdClosed -= OnAdClosed;
interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed;
// Destroy the ad to free its native resources
interstitialAd.DestroyAd();
}
}
// Public methods for different placements
public void ShowOnLevelComplete()
{
TryShowWithPlacement(PLACEMENT_LEVEL_COMPLETE);
}
public void ShowOnGameOver()
{
TryShowWithPlacement(PLACEMENT_GAME_OVER);
}
public void ShowOnMainMenu()
{
TryShowWithPlacement(PLACEMENT_MAIN_MENU);
}
private void TryShowWithPlacement(string placementName)
{
// Check frequency cap
if (Time.time - lastAdTime < minTimeBetweenAds)
{
Debug.Log($"Frequency cap active for {placementName}");
return;
}
// Check placement capping
if (LevelPlayInterstitialAd.IsPlacementCapped(placementName))
{
Debug.Log($"Placement {placementName} is capped");
return;
}
// Check if ad ready
if (interstitialAd.IsAdReady())
{
Debug.Log($"Showing interstitial with placement: {placementName}");
interstitialAd.ShowAd(placementName: placementName);
}
else
{
Debug.LogWarning("Interstitial not ready");
}
}
private void OnAdLoaded(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial loaded");
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Interstitial load failed: {error.ErrorMessage}");
Invoke(nameof(LoadAd), 30f);
}
private void OnAdDisplayed(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial displayed");
lastAdTime = Time.time;
}
private void OnAdClosed(LevelPlayAdInfo adInfo)
{
Debug.Log("Interstitial closed");
interstitialAd.LoadAd();
}
private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
{
Debug.LogError($"Interstitial display failed: {error.ErrorMessage}");
interstitialAd.LoadAd();
}
private void LoadAd()
{
if (interstitialAd != null)
{
interstitialAd.LoadAd();
}
}
}
```
## API Reference
### Constructor
#### `new LevelPlayInterstitialAd(string adUnitId)`
Create an interstitial ad object.
**Parameters:**
- `adUnitId`: The ad unit identifier from your LevelPlay dashboard
**Returns:** `LevelPlayInterstitialAd` object
**Usage:**
```csharp
LevelPlayInterstitialAd interstitialAd = new LevelPlayInterstitialAd("your_ad_unit_id");
```
**Important:** Call this only after `LevelPlay.Init()` has completed successfully (in the `OnInitSuccess` callback).
#### `new LevelPlayInterstitialAd(string adUnitId, Config config)`
Create an interstitial ad object with custom configuration.
**Parameters:**
- `adUnitId`: The ad unit identifier
- `config`: Optional configuration object
**Usage:**
```csharp
var configBuilder = new LevelPlayInterstitialAd.Config.Builder();
configBuilder.SetBidFloor(1.0); // Minimum bid price in USD
var config = configBuilder.Build();
LevelPlayInterstitialAd interstitialAd = new LevelPlayInterstitialAd("your_ad_unit_id", config);
```
### Configuration Builder
#### `LevelPlayInterstitialAd.Config.Builder()`
Create a configuration builder for interstitial ads.
**Returns:** `LevelPlayInterstitialAd.Config.Builder` object
**Usage:**
```csharp
var configBuilder = new LevelPlayInterstitialAd.Config.Builder();
```
**Set minimum bid price:**
```csharp
configBuilder.SetBidFloor(1.0); // Minimum bid price in USD ($1.00 CPM)
```
**When to use:** Set a minimum bid price in USD for ad requests.
#### `Build()`
Build the configuration object.
**Returns:** `LevelPlayInterstitialAd.Config` object
**Usage:**
```csharp
var config = configBuilder.Build();
```
### Core Methods
#### `LoadAd()`
Load an interstitial ad.
**Usage:**
```csharp
interstitialAd.LoadAd();
```
**When to call:**
- After SDK initialization completes
- After showing an ad (to load the next one)
- After a load or display failure
#### `ShowAd()`
Show the interstitial ad without a placement name.
**Usage:**
```csharp
if (interstitialAd.IsAdReady())
{
interstitialAd.ShowAd();
}
```
**Important:** Always check `IsAdReady()` before calling `ShowAd()`.
#### `ShowAd(placementName: string)`
Show the interstitial ad with a named placement for analytics tracking.
**Parameters:**
- `placementName`: Placement name configured in LevelPlay dashboard
**Usage:**
```csharp
interstitialAd.ShowAd(placementName: "level_complete");
```
**When to use:** When tracking multiple placements within the same ad unit for analytics.
#### `IsAdReady()`
Check if an interstitial ad is loaded and ready to show.
**Returns:** `bool` - true if ready, false otherwise
**Usage:**
```csharp
if (interstitialAd.IsAdReady())
{
// Ad is ready to show
interstitialAd.ShowAd();
}
else
{
// Ad not ready, load it
interstitialAd.LoadAd();
}
```
#### `LevelPlayInterstitialAd.IsPlacementCapped(string placementName)` (Static)
Check if a placement has reached its capping limit.
**Parameters:**
- `placementName`: The placement name to check
**Returns:** `bool` - true if capped, false otherwise
**Usage:**
```csharp
if (LevelPlayInterstitialAd.IsPlacementCapped("level_complete"))
{
Debug.Log("Level complete placement is capped");
}
```
**When to use:** Before showing an ad to check if the placement frequency cap is reached.
### Events
All events are properties of the `LevelPlayInterstitialAd` object.
**Threading:** All ad callbacks run on the Unity main thread, so you can safely call Unity APIs (update UI, access GameObjects, etc.) directly in these callbacks. This is different from `LevelPlay.OnImpressionDataReady` which runs on a background thread.
#### `OnAdLoaded`
Fired when an interstitial ad is successfully loaded.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
interstitialAd.OnAdLoaded += (adInfo) =>
{
Debug.Log("Interstitial ad loaded");
};
```
#### `OnAdLoadFailed`
Fired when an interstitial ad fails to load.
**Signature:** `event Action<LevelPlayAdError>`
**Usage:**
```csharp
interstitialAd.OnAdLoadFailed += (error) =>
{
Debug.LogWarning($"Load failed: {error.ErrorMessage}");
};
```
**Best practice:** Implement retry logic with exponential backoff.
#### `OnAdDisplayed`
Fired when an interstitial ad is displayed on screen.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
interstitialAd.OnAdDisplayed += (adInfo) =>
{
Debug.Log("Interstitial ad displayed");
// Optional: Track analytics
};
```
#### `OnAdDisplayFailed`
Fired when an interstitial ad fails to display.
**Signature:** `event Action<LevelPlayAdInfo, LevelPlayAdError>`
**Usage:**
```csharp
interstitialAd.OnAdDisplayFailed += (adInfo, error) =>
{
Debug.LogError($"Display failed: {error.ErrorMessage}");
// Load a new ad
interstitialAd.LoadAd();
};
```
#### `OnAdClicked`
Fired when the user clicks on the interstitial ad.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
interstitialAd.OnAdClicked += (adInfo) =>
{
Debug.Log("Interstitial ad clicked");
// Optional analytics tracking
};
```
#### `OnAdClosed`
Fired when the interstitial ad is closed.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
interstitialAd.OnAdClosed += (adInfo) =>
{
Debug.Log("Interstitial ad closed");
// Resume game, load next ad
interstitialAd.LoadAd();
};
```
**Best practice:** Load the next ad immediately in this callback.
#### `OnAdInfoChanged`
Fired when ad information changes, such as when a new winning ad becomes available after auction.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
interstitialAd.OnAdInfoChanged += (adInfo) =>
{
Debug.Log($"Ad info changed - New ad from: {adInfo.AdNetwork}");
Debug.Log($"Estimated revenue: ${adInfo.Revenue}");
};
```
**Important:** This event indicates that a new ad with potentially different revenue has become available. The SDK automatically selects the winning ad from the auction when you call `ShowAd()`, but this event lets you know when the ad info has been updated. This is particularly important for revenue optimization as it signals when a new auction winner has loaded.
**When it fires:**
- After a new ad loads with different properties than the previous one
- When the waterfall selects a different ad network
- When ad auction results change
**Why it matters:** The updated `LevelPlayAdInfo` contains the latest revenue estimates and network information, which directly impacts your monetization. Always use the most recent `adInfo` when logging or analyzing ad performance.
**If you're using ILRD** (`references/ilrd-api.md`): the `LevelPlayImpressionData` you receive in `OnImpressionDataReady` already contains the final revenue value, so `OnAdInfoChanged` is mostly useful for in-Editor debugging of the waterfall. Most publishers can leave it as a logging hook.
## Data Types
### LevelPlayAdInfo
Contains information about the ad.
**Properties:**
- `AdId` (string): Unique identifier for this specific ad instance
- `AdUnitId` (string): The ad unit identifier
- `AdUnitName` (string): The ad unit name
- `AdSize` (LevelPlayAdSize): The ad size (may be null; banner-relevant)
- `AdFormat` (string): The ad format (e.g., "INTERSTITIAL")
- `PlacementName` (string): Placement name where ad was shown
- `AuctionId` (string): Unique auction identifier
- `CreativeId` (string): Creative identifier
- `Country` (string): User's country code (ISO 3166-1)
- `Ab` (string): A/B test segment identifier
- `SegmentName` (string): User segment name
- `AdNetwork` (string): Ad network that served the ad
- `InstanceName` (string): Ad network instance name
- `InstanceId` (string): Ad network instance identifier
- `Revenue` (double?): Estimated revenue in USD (nullable - check for null)
- `Precision` (string): Revenue precision level
- `EncryptedCPM` (string): Encrypted CPM value
### LevelPlayAdError
Contains error information when ad operations fail.
**Properties:**
- `ErrorCode` (int): Numeric error code
- `ErrorMessage` (string): Human-readable error description
- `AdUnitId` (string): The ad unit identifier where the error occurred
- `AdId` (string): Unique identifier for the specific ad instance
## Best Practices
### Loading Strategy
**Keep an interstitial ready for opportunistic moments:**
```csharp
// Load after init
void OnInitSuccess(LevelPlayConfiguration config)
{
interstitialAd = new LevelPlayInterstitialAd(adUnitId);
interstitialAd.OnAdLoaded += OnAdLoaded;
interstitialAd.OnAdClosed += OnAdClosed;
interstitialAd.LoadAd();
}
// Reload after showing
void OnAdClosed(LevelPlayAdInfo adInfo)
{
interstitialAd.LoadAd();
}
```
### Placement Strategy
**Show at natural transition points:**
- ✅ Level complete screen
- ✅ Game over screen
- ✅ Returning to main menu
- ✅ Between sessions
- ❌ During active gameplay
- ❌ Mid-level or mid-action
### Frequency Capping
**Implement time-based capping to avoid annoying users:**
```csharp
private float minTimeBetweenAds = 300f; // 5 minutes
private float lastAdTime = -999f;
public void TryShowInterstitial()
{
if (Time.time - lastAdTime < minTimeBetweenAds)
{
Debug.Log("Too soon to show another ad");
return;
}
if (interstitialAd.IsAdReady())
{
interstitialAd.ShowAd();
}
}
private void OnAdDisplayed(LevelPlayAdInfo adInfo)
{
lastAdTime = Time.time;
}
```
**Recommended frequencies:**
- **Revenue-focused**: 3-5 minutes
- **Balanced**: 5-7 minutes
- **UX-focused**: 7-10 minutes or never
### User Flow
**Never block user progress waiting for ads:**
```csharp
// ✅ CORRECT - Don't block flow
public void OnLevelComplete()
{
// Continue to next level regardless of ad
LoadNextLevel();
// Try to show ad, but don't wait for it
TryShowInterstitial();
}
// ❌ WRONG - Blocking user
public void OnLevelComplete()
{
if (interstitialAd.IsAdReady())
{
interstitialAd.ShowAd();
// Don't wait here to load next level!
}
}
```
### Error Handling
**Handle ad load failures:**
```csharp
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Ad load failed: {error.ErrorMessage}");
// Retry loading after a delay
Invoke(nameof(RetryLoad), 30f);
}
private void RetryLoad()
{
interstitialAd.LoadAd();
}
```
### Memory Management
**Always unsubscribe from events:**
```csharp
void OnDestroy()
{
if (interstitialAd != null)
{
interstitialAd.OnAdLoaded -= OnAdLoaded;
interstitialAd.OnAdLoadFailed -= OnAdLoadFailed;
interstitialAd.OnAdDisplayed -= OnAdDisplayed;
interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed;
interstitialAd.OnAdClicked -= OnAdClicked;
interstitialAd.OnAdClosed -= OnAdClosed;
interstitialAd.OnAdInfoChanged -= OnAdInfoChanged;
// Destroy the ad to free its native resources
interstitialAd.DestroyAd();
}
}
```
## Common Issues
### Issue: Error 1037 (Load during load)
**Cause:** Calling `LoadAd()` repeatedly in `Update()` or other high-frequency loops before the previous load completes.
**Common mistake:**
```csharp
void Update()
{
// WRONG - This will cause error 1037!
if (!interstitialAd.IsAdReady())
{
interstitialAd.LoadAd();
}
}
```
**Why this fails:** If the ad is still loading, calling `LoadAd()` again triggers error 1037: "Cannot load an interstitial while another interstitial is loading."
**Solution:** Track load state manually and only call `LoadAd()` once:
```csharp
private bool isLoadingAd = false;
void Start()
{
LoadInterstitialAd();
}
void LoadInterstitialAd()
{
if (isLoadingAd) return; // Prevent duplicate loads
isLoadingAd = true;
interstitialAd.LoadAd();
}
void OnAdLoaded(LevelPlayAdInfo adInfo)
{
isLoadingAd = false;
}
void OnAdLoadFailed(LevelPlayAdError error)
{
isLoadingAd = false;
// Optionally retry after delay
}
```
### Issue: Interstitial showing too frequently
**Solution:** Implement frequency capping based on time or session count. Track when the last ad was shown and enforce a minimum interval.
### Issue: User flow blocked by ad loading
**Solution:** Never wait for ads to load or show before progressing the user. Load ads proactively and show them opportunistically.
### Issue: Ad not loading
**Possible causes:**
- SDK not initialized before creating ad object
- No internet connection
- Ad inventory not available
- Incorrect ad unit ID
**Solutions:**
- Create ad object only after `OnInitSuccess` callback
- Check network connectivity
- Verify ad unit ID in LevelPlay dashboard
- Test on real device with good connection
### Issue: Ad loading but not showing
**Possible causes:**
- Not checking `IsAdReady()` before calling `ShowAd()`
- Ad expired (loaded too long ago)
**Solutions:**
- Always check `IsAdReady()` before showing
- Load ads close to when they'll be shown (within a few minutes)
## Testing Checklist
- [ ] Ad loads successfully after SDK initialization
- [ ] `IsAdReady()` returns true when ad is loaded
- [ ] Ad displays correctly when `ShowAd()` is called
- [ ] Ad shows at appropriate transition points only
- [ ] Frequency capping works as expected
- [ ] User can progress regardless of ad status
- [ ] Ad reloads automatically after being shown
- [ ] Retry logic works when ad fails to load
- [ ] Multiple placements track correctly (if using)
- [ ] Placement capping works (if configured)
- [ ] Memory leaks prevented (events unsubscribed)
- [ ] Tested on multiple devices and network conditions
- [ ] Ad doesn't interrupt active gameplay
references/ios-setup.md›
# iOS-Specific Setup for LevelPlay
## Contents
- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [SKAdNetwork Configuration](#skadnetwork-configuration)
- [App Tracking Transparency (ATT)](#app-tracking-transparency-att)
- [Privacy Manifest](#privacy-manifest)
- [Xcode Build Settings](#xcode-build-settings)
- [Testing on iOS](#testing-on-ios)
- [Common iOS Issues](#common-ios-issues)
- [iOS Privacy Checklist](#ios-privacy-checklist)
- [Version-Specific Notes](#version-specific-notes)
- [Best Practices](#best-practices)
## Overview
LevelPlay SDK on iOS requires additional configuration beyond the basic Unity integration. This guide covers iOS-specific setup, including SKAdNetwork, App Tracking Transparency (ATT), and privacy manifest requirements.
## Prerequisites
For the current supported Unity, iOS deployment target, and Xcode versions, see the [LevelPlay iOS SDK integration guide](https://docs.unity.com/en-us/grow/levelplay/sdk/ios/sdk-integration).
## SKAdNetwork Configuration
### What is SKAdNetwork?
SKAdNetwork is Apple's privacy-preserving ad attribution framework. It allows advertisers to measure campaign effectiveness without accessing user-level data.
### Setup Steps
LevelPlay SDK automatically includes the required SKAdNetwork IDs in your app's Info.plist during the build process. However, you should verify this after building:
1. Build your Unity project for iOS
2. Open the generated Xcode project
3. Check `Info.plist` for the `SKAdNetworkItems` array
4. Verify it contains multiple SKAdNetwork IDs (format: `XXXX1234abc.skadnetwork`)
**If SKAdNetwork IDs are missing:**
The LevelPlay Unity Package should handle this automatically. If you notice missing IDs:
- Ensure you're using the latest LevelPlay Unity Package version
- Check the Unity console for any import warnings
- Manually add the SKAdNetwork IDs if necessary (see Unity documentation)
### Verifying SKAdNetwork Setup
After building to Xcode, you should see entries like this in Info.plist:
```xml
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4fzdc2evr5.skadnetwork</string>
</dict>
<!-- Many more entries... -->
</array>
```
## App Tracking Transparency (ATT)
### What is ATT?
Apple requires ATT authorization before your app tracks users or accesses the device's advertising identifier on iOS 14.5+. Request ATT authorization before calling `LevelPlay.Init()` — this is both an Apple platform requirement and necessary for personalized ads (which also affects fill rate).
### Implementation
ATT requires two parts: a post-build script that writes the permission description string into `Info.plist`, and a native plugin that performs the actual permission request.
### Part 1: Permission Description (Post-Build Script)
Apple requires a user-facing description explaining why your app requests tracking permission. Add it via a post-build script — this is the reliable approach across all Unity versions. The **User Tracking Usage Description** field in Player Settings is not present in Unity 6.
Create `Assets/Editor/iOSPostBuild.cs`:
```csharp
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
using System.IO;
#endif
public class iOSPostBuild
{
#if UNITY_IOS
[PostProcessBuild(999)]
public static void OnPostProcessBuild(BuildTarget target, string buildPath)
{
if (target != BuildTarget.iOS) return;
var plistPath = Path.Combine(buildPath, "Info.plist");
var plist = new PlistDocument();
plist.ReadFromFile(plistPath);
// Required for ATT prompt — shown to users in the system dialog
plist.root.SetString(
"NSUserTrackingUsageDescription",
"We use tracking to show you relevant ads and support free gameplay."
);
plist.WriteToFile(plistPath);
Debug.Log("iOSPostBuild: NSUserTrackingUsageDescription added to Info.plist");
}
#endif
}
```
**Customise the description** to match your app's purpose. Apple reviews this text and may reject vague descriptions.
### Part 2: Native Plugin for the Permission Request
Create `Assets/Plugins/iOS/ATTRequester.mm`:
```objc
#import <AppTrackingTransparency/AppTrackingTransparency.h>
typedef void (*ATTCallback)(int status);
extern "C"
{
// Returns current ATT status without prompting:
// 0 = Not Determined, 1 = Restricted, 2 = Denied, 3 = Authorized
int _ATT_GetStatus()
{
if (@available(iOS 14, *))
{
return (int)[ATTrackingManager trackingAuthorizationStatus];
}
return 3; // Pre-iOS 14: treat as Authorized
}
// Requests ATT permission and invokes callback with the resulting status.
// On pre-iOS 14 devices, immediately calls back with Authorized (3).
void _ATT_RequestPermission(ATTCallback callback)
{
if (@available(iOS 14, *))
{
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status)
{
dispatch_async(dispatch_get_main_queue(), ^{
if (callback) callback((int)status);
});
}];
}
else
{
if (callback) callback(3);
}
}
}
```
**Note:** `dispatch_async(dispatch_get_main_queue(), ...)` returns the callback to Unity's main thread, which is required for safe IL2CPP interop.
### Part 3: Integrating ATT into LevelPlayInitializer
ATT must complete before `LevelPlay.Init()`. Modify your `LevelPlayInitializer.cs` from Step 7 to use a coroutine `Start()`:
```csharp
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using Unity.Services.LevelPlay;
public class LevelPlayInitializer : MonoBehaviour
{
[SerializeField] private string appKey;
#if UNITY_IOS && !UNITY_EDITOR
private delegate void ATTCallbackDelegate(int status);
[DllImport("__Internal")] private static extern int _ATT_GetStatus();
[DllImport("__Internal")] private static extern void _ATT_RequestPermission(ATTCallbackDelegate callback);
private const int ATT_NOT_DETERMINED = 0;
private static bool _attDone = false;
[AOT.MonoPInvokeCallback(typeof(ATTCallbackDelegate))]
private static void OnATTCallback(int status)
{
Debug.Log($"ATT: user responded — status {status}");
_attDone = true;
}
private IEnumerator RequestATT()
{
// Skip prompt if user has already responded (e.g. returning user)
if (_ATT_GetStatus() != ATT_NOT_DETERMINED)
{
Debug.Log($"ATT: already determined (status {_ATT_GetStatus()})");
yield break;
}
_attDone = false;
_ATT_RequestPermission(OnATTCallback);
yield return new WaitUntil(() => _attDone);
}
#endif
void Awake()
{
DontDestroyOnLoad(gameObject);
}
IEnumerator Start()
{
#if UNITY_IOS && !UNITY_EDITOR
// Request ATT before initializing LevelPlay to maximise ad fill rate
yield return RequestATT();
#else
yield return null; // Editor / Android: skip ATT
#endif
InitializeLevelPlay();
}
private void InitializeLevelPlay()
{
// Add privacy settings here before Init (see Step 6.5)
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey);
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay SDK initialized successfully");
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
}
void OnDestroy()
{
LevelPlay.OnInitSuccess -= OnInitSuccess;
LevelPlay.OnInitFailed -= OnInitFailed;
}
}
```
**Key points:**
- `[AOT.MonoPInvokeCallback]` is required for IL2CPP builds — without it the native callback will crash on device
- `[DllImport("__Internal")]` binds to the native plugin functions
- `_ATT_GetStatus()` prevents re-prompting users who have already responded
- `IEnumerator Start()` with `WaitUntil(() => _attDone)` pauses until the user responds to the ATT dialog
- `#if UNITY_IOS && !UNITY_EDITOR` guards ensure ATT code only compiles for iOS device builds
## Privacy Manifest
### iOS 17+ Requirements
Apple requires apps to declare data types collected and the reasons for using certain APIs. LevelPlay SDK includes a privacy manifest, but you should be aware of what it declares.
### What LevelPlay Declares
The LevelPlay SDK's privacy manifest typically declares:
- **Data Collection**: Device identifiers (IDFA), usage data, location (if used)
- **Required Reason APIs**: User defaults, file timestamp, system boot time, disk space
- **Tracking**: Yes (for personalized advertising)
### Your App's Privacy Manifest
Ensure your app's privacy manifest (if you have one) aligns with LevelPlay's declarations:
1. Check for `PrivacyInfo.xcprivacy` in your Xcode project
2. Verify it includes LevelPlay's data types
3. Update your App Store privacy declarations accordingly
## Xcode Build Settings
### Required iOS Capabilities
LevelPlay SDK requires certain capabilities to function properly:
1. Open your Xcode project after building from Unity
2. Select your target > **Signing & Capabilities**
3. Verify these frameworks are included (LevelPlay should add them automatically):
- **AdSupport.framework**: For IDFA access
- **AppTrackingTransparency.framework**: For ATT (iOS 14+)
- **StoreKit.framework**: For SKAdNetwork
### App Transport Security (ATS) Configuration
To ensure ads load correctly, configure App Transport Security in your Info.plist:
**Option 1: Allow arbitrary loads (easiest, less secure)**
Add this to your Info.plist:
```xml
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
```
**Option 2: Allow specific domains (more secure)**
If you prefer to only allow specific ad network domains:
```xml
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>ironsrc.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<!-- Add other ad network domains as needed -->
</dict>
</dict>
```
**Note:** Most ad networks require HTTP access for legacy ad creatives. Without proper ATS configuration, some ads may fail to load.
**When to configure:** Before building for iOS. This can be done in Unity's PostProcessBuild or manually in Xcode after export.
### Recommended Xcode Build Settings
In Xcode, verify these settings:
- **Deployment Target**: see the [LevelPlay iOS SDK integration guide](https://docs.unity.com/en-us/grow/levelplay/sdk/ios/sdk-integration) for the currently supported minimum
- **Enable Bitcode**: No
- **Other Linker Flags**: Should include `-ObjC` (automatically added by Unity)
## Testing on iOS
### Test Device Setup
1. Build and run on a physical iOS device (simulators have limitations)
2. For ATT testing:
- Go to **Settings > Privacy & Security > Tracking**
- You can reset ATT prompt status by going to **Settings > General > Transfer or Reset iPhone > Reset > Reset Location & Privacy**
### Integration Validation
After building to iOS:
```csharp
void Start()
{
#if UNITY_IOS
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init("YOUR_APP_KEY");
#endif
}
void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay initialized successfully on iOS");
}
void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay init failed on iOS: {error.ErrorMessage}");
}
```
Check the Xcode console for initialization output. Look for:
- ✅ "LevelPlay initialized successfully on iOS"
- ❌ Any initialization errors or warnings
### Testing Ads on iOS
1. **Test mode**: Enable test mode in your LevelPlay dashboard
2. **Real ads**: Disable test mode and test with real ads (use low-value test account)
3. **Different ATT states**: Test with:
- ATT granted (tracking allowed)
- ATT denied (tracking disallowed)
- ATT restricted (device-level restriction)
## Common iOS Issues
### Issue: ATT prompt not showing
**Causes:**
- Missing `NSUserTrackingUsageDescription` in Info.plist
- ATT already determined on device
- Testing on iOS Simulator (use real device)
**Solutions:**
- Verify `iOSPostBuild.cs` exists in `Assets/Editor/` and ran successfully (check console for "iOSPostBuild: NSUserTrackingUsageDescription added to Info.plist")
- Reset privacy settings on device: **Settings > General > Transfer or Reset iPhone > Reset > Reset Location & Privacy**
- Test on physical iOS device
### Issue: Ads not loading after ATT denial
**This is expected behavior:**
- When users deny tracking, personalized ads are limited
- Non-personalized ads may still serve
- Revenue may be lower for users who deny tracking
**What to do:**
- Continue with LevelPlay initialization regardless of ATT response
- Don't block features if tracking is denied
- Comply with Apple's guidelines
### Issue: SKAdNetwork IDs missing
**Causes:**
- Outdated LevelPlay package
- Custom build postprocessor conflict
- Manual Info.plist modifications
**Solutions:**
- Update to latest LevelPlay Unity Package
- Check for conflicting build scripts
- Let LevelPlay handle SKAdNetwork IDs automatically
### Issue: App rejected for privacy reasons
**Common causes:**
- Missing privacy manifest declarations
- Incorrect ATT usage description
- Data collection not properly declared
**Solutions:**
- Review Apple's App Privacy Details guidelines
- Ensure ATT description is clear and user-friendly
- Declare all data types collected in App Store Connect
- Keep LevelPlay SDK updated (includes privacy manifest updates)
## iOS Privacy Checklist
Before submitting to App Store:
- [ ] ATT prompt implemented: `ATTRequester.mm` in `Assets/Plugins/iOS/`, `iOSPostBuild.cs` in `Assets/Editor/`, `LevelPlayInitializer.cs` updated to `IEnumerator Start()`
- [ ] SKAdNetwork IDs included in Info.plist
- [ ] Privacy manifest present and accurate
- [ ] App Store privacy declarations filled out
- [ ] Data collection disclosed transparently
- [ ] Tested on real iOS devices
- [ ] Verified ads load and show correctly
- [ ] Tested both ATT granted and denied scenarios
- [ ] No tracking of users who denied ATT
- [ ] Compliant with Apple's advertising policies
## Additional Resources
### Apple Documentation
- [App Tracking Transparency Guide](https://developer.apple.com/documentation/apptrackingtransparency)
- [SKAdNetwork Documentation](https://developer.apple.com/documentation/storekit/skadnetwork)
- [User Privacy and Data Use](https://developer.apple.com/app-store/user-privacy-and-data-use/)
### LevelPlay Documentation
- Refer to LevelPlay's Unity documentation for the latest iOS setup requirements
- Check for SDK update notes regarding iOS changes
- Review LevelPlay's privacy manifest documentation
## Version-Specific Notes
### iOS 14.5+
- ATT required for IDFA access
- Personalized ads limited without tracking permission
### iOS 15+
- Custom product pages support
- App privacy report visible to users
### iOS 16+
- Additional privacy enhancements
- More granular location privacy
### iOS 17+
- Privacy manifest required
- Increased API usage disclosure requirements
- SDK transparency requirements
## Best Practices
1. **Request ATT at an appropriate time**: Not immediately on first launch, but before showing ads
2. **Explain value to users**: Help users understand why you're requesting tracking
3. **Respect user choice**: Never block app functionality if tracking is denied
4. **Keep SDK updated**: Apple requirements change frequently
5. **Test thoroughly**: Test on real devices with different iOS versions
6. **Monitor metrics**: Track opt-in rates and ad performance post-iOS 14.5
7. **Stay compliant**: Follow Apple's Human Interface Guidelines for ATT
8. **Prepare for rejections**: Have documentation ready if Apple requests clarification
references/migration-sdk-9.md›
# LevelPlay SDK 9.x.x Migration Guide
## Contents
- [Overview](#overview)
- [Scenario A: Upgrade SDK to 9.x.x](#scenario-a-upgrade-sdk-to-9xx)
- [Scenario B: Migrate Init API (IronSource → LevelPlay)](#scenario-b-migrate-init-api)
- [Scenario C: Migrate Ad Unit APIs](#scenario-c-migrate-ad-unit-apis)
- [C1: Rewarded](#c1-rewarded-ads) · [C2: Interstitial](#c2-interstitial-ads) · [C3: Banner](#c3-banner-ads) · [C4: ILRD Handler](#c4-impression-data-ilrd-handler) · [C5: Completeness Checklist](#c5-migration-completeness-checklist)
- [Scenario D: Maven Central Migration](#scenario-d-maven-central-migration)
- [Scenario E: Migrate from Unity Ads to LevelPlay](#scenario-e-migrate-from-unity-ads-to-levelplay)
- [API Reference: Legacy vs. New](#api-reference-legacy-vs-new)
---
## Overview
SDK 9.0.0 introduced breaking changes: the new `LevelPlay.Init()` API replaces `IronSource.Agent.init()`, and ad unit IDs (Ad Unit API) replace the older placement-based IronSource APIs for rewarded, interstitial, and banner ads.
**Minimum SDK version for new Ad Unit APIs**: 8.4.0 (required in 9.0.0+)
---
## Scenario A: Upgrade SDK to 9.x.x
**Before touching anything: inventory, confirm, then delete.** The steps below delete folders, which wipes settings and installed adapters. Complete these in order:
1. **Record Developer Settings values** (app keys, app IDs) from **Ads Mediation > Developer Settings** — they are lost during upgrade. Ask the user to save them and wait for their confirmation before proceeding.
2. **Record the installed network adapters** (check **Ads Mediation > Network Manager**, or list the `IS*AdapterDependencies.xml` files under `Assets > LevelPlay > Editor`) so the same set can be reinstalled after the upgrade.
3. **List exactly what will be deleted and why** (deleting Mobile Dependency Resolver is safe — the new package re-ships it), and get the user's explicit confirmation before deleting anything. Never delete folders unannounced.
### A1: Upgrading via .unitypackage
1. Delete `Assets > LevelPlay` folder and all contents
2. Delete `Assets > Mobile Dependency Resolver` if present
3. Download the [latest Unity Plugin](https://docs.unity.com/en-us/grow/levelplay/sdk/unity/package-integration)
4. Import the .unitypackage file
5. Continue with **Post-Upgrade Verification** below — adapter reinstall and settings re-entry happen there
### A2: Upgrading via UPM (Ads Mediation package)
1. Delete `Assets > LevelPlay` or `Assets > IronSource` folder and all contents
2. Delete `Assets > Mobile Dependency Resolver` if present
3. Open **Window > Package Manager**
4. Select **Unity Registry** → find **Ads Mediation** → click **Update**
5. Continue with **Post-Upgrade Verification** below — adapter reinstall and settings re-entry happen there
### A3: Switching from .unitypackage to UPM
1. Delete `Assets > LevelPlay` or `Assets > IronSource` folder and all contents
2. Delete `Assets > Mobile Dependency Resolver` if present
3. Open **Window > Package Manager**
4. Select **Unity Registry** → find **Ads Mediation** → click **Install**
5. Continue with **Post-Upgrade Verification** below — adapter reinstall and settings re-entry happen there
**Note on package file edits (A2/A3):** when changing package versions by editing files, modify only `Packages/manifest.json`. Never hand-edit `Packages/packages-lock.json` — Unity regenerates it from the manifest, and a corrupted lock file can break the Editor.
### Post-Upgrade Verification
After upgrading:
1. Reinstall the recorded network adapters via **Ads Mediation > Network Manager** and re-enter the recorded Developer Settings values. Do not repeat the backup warning at this point — it is only useful before deletion.
2. Only if the project switched from .unitypackage to UPM (A3): remove the `LEVELPLAY_DEPENDENCIES_INSTALLED` entry from **Project Settings > Player > Scripting Define Symbols**. Both distributions use this define, so do NOT remove it in any other situation. Removing it once after the switch is a safe reset: the stale flag from the old install would otherwise skip the UPM package's dependency verification, and the UPM installer re-verifies and re-adds the define automatically.
3. Check for deprecated API warnings in the Unity console. Then:
- Migrate Init API → see [Scenario B](#scenario-b-migrate-init-api)
- Migrate ad unit code → see [Scenario C](#scenario-c-migrate-ad-unit-apis)
- Maven Central (Scenario D) applies ONLY if the project previously used a .unitypackage older than 7.9.0, or its dependency XMLs under `Assets/LevelPlay/Editor` still reference `android-sdk.is.com`. Fresh UPM installs never need it — if it does not apply, do not bring it up.
---
## Scenario B: Migrate Init API
Replaces the legacy `IronSource.Agent.init()` call with the new `LevelPlay.Init()` API.
### Namespace Change
```csharp
// Old (8.x): the classic IronSource classes (IronSource.Agent, IronSourceEvents, ...)
// live in the GLOBAL namespace — no using directive needed or possible for them.
// The 8.x LevelPlay ad-unit classes (LevelPlayRewardedAd, ...) live in:
using com.unity3d.mediation;
// New
using Unity.Services.LevelPlay;
```
### Initialization Code
**Old (deprecated):**
```csharp
IronSourceEvents.onSdkInitializationCompletedEvent += OnInitSuccess;
IronSource.Agent.setUserId("userId");
IronSource.Agent.validateIntegration();
IronSource.Agent.init(appKey);
```
**New:**
```csharp
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey); // basic
LevelPlay.Init(appKey, "userId"); // with optional user ID
```
### Callback Signatures
| Old | New |
|-----|-----|
| `void onSdkInitializationCompletedEvent()` | `void OnInitSuccess(LevelPlayConfiguration config)` |
| *(no failure callback)* | `void OnInitFailed(LevelPlayInitError error)` |
### Complete Migration Example
**Before:**
```csharp
void Start()
{
IronSourceEvents.onSdkInitializationCompletedEvent += SdkInitializationCompleted;
IronSource.Agent.setUserId("user_123");
IronSource.Agent.init(appKey);
}
void SdkInitializationCompleted()
{
Debug.Log("IronSource initialized");
// load ads here
}
```
**After:**
```csharp
void Start()
{
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey, "user_123");
}
void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay initialized");
// create and load ad objects here
}
void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"Init failed: {error.ErrorMessage}");
}
void OnDestroy()
{
LevelPlay.OnInitSuccess -= OnInitSuccess;
LevelPlay.OnInitFailed -= OnInitFailed;
}
```
---
## Scenario C: Migrate Ad Unit APIs
The new Ad Unit API uses per-instance objects (`LevelPlayRewardedAd`, `LevelPlayInterstitialAd`, `LevelPlayBannerAd`) with Ad Unit IDs from the LevelPlay dashboard. The old `IronSource.Agent.*` static methods are deprecated.
**Get Ad Unit IDs**: LevelPlay dashboard → **Setup > Ad Units**
### C1: Rewarded Ads
**Old (deprecated):**
```csharp
// Register listeners via interface
IronSourceRewardedVideoEvents.onAdAvailableEvent += OnAdAvailable;
IronSourceRewardedVideoEvents.onAdUnavailableEvent += OnAdUnavailable;
IronSourceRewardedVideoEvents.onAdOpenedEvent += OnAdOpened;
IronSourceRewardedVideoEvents.onAdClosedEvent += OnAdClosed;
IronSourceRewardedVideoEvents.onAdRewardedEvent += OnAdRewarded;
IronSourceRewardedVideoEvents.onAdShowFailedEvent += OnAdShowFailed;
IronSourceRewardedVideoEvents.onAdClickedEvent += OnAdClicked;
// Load / Show
if (IronSource.Agent.isRewardedVideoAvailable())
IronSource.Agent.showRewardedVideo();
```
**New:**
```csharp
private LevelPlayRewardedAd rewardedAd;
// Create after OnInitSuccess
rewardedAd = new LevelPlayRewardedAd(adUnitId);
rewardedAd.OnAdLoaded += OnAdLoaded;
rewardedAd.OnAdLoadFailed += OnAdLoadFailed;
rewardedAd.OnAdDisplayed += OnAdDisplayed;
rewardedAd.OnAdDisplayFailed += OnAdDisplayFailed;
rewardedAd.OnAdRewarded += OnAdRewarded;
rewardedAd.OnAdClosed += OnAdClosed;
rewardedAd.OnAdClicked += OnAdClicked; // optional
rewardedAd.OnAdInfoChanged += OnAdInfoChanged; // optional
// Do NOT call rewardedAd.LoadAd() here automatically.
// Unlike the legacy IronSource rewarded video (where the SDK managed loading internally),
// the MADU API requires an explicit LoadAd() call. Add a dedicated load trigger — for
// example a UI button, a scene entry method, or a gameplay event — and call LoadAd() from
// there. If the legacy code had auto-load-on-init behavior, replace it with an explicit
// trigger rather than carrying it forward.
//
// Example explicit trigger:
// public void OnPlayerReachesRewardedOpportunity() { rewardedAd.LoadAd(); }
//
// If the game needs an ad preloaded at all times, calling LoadAd() immediately after
// subscribing events (and again in OnAdClosed) is valid — but treat it as a conscious
// choice, not a default carryover from legacy behavior.
void ShowRewardedAd(string placementName = null)
{
// Check IsAdReady() before showing. If the game uses dashboard placements, also
// check IsPlacementCapped(placementName) — showing a capped placement fails.
if (rewardedAd.IsAdReady() && !LevelPlayRewardedAd.IsPlacementCapped(placementName))
rewardedAd.ShowAd(placementName);
}
```
**Rewarded event + reward handling:**
```csharp
void OnAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
{
// Old: placement.GetRewardName(), placement.GetRewardAmount()
// New:
Debug.Log($"Reward: {reward.Name} x {reward.Amount}");
GrantReward(reward.Name, reward.Amount);
}
// OnAdDisplayFailed — note the correct parameter types: LevelPlayAdInfo + LevelPlayAdError.
// Using LevelPlayAdDisplayInfoError (which existed in 8.x but was removed in 9.x) will cause a CS0246 compile error.
void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
{
Debug.LogError($"Rewarded ad failed to display: {error.ErrorMessage}");
}
```
**Key API mapping:**
| Old | New |
|-----|-----|
| `IronSource.Agent.loadRewardedVideo()` | `rewardedAd.LoadAd()` |
| `IronSource.Agent.showRewardedVideo()` | `rewardedAd.ShowAd()` |
| `IronSource.Agent.isRewardedVideoAvailable()` | `rewardedAd.IsAdReady()` |
| `IronSource.Agent.isRewardedVideoPlacementCapped(name)` | `LevelPlayRewardedAd.IsPlacementCapped(name)` |
| `placement.GetRewardName()` | `reward.Name` |
| `placement.GetRewardAmount()` | `reward.Amount` |
---
### C2: Interstitial Ads
**Old (deprecated):**
```csharp
IronSourceInterstitialEvents.onAdReadyEvent += OnAdReady;
IronSourceInterstitialEvents.onAdLoadFailedEvent += OnAdLoadFailed;
IronSourceInterstitialEvents.onAdOpenedEvent += OnAdOpened; // → OnAdDisplayed
IronSourceInterstitialEvents.onAdClosedEvent += OnAdClosed;
IronSourceInterstitialEvents.onAdShowFailedEvent += OnAdShowFailed; // → OnAdDisplayFailed
IronSourceInterstitialEvents.onAdClickedEvent += OnAdClicked;
IronSource.Agent.loadInterstitial();
if (IronSource.Agent.isInterstitialReady())
IronSource.Agent.showInterstitial();
```
**New:**
```csharp
private LevelPlayInterstitialAd interstitialAd;
// Create after OnInitSuccess
interstitialAd = new LevelPlayInterstitialAd(adUnitId);
interstitialAd.OnAdLoaded += OnAdLoaded;
interstitialAd.OnAdLoadFailed += OnAdLoadFailed;
interstitialAd.OnAdDisplayed += OnAdDisplayed;
interstitialAd.OnAdDisplayFailed += OnAdDisplayFailed;
interstitialAd.OnAdClicked += OnAdClicked;
interstitialAd.OnAdClosed += OnAdClosed;
interstitialAd.OnAdInfoChanged += OnAdInfoChanged;
interstitialAd.LoadAd();
void ShowInterstitialAd(string placementName = null)
{
// Check IsAdReady() before showing. If the game uses dashboard placements, also
// check IsPlacementCapped(placementName) — showing a capped placement fails.
if (interstitialAd.IsAdReady() && !LevelPlayInterstitialAd.IsPlacementCapped(placementName))
interstitialAd.ShowAd(placementName);
}
void OnDestroy()
{
interstitialAd?.DestroyAd();
}
```
**Key API mapping:**
| Old | New |
|-----|-----|
| `IronSource.Agent.loadInterstitial()` | `interstitialAd.LoadAd()` |
| `IronSource.Agent.showInterstitial()` | `interstitialAd.ShowAd()` |
| `IronSource.Agent.isInterstitialReady()` | `interstitialAd.IsAdReady()` |
| `IronSource.Agent.isInterstitialPlacementCapped(name)` | `LevelPlayInterstitialAd.IsPlacementCapped(name)` |
| `onAdReadyEvent` | `OnAdLoaded` |
| `onAdOpenedEvent` | `OnAdDisplayed` |
| `onAdShowFailedEvent` | `OnAdDisplayFailed` |
| `onAdShowSucceededEvent` | *(removed)* |
---
### C3: Banner Ads
**Old (deprecated):**
```csharp
IronSourceBannerEvents.onAdLoadedEvent += OnAdLoaded;
IronSourceBannerEvents.onAdLoadFailedEvent += OnAdLoadFailed;
IronSourceBannerEvents.onAdClickedEvent += OnAdClicked;
IronSourceBannerEvents.onAdScreenPresentedEvent += OnAdScreenPresented; // → OnAdExpanded
IronSourceBannerEvents.onAdScreenDismissedEvent += OnAdScreenDismissed; // → OnAdCollapsed
IronSource.Agent.loadBanner(IronSourceBannerSize.BANNER, IronSourceBannerPosition.BOTTOM);
IronSource.Agent.destroyBanner();
```
**New:**
```csharp
private LevelPlayBannerAd bannerAd;
// Create after OnInitSuccess.
// Default constructor (1-arg) uses BANNER size and BottomCenter position.
// Use this form when migrating a legacy call that used BANNER + BOTTOM — no need to re-specify defaults.
bannerAd = new LevelPlayBannerAd(adUnitId);
// Use Config.Builder only when specifying non-default size, position, or safe area:
// var config = new LevelPlayBannerAd.Config.Builder()
// .SetSize(LevelPlayAdSize.LARGE)
// .SetPosition(LevelPlayBannerPosition.TopCenter)
// .SetRespectSafeArea(true)
// .Build();
// bannerAd = new LevelPlayBannerAd(adUnitId, config);
bannerAd.OnAdLoaded += OnAdLoaded;
bannerAd.OnAdLoadFailed += OnAdLoadFailed;
bannerAd.OnAdDisplayed += OnAdDisplayed;
bannerAd.OnAdDisplayFailed += OnAdDisplayFailed;
bannerAd.OnAdClicked += OnAdClicked;
bannerAd.OnAdCollapsed += OnAdCollapsed;
bannerAd.OnAdExpanded += OnAdExpanded;
bannerAd.OnAdLeftApplication += OnAdLeftApplication;
bannerAd.LoadAd();
bannerAd.ShowAd();
// HideAd() vs DestroyAd() — these are not equivalent (see note below).
bannerAd.HideAd();
bannerAd.PauseAutoRefresh();
bannerAd.ResumeAutoRefresh();
void OnDestroy()
{
bannerAd?.DestroyAd();
}
```
**HideAd() vs DestroyAd() — choose the right one when migrating `destroyBanner()`:**
The legacy `IronSource.Agent.destroyBanner()` maps to two different new APIs depending on intent:
- `bannerAd.HideAd()` — hides the banner but keeps the instance alive. Use this for a visibility toggle (e.g., hiding during gameplay and re-showing later with `ShowAd()`). No need to recreate the object or reload.
- `bannerAd.DestroyAd()` — tears down the instance entirely. Use this only for permanent lifecycle cleanup (e.g., `OnDestroy()` / `OnDisable()`). To show a banner again after calling `DestroyAd()`, a new `LevelPlayBannerAd` object must be created and `LoadAd()` called again.
When migrating, ask: was the legacy `destroyBanner()` call being used to temporarily hide the banner, or to permanently remove it? If temporarily hiding, use `HideAd()`. If in doubt, prefer `HideAd()` for visibility toggling and reserve `DestroyAd()` for `OnDestroy()`/`OnDisable()`.
**Banner size mapping:**
| Old (`IronSourceBannerSize`) | New (`LevelPlayAdSize`) | Dimensions (dp) |
|------------------------------|-------------------------|-----------------|
| `BANNER` | `LevelPlayAdSize.BANNER` | 320 × 50 |
| `LARGE` | `LevelPlayAdSize.LARGE` | 320 × 90 |
| `RECTANGLE` | `LevelPlayAdSize.MEDIUM_RECTANGLE` | 300 × 250 |
| `SMART` | `LevelPlayAdSize.CreateAdaptiveAdSize()` | Auto-adjusting |
**Adaptive size (recommended):**
```csharp
// The 9.x constructor only accepts (adUnitId, Config) — passing a LevelPlayAdSize
// directly does not compile. Set the size through Config.Builder:
var adaptiveConfig = new LevelPlayBannerAd.Config.Builder()
.SetSize(LevelPlayAdSize.CreateAdaptiveAdSize())
.Build();
bannerAd = new LevelPlayBannerAd(adUnitId, adaptiveConfig);
```
**Key API mapping:**
| Old | New |
|-----|-----|
| `IronSource.Agent.loadBanner(size, pos)` | `bannerAd.LoadAd()` (size/position in constructor) |
| `IronSource.Agent.destroyBanner()` | `bannerAd.HideAd()` (visibility toggle) or `bannerAd.DestroyAd()` (lifecycle teardown) |
| `IronSource.Agent.displayBanner()` | `bannerAd.ShowAd()` |
| `IronSource.Agent.hideBanner()` | `bannerAd.HideAd()` |
| `onAdScreenPresentedEvent` | `OnAdExpanded` |
| `onAdScreenDismissedEvent` | `OnAdCollapsed` |
### C4: Impression Data (ILRD) Handler
Migrate the event subscription first, then the handler.
**Subscription — the event name changes, and the correct new event depends on the SDK version:**
**Old (deprecated):**
```csharp
IronSourceEvents.onImpressionDataReadyEvent += ImpressionDataReadyEvent;
```
**New — SDK 9.4.x and earlier** (single global event, subscribe before `LevelPlay.Init()`):
```csharp
LevelPlay.OnImpressionDataReady += ImpressionDataReadyEvent;
```
**New — SDK 9.5.0+** (per ad instance, subscribe when each ad object is created; the global event still exists but is deprecated and generates a compiler warning):
```csharp
rewardedAd.OnAdImpressionDataReady += ImpressionDataReadyEvent;
interstitialAd.OnAdImpressionDataReady += ImpressionDataReadyEvent;
bannerAd.OnAdImpressionDataReady += ImpressionDataReadyEvent;
```
The new global event has NO "Event" suffix: `LevelPlay.OnImpressionDataReadyEvent` has never existed and will not compile. Beware: the 8.x SDK's own deprecation message on the legacy event misnames the replacement as `LevelPlay.OnImpressionDataReadyEvent` — do not follow that message; the correct member is `LevelPlay.OnImpressionDataReady`. Unsubscribe in `OnDestroy()` using the same event names. For forwarding the data to an analytics platform, see `references/ilrd-api.md`.
**Handler — preserve both log lines and apply the property rename:**
**Old:**
```csharp
void ImpressionDataReadyEvent(IronSourceImpressionData impressionData)
{
Debug.Log("ImpressionDataReadyEvent ToString(): " + impressionData.ToString());
Debug.Log("ImpressionDataReadyEvent allData: " + impressionData.allData);
}
```
**New:**
```csharp
void ImpressionDataReadyEvent(LevelPlayImpressionData impressionData)
{
Debug.Log("ImpressionDataReadyEvent ToString(): " + impressionData.ToString());
// allData renamed to AllData (camelCase to PascalCase)
Debug.Log("ImpressionDataReadyEvent AllData: " + impressionData.AllData);
}
```
**Key property rename:**
| Old | New |
|-----|-----|
| `impressionData.allData` | `impressionData.AllData` |
Both log lines should be preserved. Migrations that keep only the `ToString()` line silently drop the raw impression-data dump, which may break analytics code that reads `allData` directly.
### C5: Migration Completeness Checklist
A faithful line-by-line translation is not enough. Several 9.x requirements have no counterpart line in the legacy code, so translating only what is there will silently miss them. After migrating, verify each item against the new code:
- [ ] **Show paths check `IsAdReady()`, plus `IsPlacementCapped(placementName)` when the game uses dashboard placements** — the legacy code only checked availability. If placements are in use, add the capping check (showing a capped placement fails); if the game does not use placements, note that and move on.
- [ ] **A rewarded load trigger exists and is publisher-controlled** — the legacy SDK auto-loaded rewarded video internally, so legacy code has no load call to translate; the migration must ADD one (for example a Load Rewarded Video button mirroring the interstitial's, or a scene-entry call). Without it the rewarded ad can never become ready. No auto-load in `OnInitSuccess` and no auto-reload in `OnAdClosed` unless the publisher deliberately chooses a preload pattern — when repairing or choosing, ask which the publisher prefers.
- [ ] **Version APIs map one-to-one** — `IronSource.unityVersion()` → `LevelPlay.UnityVersion`, and `IronSource.pluginVersion()` → `LevelPlay.PluginVersion`. They return different values; do not swap or merge them.
- [ ] **Logging is preserved, not expanded** — keep the legacy code's log lines (renamed as needed), but do not add new log statements the legacy code did not have.
- [ ] **ILRD subscription uses the correct event name, and both handler log lines survive** — `onImpressionDataReadyEvent` becomes `LevelPlay.OnImpressionDataReady` on SDK 9.4.x and earlier, or per-instance `OnAdImpressionDataReady` on 9.5.0+; `LevelPlay.OnImpressionDataReadyEvent` does not exist. `allData` is renamed to `AllData` (see C4).
- [ ] **`destroyBanner()` intent resolved** — `HideAd()` for a visibility toggle, `DestroyAd()` only in `OnDestroy()`/`OnDisable()` (see C3).
- [ ] **`IronSource.Agent.onApplicationPause(isPaused)` is removed, not replaced.** The legacy API forwarded Unity's pause/resume to the native SDK; in 9.x the SDK fetches pause/resume from the app lifecycle itself, so there is no equivalent to call. `LevelPlay.OnApplicationPause` does not exist and will not compile. `LevelPlay.SetPauseGame(bool)` is a different feature (pausing the game while an ad is showing) — it is not a substitute. If the Unity `OnApplicationPause` override contained only this call, delete the whole override and tell the user why.
- [ ] **No invented APIs** — every LevelPlay member the migrated code uses should appear in this guide or in the installed package. If unsure whether a member exists, verify before using it; a plausible-sounding name that does not exist costs the user a compile-fix cycle.
---
## Scenario D: Maven Central Migration
LevelPlay SDK Android dependencies moved from `is.com` to Maven Central. This migration was required by June 30, 2025. If your project still references `is.com` repositories, Android builds are currently failing. Follow the steps below to fix it.
### Check If Migration Is Needed
1. Go to `Assets > LevelPlay > Editor`
2. Open any dependency XML (e.g., `IronSourceSDKDependencies.xml`)
3. Look for `https://android-sdk.is.com/` inside `<repository>` tags
**If found** → Manual migration required (see below)
**If not found** → Already migrated, no action needed
### Detect Old Format
```xml
<!-- OLD - needs migration -->
<androidPackage spec="com.ironsource.sdk:mediationsdk:7.9.0">
<repositories>
<repository>https://android-sdk.is.com/</repository>
</repositories>
</androidPackage>
```
### Expected New Format
```xml
<!-- NEW - correct -->
<androidPackage spec="com.unity3d.ads-mediation:mediation-sdk:x.x.x">
</androidPackage>
```
### Migration Steps
**For LevelPlay Package:**
1. Go to `Assets > LevelPlay > Editor`
2. Delete `IronSourceSDKDependencies.xml` and all `IS*AdapterDependencies.xml` files
3. Open **Ads Mediation > Network Manager** → reinstall LevelPlay SDK and all required adapters
4. Verify XML files now use `com.unity3d.ads-mediation` (no `is.com` references)
**For Ad Quality SDK:**
1. Go to `Assets > LevelPlay > Editor`
2. Delete `IronSourceAdQualityDependencies.xml`
3. Download the Maven Central version (Ad Quality 7.24.0+) and place it in `Assets > LevelPlay > Editor`
4. Verify the file references `com.unity3d.ads-mediation:adquality-sdk`
**Minimum versions required:**
- LevelPlay Unity Package: **7.9.0+**
- Ad Quality SDK: **7.19.2+**
---
## Scenario E: Migrate from Unity Ads to LevelPlay
For projects using the `Advertisement Legacy` Unity package.
> **Note**: Since April 1, 2026, direct Unity Ads integration may deliver reduced ad performance. Migrating to LevelPlay is now strongly recommended.
### Step 1: Install LevelPlay Package
1. **Window > Package Manager** → Packages: Unity Registry → search **Ads Mediation** → **Install**
2. If prompted to install Mobile Dependency Resolver, click **Import**
3. Do **not** remove Advertisement Legacy yet — keep both until migration is complete
### Step 2: Configure LevelPlay Dashboard
1. Log in at [platform.ironsrc.com](https://platform.ironsrc.com/partners/identity/login)
2. Add your app → create ad units (Banner, Interstitial, Rewarded)
3. Copy your **App Key** and **Ad Unit IDs**
### Step 3: Replace Initialization Code
**Replace:**
```csharp
Advertisement.Initialize(_gameId, _testMode, this);
void OnInitializationComplete() { /* load ads */ }
void OnInitializationFailed(UnityAdsInitializationError error, string message) { }
```
**With:**
```csharp
using Unity.Services.LevelPlay;
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init("YOUR_APP_KEY"); // use appKey, not gameId; test mode not supported
void OnInitSuccess(LevelPlayConfiguration config)
{
// Create ad objects here
}
void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"Init failed: {error.ErrorMessage}");
}
```
**Test mode:** if the legacy code passed `testMode` to `Advertisement.Initialize`, tell the user explicitly that `LevelPlay.Init()` has no test-mode parameter — do not drop the flag silently. The equivalents are the LevelPlay Test Suite on a device build, or enabling test mode in the LevelPlay dashboard.
### Step 4: Replace Ad Implementation
Use the Ad Unit API examples from [Scenario C](#scenario-c-migrate-ad-unit-apis) for each format.
**Unity Ads → LevelPlay API mapping:**
| Use Case | Unity Ads | LevelPlay |
|----------|-----------|-----------|
| Initialization ID | `gameId` | `appKey` |
| Initialize | `Advertisement.Initialize()` | `LevelPlay.Init()` |
| Init Success | `OnInitializationComplete()` | `LevelPlay.OnInitSuccess` |
| Init Failed | `OnInitializationFailed()` | `LevelPlay.OnInitFailed` |
| Is Initialized | `Advertisement.isInitialized` | Not supported |
| Set MetaData | `Advertisement.SetMetaData()` | `LevelPlay.SetMetaData(key, value)` |
| Banner Load | `Advertisement.Banner.Load()` | `bannerAd.LoadAd()` |
| Banner Show | `Advertisement.Banner.Show()` | `bannerAd.ShowAd()` |
| Banner Hide | `Advertisement.Banner.Hide()` | `bannerAd.HideAd()` |
| Rewarded/Interstitial Load | `Advertisement.Load(placementId, listener)` | `ad.LoadAd()` |
| Rewarded/Interstitial Show | `Advertisement.Show(placementId, listener)` | `ad.ShowAd()` |
| Ad Loaded | `OnUnityAdsAdLoaded()` | `OnAdLoaded` event on instance |
| Ad Failed to Load | `OnUnityAdsFailedToLoad()` | `OnAdLoadFailed` event on instance |
| Ad Show Start | `OnUnityAdsShowStart()` | `OnAdDisplayed` event on instance |
| Ad Show Complete | `OnUnityAdsShowComplete()` | `OnAdClosed` event on instance |
| Ad Show Failed | `OnUnityAdsShowFailure()` | `OnAdDisplayFailed` event on instance |
| Ad Clicked | `OnUnityAdsShowClick()` | `OnAdClicked` event on instance |
| Plugin Version | `Advertisement.version` | `LevelPlay.PluginVersion` |
**Key differences:**
- LevelPlay uses **instance-based** ad objects instead of static methods
- Uses **App Key** instead of Game ID
- Events are subscribed per instance, not via global interfaces
- **No test mode parameter** in `LevelPlay.Init()`
- Banner position/size configured in constructor, not via separate API call
### Step 5: Remove Advertisement Legacy Package
Only after verifying all ads work correctly:
1. **Window > Package Manager** → Packages: In Project → select **Advertisement Legacy** → **Remove**
⚠️ Do not remove until all ad implementations are tested and working.
---
## API Reference: Legacy vs. New
### Initialization
| | Legacy (IronSource) | New (LevelPlay) |
|--|---------------------|-----------------|
| Namespace | global namespace (classic IronSource classes) / `com.unity3d.mediation` (8.x ad-unit classes) | `Unity.Services.LevelPlay` |
| Init method | `IronSource.Agent.init(appKey)` | `LevelPlay.Init(appKey)` |
| User ID | `IronSource.Agent.setUserId(id)` | `LevelPlay.Init(appKey, userId)` |
| Success callback | `onSdkInitializationCompletedEvent` | `LevelPlay.OnInitSuccess` |
| Failure callback | *(none)* | `LevelPlay.OnInitFailed` |
| Validate | `IronSource.Agent.validateIntegration()` | `LevelPlay.ValidateIntegration()` |
| Test Suite | `IronSource.Agent.launchTestSuite()` | `LevelPlay.LaunchTestSuite()` |
| Unity version | `IronSource.unityVersion()` | `LevelPlay.UnityVersion` (not `PluginVersion`) |
| Plugin version | `IronSource.pluginVersion()` | `LevelPlay.PluginVersion` (not `UnityVersion`) |
| App pause notify | `IronSource.Agent.onApplicationPause(isPaused)` | *(removed — handled internally in 9.x; delete the call, see checklist C5)* |
### Rewarded
| | Legacy | New |
|--|--------|-----|
| Load | `IronSource.Agent.loadRewardedVideo()` | `rewardedAd.LoadAd()` |
| Show | `IronSource.Agent.showRewardedVideo()` | `rewardedAd.ShowAd()` |
| Is ready | `IronSource.Agent.isRewardedVideoAvailable()` | `rewardedAd.IsAdReady()` |
| Placement capped | `IronSource.Agent.isRewardedVideoPlacementCapped(name)` | `LevelPlayRewardedAd.IsPlacementCapped(name)` |
### Interstitial
| | Legacy | New |
|--|--------|-----|
| Load | `IronSource.Agent.loadInterstitial()` | `interstitialAd.LoadAd()` |
| Show | `IronSource.Agent.showInterstitial()` | `interstitialAd.ShowAd()` |
| Is ready | `IronSource.Agent.isInterstitialReady()` | `interstitialAd.IsAdReady()` |
| Placement capped | `IronSource.Agent.isInterstitialPlacementCapped(name)` | `LevelPlayInterstitialAd.IsPlacementCapped(name)` |
### Banner
| | Legacy | New |
|--|--------|-----|
| Load | `IronSource.Agent.loadBanner(size, pos)` | `bannerAd.LoadAd()` |
| Destroy | `IronSource.Agent.destroyBanner()` | `bannerAd.HideAd()` (visibility toggle) or `bannerAd.DestroyAd()` (lifecycle teardown) — see C3 |
| Show | `IronSource.Agent.displayBanner()` | `bannerAd.ShowAd()` |
| Hide | `IronSource.Agent.hideBanner()` | `bannerAd.HideAd()` |
### Impression Data (ILRD)
| | Legacy | New |
|--|--------|-----|
| Event | `IronSourceEvents.onImpressionDataReadyEvent` | SDK 9.4.x and earlier: `LevelPlay.OnImpressionDataReady` (before `Init()`) — SDK 9.5.0+: `OnAdImpressionDataReady` on each ad instance |
| Payload type | `IronSourceImpressionData` | `LevelPlayImpressionData` |
| Raw data property | `impressionData.allData` | `impressionData.AllData` |
references/privacy-settings.md›
# Privacy and Regulation Settings
## Contents
- [Overview](#overview)
- [Quick Reference](#quick-reference)
- [GDPR Consent Management](#gdpr-consent-management)
- [CCPA Compliance](#ccpa-compliance)
- [COPPA Compliance](#coppa-compliance)
- [Combined Compliance Example](#combined-compliance-example)
- [Deprecated / Legacy APIs](#deprecated--legacy-apis)
- [Best Practices](#best-practices)
- [Testing Privacy Settings](#testing-privacy-settings)
- [Common Issues](#common-issues)
## Overview
> **Note:** This skill provides technical integration guidance, including for LevelPlay's privacy APIs. It is not legal advice, and it does not determine which laws apply to your app — that depends on your users, your data practices, and your distribution. Consult your own legal counsel, and refer to [Regulation Advanced Settings for Unity](https://docs.unity.com/en-us/grow/levelplay/sdk/unity/regulation-advanced-settings) for the authoritative LevelPlay documentation.
LevelPlay SDK provides APIs to help facilitate privacy regulation requirements including GDPR, CCPA, and COPPA. These settings must be configured before SDK initialization.
**SDK Version Requirement:** SDK 9.4.0+ for current APIs (older deprecated APIs available in SDK 9.3.0 and lower)
## Quick Reference
| Regulation | SDK 9.5.0+ API | SDK 9.4.x API | When to Use |
|------------|---------------|---------------|-------------|
| **GDPR** | `LevelPlayPrivacySettings.SetGDPRConsent(bool)` | `LevelPlayPrivacySettings.SetGDPRConsents(Dictionary)` | EU users, requires explicit consent |
| **CCPA** | `LevelPlayPrivacySettings.SetCCPA(true)` | `LevelPlayPrivacySettings.SetCCPA(true)` | California users opting out of data sale |
| **COPPA** | `LevelPlayPrivacySettings.SetCOPPA(true)` | `LevelPlayPrivacySettings.SetCOPPA(true)` | Apps directed at children under 13 |
## GDPR Consent Management
### What is GDPR?
The General Data Protection Regulation (GDPR) is a European Union privacy law requiring explicit user consent for data collection and processing. Apps serving EU users must implement GDPR consent flows.
### Set GDPR Consent
The GDPR consent API changed between SDK versions. Check your installed SDK version in **Ads Mediation > Network Manager**.
**SDK 9.5.0+ — global consent boolean:**
```csharp
using Unity.Services.LevelPlay;
// true = user has granted consent, false = user has not consented
// Call BEFORE SDK initialization
LevelPlayPrivacySettings.SetGDPRConsent(true);
```
**Parameters:**
- `true`: User has granted consent for data collection across all networks
- `false`: User has denied consent (non-personalized ads only)
**SDK 9.4.x — per-network consent dictionary:**
```csharp
using Unity.Services.LevelPlay;
using System.Collections.Generic;
// Set consent for each installed ad network
// Call BEFORE SDK initialization
LevelPlayPrivacySettings.SetGDPRConsents(new Dictionary<string, bool> {
{ "UnityAds", true },
{ "IronSource", true }
// Add an entry for each network you have installed — see Supported Network Keys below
});
```
**Important:**
- Call **BEFORE** `LevelPlay.Init()`
- `SetGDPRConsent(bool)` (singular) was introduced in SDK 9.5.0. If you are on SDK 9.4.x, use `SetGDPRConsents(Dictionary)` instead.
- `SetGDPRConsents(Dictionary)` is deprecated as of SDK 9.5.0+ — migrate to `SetGDPRConsent(bool)` when you upgrade.
### Supported Network Keys (for SetGDPRConsents)
| Network Key | Ad Network |
|-------------|------------|
| `UnityAds` | Unity Ads |
| `AdMob` | Google AdMob |
| `AppLovin` | AppLovin |
| `APS` | Amazon Publisher Services |
| `BidMachine` | BidMachine |
| `Bigo` | Bigo Ads |
| `Chartboost` | Chartboost |
| `Facebook` | Meta Audience Network |
| `Fyber` | Digital Turbine (Fyber) |
| `HyprMx` | HyprMX |
| `InMobi` | InMobi |
| `Line` | LINE Ads |
| `Mintegral` | Mintegral |
| `MobileFuse` | MobileFuse |
| `Moloco` | Moloco |
| `MyTarget` | myTarget |
| `Ogury` | Ogury |
| `Pangle` | Pangle (TikTok) |
| `PubMatic` | PubMatic |
| `Smaato` | Smaato |
| `SuperAwesome` | SuperAwesome |
| `Verve` | Verve |
| `Voodoo` | Voodoo |
| `Vungle` | Vungle |
| `Yandex` | Yandex Ads |
| `YSO` | YSO |
### Complete GDPR Implementation Example
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class GDPRConsentManager : MonoBehaviour
{
[SerializeField] private string appKey;
void Start()
{
// Check if user is in GDPR region (EU)
if (IsUserInGDPRRegion())
{
// Show consent dialog and get user's answer
ShowConsentDialog();
}
else
{
// Not in GDPR region, initialize normally
InitializeLevelPlay();
}
}
private bool IsUserInGDPRRegion()
{
// Implement your region detection logic
// Options: IP geolocation, device locale, or third-party consent SDK
return false; // Placeholder
}
private void ShowConsentDialog()
{
// Show your GDPR consent UI
// When user makes a choice, call OnConsentReceived()
}
private void OnConsentReceived(bool userConsented)
{
// Store consent for future sessions
PlayerPrefs.SetInt("GDPR_Consent", userConsented ? 1 : 0);
PlayerPrefs.Save();
// Apply consent to LevelPlay (BEFORE Init)
LevelPlayPrivacySettings.SetGDPRConsent(userConsented);
// Initialize SDK
InitializeLevelPlay();
}
private void InitializeLevelPlay()
{
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey);
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay initialized with GDPR consent applied");
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay init failed: {error.ErrorMessage}");
}
}
```
### Updating Consent
If user changes consent preferences after initialization:
```csharp
// User updates consent — apply immediately
LevelPlayPrivacySettings.SetGDPRConsent(false); // User revoked consent
// Note: Changes apply to future ad requests
// Currently loaded ads are not affected
```
---
## CCPA Compliance
### What is CCPA?
The California Consumer Privacy Act (CCPA) gives California residents the right to opt out of the "sale" of their personal information. Apps must provide a "Do Not Sell My Personal Information" option.
### Set CCPA Opt-Out (SDK 9.4.0+)
Indicate that the user has opted out of data sale:
```csharp
using Unity.Services.LevelPlay;
// User has opted out of data sale (call BEFORE SDK initialization)
LevelPlayPrivacySettings.SetCCPA(true);
```
**Parameters:**
- `true`: User has opted out of data sale (restrict data collection)
- `false`: User has not opted out (default behavior)
**When to call:** BEFORE `LevelPlay.Init()`
### Complete CCPA Implementation Example
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class CCPAComplianceManager : MonoBehaviour
{
[SerializeField] private string appKey;
private bool userOptedOut = false;
void Start()
{
// Check if user is in California
if (IsUserInCalifornia())
{
// Load previously saved opt-out preference
userOptedOut = PlayerPrefs.GetInt("CCPA_OptOut", 0) == 1;
// Show "Do Not Sell My Info" option in settings
// If user hasn't made a choice, you may show a prompt
}
// Apply CCPA setting before initialization
if (userOptedOut)
{
LevelPlayPrivacySettings.SetCCPA(true);
}
// Initialize SDK
InitializeLevelPlay();
}
private bool IsUserInCalifornia()
{
// Implement your region detection logic
// Options: IP geolocation, device locale
return false; // Placeholder
}
// Call this when user toggles "Do Not Sell" in settings
public void OnUserToggleCCPAOptOut(bool optOut)
{
userOptedOut = optOut;
// Save preference
PlayerPrefs.SetInt("CCPA_OptOut", optOut ? 1 : 0);
PlayerPrefs.Save();
// Apply to SDK
LevelPlayPrivacySettings.SetCCPA(optOut);
Debug.Log($"CCPA opt-out set to: {optOut}");
}
private void InitializeLevelPlay()
{
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey);
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay initialized with CCPA settings applied");
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay init failed: {error.ErrorMessage}");
}
}
```
---
## COPPA Compliance
### What is COPPA?
The Children's Online Privacy Protection Act (COPPA) is a US federal law protecting children under 13. Apps directed at children must not collect personal information without parental consent.
### Set Child-Directed Treatment (SDK 9.4.0+)
Indicate that your app is directed at children:
```csharp
using Unity.Services.LevelPlay;
// App is directed at children under 13 (call BEFORE SDK initialization)
LevelPlayPrivacySettings.SetCOPPA(true);
```
**Parameters:**
- `true`: App is child-directed, apply COPPA restrictions
- `false`: App is not child-directed (default)
**When to call:** BEFORE `LevelPlay.Init()`
### COPPA Implementation Example
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class COPPACompliantInitializer : MonoBehaviour
{
[SerializeField] private string appKey;
[SerializeField] private bool isChildDirectedApp = true; // Set this based on your app
void Start()
{
// Set COPPA compliance BEFORE initialization
if (isChildDirectedApp)
{
LevelPlayPrivacySettings.SetCOPPA(true);
Debug.Log("COPPA child-directed treatment enabled");
}
// Initialize SDK
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey);
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay initialized with COPPA compliance");
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay init failed: {error.ErrorMessage}");
}
}
```
### Additional COPPA Considerations
If your app is child-directed:
1. **Disable personalized ads**: COPPA requires non-personalized ads only
2. **Google Play Families Policy**: If targeting children on Google Play:
- Set COPPA flag: `LevelPlayPrivacySettings.SetCOPPA(true)`
- Review Google Play Families requirements for child-directed apps
- Test thoroughly before submission
3. **App Store age ratings**: Set appropriate age ratings in both app stores
4. **Privacy policy**: Clearly state data collection practices for children
---
## Combined Compliance Example
If you need to support multiple regulations:
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
using System.Collections.Generic;
public class PrivacyComplianceManager : MonoBehaviour
{
[SerializeField] private string appKey;
[SerializeField] private bool isChildDirectedApp = false;
void Start()
{
// Apply all applicable privacy settings BEFORE initialization
// 1. COPPA (if child-directed app)
if (isChildDirectedApp)
{
LevelPlayPrivacySettings.SetCOPPA(true);
}
// 2. GDPR (if user in EU)
if (IsUserInGDPRRegion())
{
if (HasStoredGDPRConsent())
{
LevelPlayPrivacySettings.SetGDPRConsent(LoadGDPRConsent());
}
else
{
// Show consent dialog first, then initialize
ShowGDPRConsentDialog();
return; // Don't initialize yet
}
}
// 3. CCPA (if user in California)
if (IsUserInCalifornia())
{
bool ccpaOptOut = LoadCCPAOptOut();
if (ccpaOptOut)
{
LevelPlayPrivacySettings.SetCCPA(true);
}
}
// Initialize after applying all privacy settings
InitializeLevelPlay();
}
private bool HasStoredGDPRConsent()
{
return PlayerPrefs.HasKey("GDPR_Consent");
}
private bool LoadGDPRConsent()
{
return PlayerPrefs.GetInt("GDPR_Consent", 0) == 1;
}
private bool LoadCCPAOptOut()
{
return PlayerPrefs.GetInt("CCPA_OptOut", 0) == 1;
}
private bool IsUserInGDPRRegion()
{
// Implement region detection
return false;
}
private bool IsUserInCalifornia()
{
// Implement region detection
return false;
}
private void ShowGDPRConsentDialog()
{
// Show your consent UI, then call OnGDPRConsentReceived()
}
private void OnGDPRConsentReceived(bool userConsented)
{
PlayerPrefs.SetInt("GDPR_Consent", userConsented ? 1 : 0);
PlayerPrefs.Save();
LevelPlayPrivacySettings.SetGDPRConsent(userConsented);
InitializeLevelPlay();
}
private void InitializeLevelPlay()
{
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey);
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay initialized with privacy compliance");
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay init failed: {error.ErrorMessage}");
}
}
```
---
## Deprecated / Legacy APIs
**⚠️ The APIs below are superseded across all supported versions. Use the current `LevelPlayPrivacySettings` APIs shown in the sections above instead.**
> **`SetGDPRConsents(Dictionary)` is NOT in this list.** It is the **current GDPR API on SDK 9.4.x** — not a legacy API. It only becomes `[Obsolete]` on SDK 9.5.0+, where `SetGDPRConsent(bool)` replaces it. See [GDPR Consent Management](#gdpr-consent-management) above for the version-appropriate call. Do not treat the per-network dictionary as deprecated if you are on 9.4.x.
### Legacy GDPR API — LevelPlay.SetConsent (all versions)
```csharp
// DEPRECATED - Do not use. Marked [Obsolete] in the SDK.
LevelPlay.SetConsent(true); // Grants consent for all networks
LevelPlay.SetConsent(false); // Denies consent for all networks
```
**Status:** Marked as `[Obsolete]` in SDK code (all supported versions).
**Migration:** Use `LevelPlayPrivacySettings.SetGDPRConsents(Dictionary)` on SDK 9.4.x, or `LevelPlayPrivacySettings.SetGDPRConsent(bool)` on SDK 9.5.0+.
### Legacy CCPA API
```csharp
// DEPRECATED - Do not use
LevelPlay.SetMetaData("do_not_sell", "true");
LevelPlay.SetMetaData("do_not_sell", "false");
```
**Migration:** Use `LevelPlayPrivacySettings.SetCCPA(true)` instead.
### Legacy COPPA API
```csharp
// DEPRECATED - Do not use
LevelPlay.SetMetaData("is_child_directed", "true");
LevelPlay.SetMetaData("is_child_directed", "false");
```
**Migration:** Use `LevelPlayPrivacySettings.SetCOPPA(true)` instead.
---
## Best Practices
1. **Call before initialization**: All privacy settings must be applied before `LevelPlay.Init()`
2. **Persist user choices**: Store consent preferences and reapply on each app launch
3. **Detect user region**: Use geolocation or device locale to determine applicable regulations
4. **Provide UI controls**: Give users easy access to privacy settings
5. **Respect user choices**: Honor opt-outs and consent denials
6. **Update promptly**: When user changes preferences, apply immediately via privacy APIs
7. **Test thoroughly**: Test your privacy settings in all supported regions
8. **Document clearly**: Include privacy policy links in your app
9. **Use consent management platforms**: Consider third-party CMPs for complex consent requirements
10. **Stay updated**: Privacy regulations evolve, monitor SDK updates for compliance changes
---
## Testing Privacy Settings
### Verify Settings Are Applied
```csharp
void Start()
{
// Set privacy settings
LevelPlayPrivacySettings.SetCOPPA(true);
LevelPlayPrivacySettings.SetGDPRConsent(true); // true = user consented
LevelPlayPrivacySettings.SetCCPA(true);
// Initialize
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.Init(appKey);
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("Privacy settings applied successfully");
// Check console logs from SDK for confirmation
}
```
### Test Scenarios
1. **GDPR - All consents granted**: Set all network consents to `true`, verify ads load
2. **GDPR - All consents denied**: Set all network consents to `false`, verify limited ads
3. **CCPA opt-out**: Set `SetCCPA(true)`, verify data collection restricted
4. **COPPA enabled**: Set `SetCOPPA(true)`, verify child-safe ad delivery
5. **Combined**: Apply multiple regulations, verify all honored
---
## Common Issues
### Issue: Privacy settings not taking effect
**Causes:**
- Privacy APIs called after SDK initialization
- Settings not persisted across app launches
**Solutions:**
- Always call privacy APIs BEFORE `LevelPlay.Init()`
- Store user preferences and reapply on every launch
### Issue: Using deprecated APIs
**Cause:** Following outdated documentation or examples using the legacy `LevelPlay.SetConsent()` or `LevelPlay.SetMetaData("do_not_sell"/"is_child_directed", ...)` privacy calls.
**Solution:** Use the `LevelPlayPrivacySettings` API appropriate to your SDK version — for GDPR, `SetGDPRConsents(Dictionary)` on SDK 9.4.x, or `SetGDPRConsent(bool)` on SDK 9.5.0+. (Note: `SetGDPRConsents(Dictionary)` is current on 9.4.x and only warns as obsolete on 9.5.0+ — it is not a legacy API on 9.4.x.)
---
## Additional Resources
- [LevelPlay Privacy Documentation](https://docs.unity.com/en-us/grow/levelplay)
- [GDPR Overview](https://gdpr.eu/)
- [CCPA Information](https://oag.ca.gov/privacy/ccpa)
- [COPPA Requirements](https://www.ftc.gov/legal-library/browse/rules/childrens-online-privacy-protection-rule-coppa)
- [Google Play Families Policy](https://support.google.com/googleplay/android-developer/answer/9893335)
references/rewarded-api.md›
# Rewarded Ads API Reference
## Contents
- [Overview](#overview)
- [Key Characteristics](#key-characteristics)
- [Implementation Pattern](#implementation-pattern)
- [API Reference](#api-reference)
- [Data Types](#data-types)
- [Best Practices](#best-practices)
- [Common Issues](#common-issues)
- [Testing Checklist](#testing-checklist)
## Overview
Rewarded ads are video ads that users choose to watch in exchange for in-app rewards. They're the highest-earning ad format and provide excellent user experience when implemented correctly.
## Key Characteristics
- **User-initiated**: Users explicitly choose to watch
- **Win-win**: Users get valuable rewards, developers get revenue
- **Best for**: Extra lives, hints, currency, power-ups, skipping wait times
## Implementation Pattern
### Basic Rewarded Ad Implementation
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class RewardedAdManager : MonoBehaviour
{
private LevelPlayRewardedAd rewardedAd;
private string adUnitId = "YOUR_REWARDED_AD_UNIT_ID";
// Optional: assign a handler to be notified when the user earns the reward.
// Callers (e.g. a HintSystem) set this before calling ShowAd().
public System.Action OnRewardGranted;
void Start()
{
// Create the rewarded ad object and register event listeners.
// Do not call LoadAd() here — load is triggered explicitly by a publisher-controlled
// action (e.g. a button press, scene entry, or a point in gameplay where the ad
// should be available). See LoadAd() below.
rewardedAd = new LevelPlayRewardedAd(adUnitId);
// ILRD (SDK 9.5.0+): if impression-revenue tracking is enabled, also subscribe here —
// rewardedAd.OnAdImpressionDataReady += OnImpressionDataReady; // see references/ilrd-api.md
// Register event listeners
rewardedAd.OnAdLoaded += OnAdLoaded;
rewardedAd.OnAdLoadFailed += OnAdLoadFailed;
rewardedAd.OnAdDisplayed += OnAdDisplayed;
rewardedAd.OnAdDisplayFailed += OnAdDisplayFailed;
rewardedAd.OnAdRewarded += OnAdRewarded;
rewardedAd.OnAdClosed += OnAdClosed;
rewardedAd.OnAdClicked += OnAdClicked;
rewardedAd.OnAdInfoChanged += OnAdInfoChanged; // Fires when the loaded ad updates after a new auction result
}
void OnDestroy()
{
// Unregister event listeners
if (rewardedAd != null)
{
rewardedAd.OnAdLoaded -= OnAdLoaded;
rewardedAd.OnAdLoadFailed -= OnAdLoadFailed;
rewardedAd.OnAdDisplayed -= OnAdDisplayed;
rewardedAd.OnAdDisplayFailed -= OnAdDisplayFailed;
rewardedAd.OnAdRewarded -= OnAdRewarded;
rewardedAd.OnAdClosed -= OnAdClosed;
rewardedAd.OnAdClicked -= OnAdClicked;
rewardedAd.OnAdInfoChanged -= OnAdInfoChanged;
// ILRD (9.5.0+): rewardedAd.OnAdImpressionDataReady -= OnImpressionDataReady;
}
}
// Call this from the trigger point in your game where you want the ad to be available —
// for example, from a UI button handler, on entering a scene, or at a natural moment
// in gameplay. Unlike the legacy IronSource rewarded video, the SDK does not load
// automatically; LoadAd() must always be called explicitly.
public void LoadAd()
{
Debug.Log("Loading rewarded ad...");
rewardedAd.LoadAd();
}
public void ShowAd()
{
if (rewardedAd.IsAdReady())
{
Debug.Log("Showing rewarded ad");
rewardedAd.ShowAd();
}
else
{
Debug.LogWarning("Rewarded ad is not ready yet");
}
}
// True once a rewarded ad has finished loading and can be shown.
// Use this to decide whether to offer the ad before calling ShowAd().
public bool IsAdReady() => rewardedAd != null && rewardedAd.IsAdReady();
// Event Callbacks
private void OnAdLoaded(LevelPlayAdInfo adInfo)
{
Debug.Log("Rewarded ad loaded successfully");
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Rewarded ad failed to load: {error.ErrorMessage}");
// Retry loading after a delay
Invoke(nameof(LoadAd), 30f);
}
private void OnAdDisplayed(LevelPlayAdInfo adInfo)
{
Debug.Log("Rewarded ad displayed");
}
private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
{
Debug.LogError($"Rewarded ad failed to display: {error.ErrorMessage}");
// Load a new ad
LoadAd();
}
private void OnAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
{
Debug.Log($"User earned reward: {reward.Amount} {reward.Name}");
// Grant the reward to the user
GrantReward(reward);
// Notify any external listener (e.g. HintSystem) that the reward was earned
OnRewardGranted?.Invoke();
}
private void OnAdClosed(LevelPlayAdInfo adInfo)
{
Debug.Log("Rewarded ad closed");
// LoadAd() is not called here automatically. If your game preloads rewarded ads
// eagerly (so one is always ready), call LoadAd() here. If load is triggered by
// a player action, wait for that action instead.
}
private void OnAdClicked(LevelPlayAdInfo adInfo)
{
Debug.Log("Rewarded ad clicked");
}
private void OnAdInfoChanged(LevelPlayAdInfo adInfo)
{
// Optional: log or update analytics with the latest revenue estimate
Debug.Log($"Rewarded ad info changed - network: {adInfo.AdNetwork}, revenue: ${adInfo.Revenue}");
}
private void GrantReward(LevelPlayReward reward)
{
// Implement your reward logic here
Debug.Log($"Granting {reward.Amount} {reward.Name} to the user");
}
}
```
### Advanced: Multiple Rewarded Ad Placements
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
using System.Collections.Generic;
public class MultiPlacementRewardedAdManager : MonoBehaviour
{
// Single ad unit ID, multiple placements
private string adUnitId = "YOUR_REWARDED_AD_UNIT_ID";
private LevelPlayRewardedAd rewardedAd;
// Track which placement is currently being shown
private string currentPlacement;
void Start()
{
// Create single rewarded ad object
rewardedAd = new LevelPlayRewardedAd(adUnitId);
// Register callbacks
rewardedAd.OnAdLoaded += OnAdLoaded;
rewardedAd.OnAdLoadFailed += OnAdLoadFailed;
rewardedAd.OnAdRewarded += OnAdRewarded;
rewardedAd.OnAdClosed += OnAdClosed;
rewardedAd.OnAdDisplayFailed += OnAdDisplayFailed;
// Load ad
rewardedAd.LoadAd();
}
void OnDestroy()
{
if (rewardedAd != null)
{
rewardedAd.OnAdLoaded -= OnAdLoaded;
rewardedAd.OnAdLoadFailed -= OnAdLoadFailed;
rewardedAd.OnAdRewarded -= OnAdRewarded;
rewardedAd.OnAdClosed -= OnAdClosed;
rewardedAd.OnAdDisplayFailed -= OnAdDisplayFailed;
}
}
// Public methods for different placements
public void ShowAdForHints()
{
ShowAdWithPlacement("hints");
}
public void ShowAdForExtraLives()
{
ShowAdWithPlacement("extra_lives");
}
public void ShowAdForCoins()
{
ShowAdWithPlacement("bonus_coins");
}
private void ShowAdWithPlacement(string placementName)
{
if (rewardedAd.IsAdReady())
{
// Check if placement is capped
if (LevelPlayRewardedAd.IsPlacementCapped(placementName))
{
Debug.LogWarning($"Placement '{placementName}' is capped");
return;
}
currentPlacement = placementName;
Debug.Log($"Showing rewarded ad with placement: {placementName}");
rewardedAd.ShowAd(placementName: placementName);
}
else
{
Debug.LogWarning("Rewarded ad is not ready");
}
}
private void OnAdLoaded(LevelPlayAdInfo adInfo)
{
Debug.Log("Rewarded ad loaded");
}
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Rewarded ad load failed: {error.ErrorMessage}");
Invoke(nameof(LoadAd), 30f);
}
private void OnAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
{
Debug.Log($"User rewarded: {reward.Amount} {reward.Name} for placement: {currentPlacement}");
// Grant placement-specific rewards
switch (currentPlacement)
{
case "hints":
GrantHints(reward);
break;
case "extra_lives":
GrantExtraLives(reward);
break;
case "bonus_coins":
GrantCoins(reward);
break;
}
}
private void OnAdClosed(LevelPlayAdInfo adInfo)
{
Debug.Log("Rewarded ad closed");
currentPlacement = null;
LoadAd();
}
private void OnAdDisplayFailed(LevelPlayAdInfo adInfo, LevelPlayAdError error)
{
Debug.LogError($"Rewarded ad display failed: {error.ErrorMessage}");
currentPlacement = null;
LoadAd();
}
private void LoadAd()
{
if (rewardedAd != null)
{
rewardedAd.LoadAd();
}
}
// Reward granting methods
private void GrantHints(LevelPlayReward reward)
{
Debug.Log("Granting hints to player");
// Your hint logic here
}
private void GrantExtraLives(LevelPlayReward reward)
{
Debug.Log("Granting extra lives to player");
// Your lives logic here
}
private void GrantCoins(LevelPlayReward reward)
{
Debug.Log("Granting bonus coins to player");
// Your coins logic here
}
}
```
## API Reference
### Constructor
#### `new LevelPlayRewardedAd(string adUnitId)`
Create a rewarded ad object.
**Parameters:**
- `adUnitId`: The ad unit identifier from your LevelPlay dashboard
**Returns:** `LevelPlayRewardedAd` object
**Usage:**
```csharp
LevelPlayRewardedAd rewardedAd = new LevelPlayRewardedAd("your_ad_unit_id");
```
**Important:** Call this only after `LevelPlay.Init()` has completed successfully (in the `OnInitSuccess` callback).
#### `new LevelPlayRewardedAd(string adUnitId, Config config)`
Create a rewarded ad object with custom configuration.
**Parameters:**
- `adUnitId`: The ad unit identifier
- `config`: Optional configuration object
**Usage:**
```csharp
var configBuilder = new LevelPlayRewardedAd.Config.Builder();
configBuilder.SetBidFloor(1.0); // Minimum bid price in USD
var config = configBuilder.Build();
LevelPlayRewardedAd rewardedAd = new LevelPlayRewardedAd("your_ad_unit_id", config);
```
### Configuration Builder
#### `LevelPlayRewardedAd.Config.Builder()`
Create a configuration builder for rewarded ads.
**Returns:** `LevelPlayRewardedAd.Config.Builder` object
**Usage:**
```csharp
var configBuilder = new LevelPlayRewardedAd.Config.Builder();
```
**Set minimum bid price:**
```csharp
configBuilder.SetBidFloor(1.0); // Minimum bid price in USD ($1.00 CPM)
```
**When to use:** Set a minimum bid price in USD for ad requests.
#### `Build()`
Build the configuration object.
**Returns:** `LevelPlayRewardedAd.Config` object
**Usage:**
```csharp
var config = configBuilder.Build();
```
### Core Methods
#### `LoadAd()`
Load a rewarded ad.
**Usage:**
```csharp
rewardedAd.LoadAd();
```
**When to call:**
- After SDK initialization completes
- After showing an ad (to load the next one)
- After a load or display failure
#### `ShowAd()`
Show the rewarded ad without a placement name.
**Usage:**
```csharp
if (rewardedAd.IsAdReady())
{
rewardedAd.ShowAd();
}
```
**Important:** Always check `IsAdReady()` before calling `ShowAd()`.
#### `ShowAd(placementName: string)`
Show the rewarded ad with a named placement for analytics tracking.
**Parameters:**
- `placementName`: Placement name configured in LevelPlay dashboard
**Usage:**
```csharp
rewardedAd.ShowAd(placementName: "extra_lives");
```
**When to use:** When tracking multiple placements within the same ad unit for analytics.
#### `GetReward(string placementName = null)`
Get the reward configuration for a placement.
**Parameters:**
- `placementName` (optional): The placement name to query. If null, returns default reward.
**Returns:** `LevelPlayReward` object with Name and Amount
**Usage:**
```csharp
// Get default reward
LevelPlayReward defaultReward = rewardedAd.GetReward();
Debug.Log($"Default reward: {defaultReward.Amount} {defaultReward.Name}");
// Get placement-specific reward
LevelPlayReward placementReward = rewardedAd.GetReward("extra_lives");
Debug.Log($"Extra lives reward: {placementReward.Amount} {placementReward.Name}");
```
**When to use:** To display reward information to users before they watch the ad (e.g., "Watch ad to earn 5 coins").
#### `IsAdReady()`
Check if a rewarded ad is loaded and ready to show.
**Returns:** `bool` - true if ready, false otherwise
**Usage:**
```csharp
if (rewardedAd.IsAdReady())
{
// Ad is ready to show
rewardedAd.ShowAd();
}
else
{
// Ad not ready, maybe disable the button
}
```
**Best practice:** Check this before showing ad UI buttons to users.
#### `LevelPlayRewardedAd.IsPlacementCapped(string placementName)` (Static)
Check if a placement has reached its capping limit.
**Parameters:**
- `placementName`: The placement name to check
**Returns:** `bool` - true if capped, false otherwise
**Usage:**
```csharp
if (LevelPlayRewardedAd.IsPlacementCapped("extra_lives"))
{
Debug.Log("Extra lives placement is capped");
}
```
**When to use:** Before showing an ad to check if the placement frequency cap is reached.
### Events
All events are properties of the `LevelPlayRewardedAd` object.
**Threading:** All ad callbacks run on the Unity main thread, so you can safely call Unity APIs (update UI, access GameObjects, etc.) directly in these callbacks. This is different from `LevelPlay.OnImpressionDataReady` which runs on a background thread.
#### `OnAdLoaded`
Fired when a rewarded ad is successfully loaded.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
rewardedAd.OnAdLoaded += (adInfo) =>
{
Debug.Log("Rewarded ad loaded");
// Enable UI button, etc.
};
```
#### `OnAdLoadFailed`
Fired when a rewarded ad fails to load.
**Signature:** `event Action<LevelPlayAdError>`
**Usage:**
```csharp
rewardedAd.OnAdLoadFailed += (error) =>
{
Debug.LogWarning($"Load failed: {error.ErrorMessage}");
// Retry loading
};
```
**Best practice:** Implement retry logic with exponential backoff.
#### `OnAdDisplayed`
Fired when a rewarded ad is displayed on screen.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
rewardedAd.OnAdDisplayed += (adInfo) =>
{
Debug.Log("Rewarded ad displayed");
// Optional: Pause game
};
```
#### `OnAdDisplayFailed`
Fired when a rewarded ad fails to display.
**Signature:** `event Action<LevelPlayAdInfo, LevelPlayAdError>`
**Usage:**
```csharp
rewardedAd.OnAdDisplayFailed += (adInfo, error) =>
{
Debug.LogError($"Display failed: {error.ErrorMessage}");
// Load a new ad
rewardedAd.LoadAd();
};
```
#### `OnAdRewarded`
Fired when the user has earned the reward.
**Signature:** `event Action<LevelPlayAdInfo, LevelPlayReward>`
**Parameters:**
- `adInfo`: Information about the ad
- `reward`: The reward object containing `Name` and `Amount`
**Usage:**
```csharp
rewardedAd.OnAdRewarded += (adInfo, reward) =>
{
Debug.Log($"User earned: {reward.Amount} {reward.Name}");
// Grant the reward to the user
GrantReward(reward);
};
```
**Critical:** This is where you grant rewards to the user. Don't wait for `OnAdClosed` - users may close before the ad finishes.
#### `OnAdClosed`
Fired when the rewarded ad is closed.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
rewardedAd.OnAdClosed += (adInfo) =>
{
Debug.Log("Rewarded ad closed");
// Resume game. Whether to call LoadAd() here depends on your load strategy:
// call it if you preload eagerly; otherwise wait for the player's next load trigger.
};
```
#### `OnAdClicked`
Fired when the user clicks on the rewarded ad.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
rewardedAd.OnAdClicked += (adInfo) =>
{
Debug.Log("Rewarded ad clicked");
// Optional analytics tracking
};
```
#### `OnAdInfoChanged`
Fired when ad information changes, such as when a new winning ad becomes available after auction.
**Signature:** `event Action<LevelPlayAdInfo>`
**Usage:**
```csharp
rewardedAd.OnAdInfoChanged += (adInfo) =>
{
Debug.Log($"Ad info changed - New ad from: {adInfo.AdNetwork}");
Debug.Log($"Estimated revenue: ${adInfo.Revenue}");
};
```
**Important:** This event indicates that a new ad with potentially different revenue has become available. The SDK automatically selects the winning ad from the auction when you call `ShowAd()`, but this event lets you know when the ad info has been updated. This is particularly important for revenue optimization as it signals when a new auction winner has loaded.
**When it fires:**
- After a new ad loads with different properties than the previous one
- When the waterfall selects a different ad network
- When ad auction results change
**Why it matters:** The updated `LevelPlayAdInfo` contains the latest revenue estimates and network information, which directly impacts your monetization. Always use the most recent `adInfo` when logging or analyzing ad performance.
**If you're using ILRD** (`references/ilrd-api.md`): the `LevelPlayImpressionData` you receive in `OnImpressionDataReady` already contains the final revenue value, so `OnAdInfoChanged` is mostly useful for in-Editor debugging of the waterfall. Most publishers can leave it as a logging hook.
## Data Types
### LevelPlayReward
Represents the reward earned by the user.
**Properties:**
- `Name` (string): The reward name (e.g., "coins", "life")
- `Amount` (int): The reward amount (e.g., 10, 1)
**Usage:**
```csharp
private void OnAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
{
Debug.Log($"Reward: {reward.Amount} {reward.Name}");
if (reward.Name == "coins")
{
playerCoins += reward.Amount;
}
}
```
### LevelPlayAdInfo
Contains information about the ad.
**Properties:**
- `AdId` (string): Unique identifier for this specific ad instance
- `AdUnitId` (string): The ad unit identifier
- `AdUnitName` (string): The ad unit name
- `AdSize` (LevelPlayAdSize): The ad size (may be null; banner-relevant)
- `AdFormat` (string): The ad format (e.g., "REWARDED")
- `PlacementName` (string): Placement name where ad was shown
- `AuctionId` (string): Unique auction identifier
- `CreativeId` (string): Creative identifier
- `Country` (string): User's country code (ISO 3166-1)
- `Ab` (string): A/B test segment identifier
- `SegmentName` (string): User segment name
- `AdNetwork` (string): Ad network that served the ad
- `InstanceName` (string): Ad network instance name
- `InstanceId` (string): Ad network instance identifier
- `Revenue` (double?): Estimated revenue in USD (nullable - check for null)
- `Precision` (string): Revenue precision level
- `EncryptedCPM` (string): Encrypted CPM value
### LevelPlayAdError
Contains error information when ad operations fail.
**Properties:**
- `ErrorCode` (int): Numeric error code
- `ErrorMessage` (string): Human-readable error description
- `AdUnitId` (string): The ad unit identifier where the error occurred
- `AdId` (string): Unique identifier for the specific ad instance
## Best Practices
### Loading Strategy
Unlike the legacy IronSource rewarded video (where the SDK managed loading automatically), the MADU rewarded API requires `LoadAd()` to be called explicitly. The ad object is created in `OnInitSuccess`, but loading is a separate step triggered by the publisher.
**Explicit load pattern (matches official sample):**
```csharp
// In OnInitSuccess: create the object and subscribe events only — do not load yet.
void OnInitSuccess(LevelPlayConfiguration config)
{
rewardedAd = new LevelPlayRewardedAd(adUnitId);
rewardedAd.OnAdLoaded += OnAdLoaded;
rewardedAd.OnAdClosed += OnAdClosed;
// LoadAd() is called later, from a publisher-controlled trigger.
}
// Example trigger: called from a UI button or a scene entry point.
public void OnPlayerReachesRewardedAdOpportunity()
{
rewardedAd.LoadAd();
}
```
**Eager preload pattern (optional):** If the game always needs a rewarded ad ready the moment the player reaches certain screens, call `LoadAd()` immediately in `OnInitSuccess` after creating the object, and optionally again in `OnAdClosed`. This is a valid choice but makes the manual load requirement less visible — use it consciously, not as a default.
```csharp
void OnInitSuccess(LevelPlayConfiguration config)
{
rewardedAd = new LevelPlayRewardedAd(adUnitId);
rewardedAd.OnAdLoaded += OnAdLoaded;
rewardedAd.OnAdClosed += OnAdClosed;
rewardedAd.LoadAd(); // Eager preload — conscious choice, not automatic legacy behavior.
}
void OnAdClosed(LevelPlayAdInfo adInfo)
{
rewardedAd.LoadAd(); // Reload immediately so one is ready for the next opportunity.
}
```
### UI Integration
**Show/hide buttons based on ad availability:**
```csharp
public Button watchAdButton;
void Update()
{
// Enable button only if ad is ready
watchAdButton.interactable = rewardedAd != null && rewardedAd.IsAdReady();
}
void OnAdLoaded(LevelPlayAdInfo adInfo)
{
watchAdButton.interactable = true;
}
void OnAdLoadFailed(LevelPlayAdError error)
{
watchAdButton.interactable = false;
}
```
### Reward Granting
**Grant rewards in `OnAdRewarded`, not `OnAdClosed`:**
```csharp
// ✅ CORRECT
private void OnAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
{
GrantReward(reward); // Grant immediately
}
// ❌ WRONG - user may close ad early
private void OnAdClosed(LevelPlayAdInfo adInfo)
{
// Don't grant reward here!
}
```
**Note:** `OnAdRewarded` and `OnAdClosed` are asynchronous — `OnAdRewarded` may fire after `OnAdClosed`. Do not write logic in `OnAdClosed` that assumes the reward has already been granted.
### Error Handling
**Handle ad load failures:**
```csharp
private void OnAdLoadFailed(LevelPlayAdError error)
{
Debug.LogWarning($"Ad load failed: {error.ErrorMessage}");
// Retry loading after a delay
Invoke(nameof(RetryLoad), 30f);
}
private void RetryLoad()
{
rewardedAd.LoadAd();
}
```
### Multiple Placements
**Use placements to track different reward contexts:**
- Create placements in LevelPlay dashboard (e.g., "hints", "extra_lives")
- Use `ShowAd(placementName: "name")` to track separately
- Check `IsPlacementCapped()` before showing
- Handle rewards differently based on placement in `OnAdRewarded`
### Memory Management
**Always unsubscribe from events:**
```csharp
void OnDestroy()
{
if (rewardedAd != null)
{
rewardedAd.OnAdLoaded -= OnAdLoaded;
rewardedAd.OnAdLoadFailed -= OnAdLoadFailed;
rewardedAd.OnAdDisplayed -= OnAdDisplayed;
rewardedAd.OnAdDisplayFailed -= OnAdDisplayFailed;
rewardedAd.OnAdRewarded -= OnAdRewarded;
rewardedAd.OnAdClosed -= OnAdClosed;
rewardedAd.OnAdClicked -= OnAdClicked;
rewardedAd.OnAdInfoChanged -= OnAdInfoChanged;
}
}
```
## Common Issues
### Issue: Error 1037 (Load during load)
**Cause:** Calling `LoadAd()` repeatedly in `Update()` or other high-frequency loops before the previous load completes.
**Common mistake:**
```csharp
void Update()
{
// WRONG - This will cause error 1037!
if (!rewardedAd.IsAdReady())
{
rewardedAd.LoadAd();
}
}
```
**Why this fails:** If the ad is still loading, calling `LoadAd()` again triggers error 1037: "Cannot load while another load is in progress."
**Solution:** Track load state manually and only call `LoadAd()` once:
```csharp
private bool isLoadingAd = false;
void Start()
{
LoadRewardedAd();
}
void LoadRewardedAd()
{
if (isLoadingAd) return; // Prevent duplicate loads
isLoadingAd = true;
rewardedAd.LoadAd();
}
void OnAdLoaded(LevelPlayAdInfo adInfo)
{
isLoadingAd = false;
}
void OnAdLoadFailed(LevelPlayAdError error)
{
isLoadingAd = false;
// Optionally retry after delay
}
```
### Issue: Reward not granted to user
**Possible causes:**
- Granting reward in `OnAdClosed` instead of `OnAdRewarded`
- User closes ad before completion
- Reward logic has bugs
**Solution:**
Always grant rewards in `OnAdRewarded`, which fires when the user has earned the reward (typically after watching enough of the ad).
### Issue: Ad not loading
**Possible causes:**
- SDK not initialized before creating ad object
- No internet connection
- Ad inventory not available in test region
- Incorrect ad unit ID
**Solutions:**
- Create ad object only after `OnInitSuccess` callback
- Check network connectivity
- Verify ad unit ID in LevelPlay dashboard
- Test on real device with good connection
### Issue: Button always disabled
**Possible causes:**
- Not checking `IsAdReady()` correctly
- Ad failed to load and no retry logic
- Event subscriptions not working
**Solutions:**
- Implement `OnAdLoaded` callback to enable button
- Add retry logic in `OnAdLoadFailed`
- Debug log all callbacks to verify they fire
### Issue: Multiple reward grants
**Possible causes:**
- `OnAdRewarded` callback subscribed multiple times
- Not tracking if reward already granted
**Solutions:**
- Unsubscribe in `OnDestroy()`
- Only subscribe once in initialization
- Use flag to track if reward granted for this ad
## Testing Checklist
- [ ] Ad loads successfully after SDK initialization
- [ ] `IsAdReady()` returns true when ad is loaded
- [ ] Ad displays correctly when `ShowAd()` is called
- [ ] `OnAdRewarded` fires and reward is granted
- [ ] UI button enables when ad is ready
- [ ] UI button disables when ad is not ready
- [ ] Ad reloads automatically after being shown
- [ ] Retry logic works when ad fails to load
- [ ] Multiple placements track correctly (if using)
- [ ] Placement capping works (if configured)
- [ ] Memory leaks prevented (events unsubscribed)
- [ ] Tested on multiple devices and network conditions
references/testing-and-validation.md›
# LevelPlay Testing and Validation
Detailed guidance for the two validation approaches referenced from Step 10 of SKILL.md: mock ads in
the Unity Editor (early development) and the LevelPlay Test Suite (integration validation on device).
Read this when the user is testing their integration, asks how mock ads work, or is setting up the
Test Suite.
## Contents
- [Early Development: Mock Ads in Unity Editor](#early-development-mock-ads-in-unity-editor)
- [Mock Ad Callback Behavior](#mock-ad-callback-behavior)
- [Mock Ads Limitations](#mock-ads-limitations)
- [Integration Validation: LevelPlay Test Suite](#integration-validation-levelplay-test-suite)
- [Test Suite Setup](#test-suite-setup)
- [Test Suite Initializer Template](#test-suite-initializer-template)
- [Testing Workflow](#testing-workflow)
## Early Development: Mock Ads in Unity Editor
For early iteration and callback testing, use **mock ads** in Unity Editor. When you press Play in the
Editor, Unity provides mock ads automatically — but only if the active build target is Android or iOS
(see the requirement below).
> **Required: set the active build target to Android or iOS first.** Mock ads (and the LevelPlay SDK
generally) do **not** work while the active build target is Standalone/PC/Mac — which is the default
for new desktop projects. On a non-mobile target the SDK runs as an "unsupported platform" and returns
no ads, so `Init()` and ad loads will appear to do nothing in the Editor. Switch via **File ▸ Build
Profiles** (called **Build Settings** before Unity 6) → select **Android** or **iOS** → **Switch
Platform**, then press Play. If mock ads never appear in the Editor, this is the most likely cause.
**How it works:**
Once the build target is Android or iOS, pressing Play in the Editor provides mock ads automatically.
Mock ads work with ANY App Key and Ad Unit ID values (including dummy values like "test" or "editor").
However, **recommend using real App Key and real Ad Unit IDs** from the LevelPlay dashboard so you don't
forget to update them before building to device.
**Example setup (works in both Editor and device builds):**
The same initialization code you wrote in Step 7 works in both Editor (with mock ads) and device builds
(with real ads). For example:
```csharp
// Initialize with your actual App Key (replace "abc123..." with yours)
// Mock ads work with any value, but use your real key to avoid forgetting to update it later
LevelPlay.Init("abc123youractualappkey");
```
Example for rewarded ads (works in both Editor and device):
```csharp
// In OnInitSuccess callback:
// Use your real ad unit ID from LevelPlay dashboard (replace "12345..." with yours)
// Mock ads work with any value, but use your real IDs to avoid forgetting to update them later
LevelPlayRewardedAd rewardedAd = new LevelPlayRewardedAd("12345youractualadunitid");
rewardedAd.OnAdLoaded += OnAdLoaded;
rewardedAd.OnAdRewarded += OnAdRewarded;
rewardedAd.LoadAd();
```
**Key points:**
- **Mock ads work with any App Key/Ad Unit ID values** (you can even use "test" or "editor")
- **Recommended: Use your real credentials** from Step 5 to avoid forgetting to update them later
- **Mock ads appear automatically** when testing in Unity Editor
- **Real ads appear automatically** when building to device
- **Same code works everywhere** - no switching or conditional compilation needed
- **Android API 33+**: If targeting Android 13+ devices, verify you've added the AD_ID permission to AndroidManifest.xml (see Step 4)
**What mock ads validate:**
- Ad integration flow works correctly
- Most callbacks fire as expected (see callback behavior below)
- Ad loading, showing, and closing logic
- Ad positioning and layout (for banners)
- Basic ad logic and state management
## Mock Ad Callback Behavior
Mock ads in Unity Editor fire most callbacks, but not all:
**Callbacks that FIRE:**
- `OnAdLoaded` - Always fires after LoadAd()
- `OnAdDisplayed` - Fires when ShowAd() is called
- `OnAdRewarded` - Fires for rewarded ads (with test reward)
- `OnAdClosed` - Fires when mock ad is dismissed
**Callbacks that DON'T fire:**
- `OnAdLoadFailed` - Mock ads always succeed loading
- `OnAdDisplayFailed` - Mock ads always succeed showing
- `OnAdClicked` - Mock ads don't simulate user clicks
- `OnAdExpanded` / `OnAdCollapsed` - Banner expand/collapse not simulated
- `OnAdLeftApplication` - No real ad redirect in Editor
- `OnAdInfoChanged` - Mock ads don't update ad info dynamically
- Impression data events (ILRD — `OnAdImpressionDataReady` on 9.5.0+, or `LevelPlay.OnImpressionDataReady` on 9.4.x and earlier) - No impression data generated in Editor
This means you can test your happy-path flow in Editor, but must test error handling on real devices.
**Note on SDK initialization in the Editor:** `LevelPlay.OnInitSuccess` may not fire in all SDK
configurations when running in the Unity Editor. If your initialization callback doesn't trigger and
your ad objects never load as a result, try creating them directly in `Start()` after calling
`LevelPlay.Init()` rather than waiting for the callback — mock ads will appear even without
`OnInitSuccess` firing.
## Mock Ads Limitations
- Don't simulate network latency or failures
- Don't test real ad network behavior
- Don't validate reward logic server-side
- Placeholder UI instead of real ad creatives
- Error callbacks never fire
**Best for**: Early development, rapid iteration on ad logic, callback testing
## Integration Validation: LevelPlay Test Suite
The **Test Suite** is the primary method for comprehensive validation. It tests your integration with
real ad networks on device.
**What Test Suite validates:**
- All ad formats (Rewarded, Interstitial, Banner) with real ads
- SDK initialization with production App Key
- All callbacks fire correctly in production environment
- Real ad network behavior, latency, and edge cases
- Ad rendering and user interaction flows
**Before running the Test Suite:**
- **Unity Ads is pre-installed** — the Ads Mediation package includes the Unity Ads adapter by default, so you have at least one network ready without any additional setup. For ads to fill on device, verify your LevelPlay dashboard has active instances configured for your ad units.
- **Enable Development Build** in **Build Profiles** (called **Build Settings** in Unity versions before Unity 6) before building to device. Without it, SDK console output won't be visible, making it very difficult to diagnose issues if something doesn't work as expected.
## Test Suite Setup
**Setup (requires device build):**
Add these two lines to your existing `LevelPlayInitializer.cs` — do not create a new file or replace
your existing initializer:
1. Before `LevelPlay.Init(appKey)` — at the top of `Start()`:
```csharp
LevelPlay.SetMetaData("is_test_suite", "enable");
```
2. Inside your `OnInitSuccess` callback:
```csharp
LevelPlay.LaunchTestSuite();
```
> **iOS path (`references/ios-setup.md` Part 3 initializer):** `Start()` is a coroutine and
`LevelPlay.Init(appKey)` lives inside `InitializeLevelPlay()`, not `Start()`. Put
`LevelPlay.SetMetaData("is_test_suite", "enable");` as the **first line inside `InitializeLevelPlay()`,
before `LevelPlay.Init(appKey)`** — do NOT put it in `Start()` and do NOT put it after `Init`.
`LaunchTestSuite()` still goes in `OnInitSuccess`. `SetMetaData` must run before `Init` or the Test
Suite will silently never launch.
**Important**: Remove both lines before your production release. Test Suite should only be used during
development and testing.
## Test Suite Initializer Template
**Don't have a `LevelPlayInitializer.cs` yet?** Use this complete template:
```csharp
using UnityEngine;
using Unity.Services.LevelPlay;
public class LevelPlayInitializer : MonoBehaviour
{
[SerializeField] private string appKey;
void Awake()
{
DontDestroyOnLoad(gameObject);
}
void Start()
{
// Enable Test Suite — REMOVE before production release
LevelPlay.SetMetaData("is_test_suite", "enable");
LevelPlay.OnInitSuccess += OnInitSuccess;
LevelPlay.OnInitFailed += OnInitFailed;
LevelPlay.Init(appKey);
}
private void OnInitSuccess(LevelPlayConfiguration config)
{
Debug.Log("LevelPlay initialized successfully");
// Launch Test Suite — REMOVE before production release
LevelPlay.LaunchTestSuite();
}
private void OnInitFailed(LevelPlayInitError error)
{
Debug.LogError($"LevelPlay initialization failed: {error.ErrorMessage}");
}
void OnDestroy()
{
LevelPlay.OnInitSuccess -= OnInitSuccess;
LevelPlay.OnInitFailed -= OnInitFailed;
}
}
```
**After creating this script:**
1. Attach it to a GameObject in your first scene
2. In the Unity Inspector, find the "App Key" field
3. Paste your App Key from Step 5 into that field
**Key points:**
- `LevelPlay.SetMetaData("is_test_suite", "enable");` must be called **BEFORE** `LevelPlay.Init()` — if the Test Suite doesn't appear after launch, this is the most likely cause
- `LevelPlay.LaunchTestSuite();` is called **AFTER** successful initialization (in `OnInitSuccess`)
- **Requires device build** - Test Suite does not work in Unity Editor
- Use production App Key, not "editor"
- Build to Android or iOS device and run the app
- Test Suite UI will appear automatically after initialization
## Testing Workflow
1. Add Test Suite code (SetMetaData before Init, LaunchTestSuite after OnInitSuccess)
2. Build to Android or iOS device
3. Run the app on device
4. Test Suite UI launches automatically
5. Follow on-screen instructions to test each ad format
6. Verify all ad formats load and callbacks fire correctly
**Best for**: Integration validation before production, final testing with real ad networks
references/troubleshooting.md›
# LevelPlay Common Issues and Solutions
Detailed troubleshooting for issues that can occur during integration, referenced from the Common Issues
table in SKILL.md. Read this when the user reports a compile error, a build failure, ads not loading,
callbacks not firing, or a platform-specific build error.
**Note**: This section addresses issues that can occur during integration. If you haven't started the
integration yet, begin with Step 1: Verify Environment.
## Contents
- [CS0246 — namespace 'Unity.Services.LevelPlay' not found](#cs0246--namespace-unityserviceslevelplay-not-found)
- [Android gradle build fails / iOS build fails with dependency errors](#android-gradle-build-fails--ios-build-fails-with-dependency-errors)
- [Ads not loading](#ads-not-loading)
- [Callbacks not firing](#callbacks-not-firing)
- [Platform-specific build errors](#platform-specific-build-errors)
## CS0246 — namespace 'Unity.Services.LevelPlay' not found
**Root cause**: Ads Mediation package not installed in Unity project
**Symptoms:**
- Compiler errors: `The type or namespace name 'LevelPlay' could not be found`
- Compiler errors: `The type or namespace name 'Unity.Services.LevelPlay' could not be found`
- Red underlines in Unity Editor on all LevelPlay code
**Solutions:**
1. Stop providing code immediately
2. Read `Packages/packages-lock.json` and look for `com.unity.services.levelplay` (the package id behind the **Ads Mediation** display name). Answer this from the project, not by asking the user to read the Package Manager window: it is the same question, and reading it is both faster and reliable.
3. If the id is absent, the package is not installed. Direct to Step 3 to install via Unity Package Manager
4. Have them restart Unity Editor after installation (important!)
5. Verify installation by checking that `using Unity.Services.LevelPlay;` no longer shows errors
6. Only resume code generation after confirmation and verification
**Prevention**: Always verify package installation at Step 7 checkpoint before generating any code.
## Android gradle build fails / iOS build fails with dependency errors
**Root cause**: Native dependencies not resolved
**Symptoms:**
- **Android**: Gradle build errors mentioning missing dependencies or classes
- **iOS**: CocoaPods errors, missing frameworks, or linker errors
- Code compiles perfectly in Unity Editor but fails during platform build
- Build succeeds in Editor but crashes immediately on device
**Solutions:**
1. Verify you have a dependency manager installed (Mobile Dependency Resolver, Unity External Dependency Manager, or EDM4U)
2. Check your project's Assets folder for dependency manager tools
3. Run dependency resolution:
- **Android (newer MDR versions)**: Should auto-resolve on build. If failing, manually resolve via Assets > Mobile Dependency Resolver > Android Resolver > Resolve
- **Android (older/other managers)**: Assets > [Your Dependency Manager] > Android Resolver > Resolve
- **iOS (all managers)**: Assets > [Your Dependency Manager] > iOS Resolver > Install Cocoapods
4. Verify resolution:
- **Android**: Check `Assets/Plugins/Android/` for gradle files
- **iOS**: Look for Podfile or CocoaPods confirmation in console
5. If you don't have a dependency manager, restart Unity - you should see a prompt to install Mobile Dependency Resolver
6. Rebuild for your target platform after resolution
**Prevention**: Complete Step 4 (dependency resolution) before building for Android/iOS.
## Ads not loading
**Possible causes:**
- SDK not initialized before loading ads
- Incorrect App Key
- Ad object created before initialization completes
- Network connectivity issues
- Ad inventory not available in test region
**Solutions:**
- Verify `LevelPlay.Init()` is called and `OnInitSuccess` fires before creating ad objects
- Create ad objects only after `OnInitSuccess` callback
- Check App Key matches LevelPlay dashboard
- Test on real devices with active internet connection
- Enable test mode in LevelPlay dashboard for guaranteed test ads (Note: dashboard test mode is separate from mock ads in the Unity Editor — it enables real test ads on device)
## Callbacks not firing
**Possible causes:**
- Events registered after SDK initialization
- Missing event subscriptions
- Script destroyed before callbacks execute
**Solutions:**
- Register callbacks before calling `Init()`
- Verify all callbacks are subscribed (check with Debug.Log statements)
- Use persistent GameObject with DontDestroyOnLoad if needed
## Platform-specific build errors
**iOS:**
- Ensure SKAdNetwork IDs are configured in Info.plist
- Verify ATT is implemented correctly (see `references/ios-setup.md`)
- Check Xcode build settings for required frameworks
**Android:**
- Verify Google Play Services is included
- Check AndroidManifest.xml for required permissions
- Ensure Gradle dependencies are resolved
SKILL.md›
---
name: levelplay-unity-integration
description: Integrates the LevelPlay Mediation SDK via the Ads Mediation UPM package. Use when a developer asks about adding ads to a Unity game, implementing rewarded, interstitial, or banner ads, setting up ad mediation, configuring ad networks, installing or updating the Ads Mediation package, troubleshooting LevelPlay namespace errors, resolving Android gradle or iOS CocoaPods dependency issues for ads, configuring ATT or privacy settings for ad compliance, tracking impression-level revenue (ILRD), initializing the LevelPlay SDK, or setting up ad unit IDs. Also use when a developer wants to monetize their Unity game with ads, asks how to get started with LevelPlay, ads, or mediation, or needs help with any part of the LevelPlay integration workflow including platform-specific setup for iOS or Android. Also use when upgrading the LevelPlay or IronSource SDK version, migrating from deprecated IronSource.Agent APIs, or migrating a game from Unity Ads to LevelPlay.
---
# LevelPlay Unity package/SDK Integration
Base editor-side checks on the actual project rather than assumptions — read the project files, or ask the user to confirm in the Editor. The C# scripts generated in this skill are MonoBehaviour files for the user to save to their project, not for inline execution.
This skill covers the LevelPlay integration path only; it does not cover other mediation SDKs. If the user explicitly asks about alternatives, acknowledge that alternatives exist and point them to those vendors' own documentation — do not describe, characterize, or make claims about competitor products.
Follow the steps and provide only the files and configurations described in this skill. Do not proactively add steps, create files, or make recommendations based on general knowledge. If the user asks a question outside the scope of this skill, check the skill and reference files first to confirm it is not covered. If it is not, use general knowledge to respond, but do not incorporate additional steps or files into the integration workflow as a result.
Follow the integration workflow sequentially, one step at a time. Ask only the questions for the current step — do not gather information for future steps in advance. Wait for the user's response at each checkpoint before proceeding.
LevelPlay is Unity's ad mediation platform: it connects your game to multiple ad networks simultaneously and runs a unified auction across multiple ad networks and bidders to maximize competition for each impression. This guide walks you through the full integration: installing the SDK, configuring dependencies for Android and iOS, initializing LevelPlay in your project, and implementing rewarded, interstitial, and banner ads. If you already have part of this set up, you can skip ahead to the relevant step.
**This SKILL.md is the workflow spine. It keeps the decisions, checkpoints, and exact questions; longer code, full API detail, and edge cases live in `references/` and are linked from the relevant step. Read the linked reference when you reach that step — do not answer from general knowledge instead.**
## Integration Workflow
### 0. New Integration or Migration?
Ask: "Are you starting a new LevelPlay integration, migrating an existing one (from an older SDK version or from Unity Ads), or troubleshooting an existing setup?"
- **New integration**: proceed to Step 1.
- **Migration** (SDK upgrade, replacing IronSource.Agent APIs, migrating from Unity Ads, or fixing a Maven Central Android build failure): Read `references/migration-sdk-9.md`. Ask which of the five scenarios applies — A = SDK upgrade, B = init API migration, C = ad unit API migration, D = Maven Central build failure, E = Unity Ads migration — then follow the matching scenario. After applying all code changes, work through the Migration Completeness Checklist (section C5 of the reference) — it catches requirements that a line-by-line translation misses because the legacy code had no equivalent line. Then ask the user to check the Unity console for compilation errors, and fix any that appear before presenting results. Do not block or keep retrying if you cannot see the console: list the files you changed, say what to look for, and continue.
- **Troubleshooting or adding to an existing setup** (ATT, GDPR, ILRD, Test Suite, build errors on a fresh integration, or adding a feature to an already-working integration): Identify what the user needs and go directly to the relevant step or reference from "When to Read Detailed References."
### 1. Verify Unity Environment
Check that the user is working in a Unity project by verifying Assets/ and ProjectSettings/ directories exist. If not in a Unity project, instruct the user to navigate to their Unity project directory. If those directories are not found but the user believes they are in the right place, ask: "It looks like you may not be at your project root — can you navigate to the top-level folder of your Unity project and confirm you can see Assets/ and ProjectSettings/ there?"
### 2. Understand Business Goals
Before implementing ad units, determine the user's optimization priorities to recommend the appropriate ad unit strategy. Ask:
**"What's your primary optimization goal?"**
- **Revenue-focused**: Maximize ad revenue and impression opportunities
- **UX-focused**: Prioritize gameplay flow and user satisfaction
- **Balanced**: Optimize for both revenue and UX
- **Not sure yet**: Default to Balanced and proceed. At Step 8, briefly note you're using Balanced since they were unsure, and invite them to indicate a different preference now that they've seen the format options.
Record this answer for later strategy recommendation in Step 8.
### 3. Install LevelPlay SDK via UPM
**If the SDK looks already installed:** do not take that on trust, and do not ask the user to read
the Package Manager window for you. Read `Packages/packages-lock.json` and look for
`com.unity.services.levelplay`. If it is there, say which version resolved and proceed to Step 4.
If it is not, it is not installed, whatever the conversation so far has assumed: continue with the
install below.
Guide through installing the LevelPlay Unity package using Unity Package Manager:
1. Open Unity → Window > Package Manager
2. Select Unity Registry dropdown or Services tab
3. In the Package Manager search bar, type **Ads Mediation**
4. Confirm the package name matches exactly: the correct package is titled **Ads Mediation**. Do not install either of these packages:
- **Ads IAP Mediation Adaptor** (a separate in-app purchases package, not the LevelPlay SDK)
- **Advertisement Legacy** (a deprecated package, not compatible with the current LevelPlay integration)
5. Click Install button
6. Wait for package to download and import
When you install the package, you may see a prompt to install Mobile Dependency Resolver — click **Import** if it appears. This is covered in more detail in the next step.
**Then verify it resolved, by reading the project rather than by asking.** The package id is
`com.unity.services.levelplay` (its Package Manager display name is **Ads Mediation**; the id is
what the project files record). Check both files:
- **`Packages/manifest.json`** lists what the project *asks for*. `com.unity.services.levelplay`
must appear under `dependencies`.
- **`Packages/packages-lock.json`** records what Unity actually *resolved*. The same id must appear
here too, with a concrete version. This is the file that answers "did it install", and it is the
one to trust.
Both are plain JSON in the project, so this check needs no Editor, no CLI, and nothing from the
user. Read them.
> **This is a hard gate, not a formality.** Do not write, generate, or paste a single line of
> LevelPlay code until `com.unity.services.levelplay` is present in `packages-lock.json`. Skipping
> ahead produces code that looks correct, compiles nowhere, and fails with `CS0246` on every
> LevelPlay symbol. If the id is missing from `manifest.json`, the install never happened. If it is
> in `manifest.json` but not `packages-lock.json`, Unity has not resolved it yet: the Editor may
> still be importing, or resolution failed. Say which of the two you found, and stop.
>
> **If you added the id to `manifest.json` yourself and no Editor has run since, the lock file will
> not show it yet. That is expected, not a failure.** Never write the entry into
> `packages-lock.json` yourself: that file is Unity's resolution output, hand-editing it is what the
> migration guide forbids, and an entry you wrote is a false "resolved" signal rather than a passed
> gate. Ask the user to open the Unity Editor so resolution runs, then re-read the file. If no
> Editor is available at all, say so and stop there rather than manufacturing the evidence.
Report the resolved version you found. Do not report "installed" on the strength of the Package
Manager window, a previous turn, or a user's recollection.
**Network Manager:** Access **Ads Mediation > Network Manager** at any time to install additional ad network adapters and check for SDK and adapter updates.
For iOS builds, note that SKAdNetwork configuration will be needed later (reference `references/ios-setup.md` when ready for iOS builds).
### 4. Resolve Native Dependencies (Critical)
**Critical for Android/iOS builds**: LevelPlay requires native dependency resolution. Without this, code compiles in Unity Editor but fails during platform builds with gradle (Android) or CocoaPods (iOS) errors.
**Platform checkpoint — ask before proceeding:** "Which platform(s) are you targeting — iOS, Android, or both?" Record this. It determines which dependency resolution steps apply here, whether ATT is required (Step 6.5), and which testing steps are relevant (Step 10).
**Set the active build target now.** Switch the project's active build target to Android or iOS via **File ▸ Build Profiles** (called **Build Settings** before Unity 6) → **Switch Platform**. This is required before any testing: LevelPlay only runs on Android/iOS targets, so even mock ads in the Editor (Step 10) do nothing while the target is Standalone/PC/Mac.
**Resolve dependencies for the target platform(s).** LevelPlay requires native Android/iOS libraries that Unity's package manager alone doesn't handle; a dependency manager (MDR, UEDM, or EDM4U) bridges this gap. The full procedure — checking for an existing dependency manager, resolving on Android vs iOS, installing one if the user has none, verification, and the older-version Custom Main Gradle Template — is in **`references/dependency-resolution.md`**. Walk the user through it now, and **if targeting both Android and iOS, complete resolution for both before proceeding.**
Ask: "Have you run dependency resolution for your target platform(s) without errors?"
**Android API 33+ (Android 13+):** declare the AD_ID permission in AndroidManifest.xml:
```xml
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
```
Without it, advertising ID access fails on Android 13+ devices. Details in `references/dependency-resolution.md`.
**If dependency resolution fails**, see `references/troubleshooting.md` for gradle and CocoaPods error guidance.
### 5. Get App Key and Ad Unit IDs
Before initializing LevelPlay, collect credentials from the LevelPlay dashboard.
**Dashboard:** https://platform.ironsrc.com/
**New to LevelPlay?** Set up your app and ad units first:
- [Add your app](https://docs.unity.com/en-us/grow/levelplay/platform/get-started/add-app)
- [Create ad units](https://docs.unity.com/en-us/grow/levelplay/platform/get-started/ad-units)
**App Key:** In the dashboard, go to **Apps** in the left sidenav → find your app → copy the alphanumeric string displayed under the app title.
**Ad Unit IDs:** Go to **Ad units** in the left sidenav → select your app → copy the ID for each format you plan to implement (Rewarded, Interstitial, Banner).
**Note:** You need your App Key now for initialization (Step 7). Ad Unit IDs are only needed at Step 9 — if you haven't decided which ad formats to implement yet, just copy your App Key for now and return here after Step 8.
Keep both accessible — you'll need them in the next steps.
### 6. Configure AdMob Keys (If Using AdMob Network)
**When to use**: Only if using AdMob as a mediation network adapter in LevelPlay.
If using AdMob, configure platform-specific app keys in Unity Editor:
**Access**: Ads Mediation > Developer Settings > LevelPlay Mediation Settings
**Configuration:**
- **Android App Key**: AdMob Android app key
- **iOS App Key**: AdMob iOS app key
This configuration is required for AdMob to work as a mediation network in LevelPlay.
**Troubleshooting**: If you don't see the 'Ads Mediation' menu in Unity Editor, verify the Ads Mediation package is installed (Step 3) and restart Unity Editor.
### 6.5. Privacy & Regulation Settings (If Required)
> **Note:** This skill provides technical integration guidance, including for LevelPlay's privacy APIs. It is not legal advice, and it does not determine which laws apply to your app — that depends on your users, your data practices, and your distribution. Consult your own legal counsel, and refer to [Regulation Advanced Settings for Unity](https://docs.unity.com/en-us/grow/levelplay/sdk/unity/regulation-advanced-settings) for the authoritative LevelPlay documentation.
Ask the user: "Do you need to configure privacy settings for GDPR, CCPA/CPRA (or certain state privacy consumer acts), or for child-directed apps?"
**If YES to any:**
Privacy settings must be configured **BEFORE** SDK initialization. See `references/privacy-settings.md` for the complete implementation guide (UI, consent management, combined regulations, and the full network key list).
**GDPR — the correct API depends on the user's SDK version** (check in **Ads Mediation > Network Manager**):
**SDK 9.5.0+** — global consent boolean:
```csharp
using Unity.Services.LevelPlay;
// true = user has granted consent, false = user has not consented
LevelPlayPrivacySettings.SetGDPRConsent(true);
```
**SDK 9.4.x** — per-network consent dictionary (this is the CURRENT API on 9.4.x, not legacy — it only becomes `[Obsolete]` on 9.5.0+; do not mislabel it as deprecated):
```csharp
using Unity.Services.LevelPlay;
using System.Collections.Generic;
// Add an entry for each ad network you have installed
LevelPlayPrivacySettings.SetGDPRConsents(new Dictionary<string, bool> {
{ "UnityAds", true },
{ "IronSource", true }
// See references/privacy-settings.md for the full network key list
});
```
If neither API compiles, your Unity package/SDK may be below 9.4.0 (legacy) — recommend upgrading via **Ads Mediation > Network Manager**. If the user cannot upgrade, the legacy `LevelPlay.SetConsent(bool)` API is documented in `references/privacy-settings.md`.
**CCPA (SDK 9.4.0+):**
```csharp
LevelPlayPrivacySettings.SetCCPA(true); // User opted out of data sale
```
**COPPA (SDK 9.4.0+):**
```csharp
LevelPlayPrivacySettings.SetCOPPA(true); // Child-directed app
```
If CCPA or COPPA fails to compile, upgrade your Unity package/SDK via **Ads Mediation > Network Manager**. Call all of these BEFORE `LevelPlay.Init()` in Step 7.
**For iOS builds — required regardless of privacy regulations above:** Also implement App Tracking Transparency (ATT) before proceeding to Step 7. Apple requires ATT authorization before your app tracks users or accesses the device's advertising identifier on iOS 14.5+. Request ATT authorization before calling `LevelPlay.Init()` — this is both an Apple platform requirement and necessary for personalized ads (which also affects fill rate). See `references/ios-setup.md` for the ATT implementation code.
**If NO privacy regulations and not targeting iOS:** Skip this step and proceed to Step 7.
### 7. Initialize LevelPlay SDK
**Installation checkpoint:**
**First, re-read `Packages/packages-lock.json` and confirm `com.unity.services.levelplay` is there.**
Do this every time you reach this point, even if Step 3 already passed earlier in the conversation.
It costs one file read, and it is the only item here you can settle without the user. An earlier
turn saying the package was installed is not evidence that it is: this check exists because the
install step is the one most often skipped, and the resulting code fails with `CS0246` on every
LevelPlay symbol. If the id is absent, go back to Step 3 and do not write initialization code.
Then confirm the remaining prerequisites with the user, which are the ones no file can answer.
**If the user confirmed they are not using AdMob, omit the Step 6 item.** If Step 4 was already
confirmed in this conversation, skip that item and ask only about Step 5 and Step 6 (if AdMob).
"Please confirm these are working correctly:
- Step 4: Have you run dependency resolution for your target platform(s) without errors?
- Step 5: Do you have your App Key copied from the LevelPlay dashboard?
- Step 6 (only if using AdMob): Have you configured AdMob keys in Unity Editor settings?
Verify these are working before proceeding."
**If the package check failed or they answer NO or are unsure:**
- Package id absent from `packages-lock.json`: code will show `CS0246` namespace errors → Direct to Step 3. This one you established yourself; do not ask the user to overrule it.
- Missing Step 4: Code compiles but Android/iOS builds will fail → Direct to Step 4
- Missing Step 5: They won't have credentials to initialize → Direct to Step 5
- Do not provide C# code until they confirm all steps are complete
**If they answer YES:**
- **Optional — Analytics: ILRD Wiring.** Ask this question verbatim — do not summarize or rephrase it: "Do you use an analytics or attribution platform (Firebase, AppsFlyer, Adjust, Singular, or custom backend) that needs ad revenue data? If yes, the init script will include a logging stub for Impression Level Revenue (ILRD) — 3 lines of code, no analytics platform setup required yet. (Yes / No / Not sure — defaults to yes)" Record the answer.
- Proceed with initialization code.
LevelPlay SDK must be initialized before loading or showing any ads. Initialization should happen early in the application lifecycle.
**Ask how they want to handle initialization. Present all four options exactly as listed — do not condense or omit any:**
1. Create a new dedicated script for LevelPlay initialization
2. Add to an existing initialization/manager script they already have
3. Create a new LevelPlay script that your existing manager references
4. Just show me the initialization code — I'll decide how to integrate it
**Full code for each option is in `references/initialization-api.md` (Code Organization Options).** Behavior that must not change:
- **Option 1 (new script):** If ATT was set up in Step 6.5 (iOS), use the `LevelPlayInitializer.cs` from `references/ios-setup.md` Part 3 (the `IEnumerator Start()` coroutine variant) instead of the plain template.
- **Option 4 (just the code):** provide the complete Option 1 initialization class as a standalone snippet — do NOT create files or add Inspector/GameObject setup steps — with the note: "save it as `LevelPlayInitializer.cs`, attach it to a persistent GameObject in your first scene, and set the App Key field in the Inspector."
**ILRD wiring (if the user answered Yes or Not Sure).** The correct approach depends on the SDK version (check in **Ads Mediation > Network Manager**):
- **SDK 9.5.0+ (current):** add nothing to the initializer — ILRD is delivered per ad instance via `OnAdImpressionDataReady`, wired when each ad is created in Step 9. The global `LevelPlay.OnImpressionDataReady` event is **deprecated on 9.5.0+ and generates a compiler warning** — do not use it.
- **SDK 9.4.x and earlier:** subscribe to the global `LevelPlay.OnImpressionDataReady` event **before** `LevelPlay.Init()`, add a logging stub, and unsubscribe in `OnDestroy()`. On the iOS coroutine initializer, place the subscription inside `InitializeLevelPlay()` immediately before `LevelPlay.Init(appKey)`.
The exact wiring code for each option (including the iOS coroutine placement) is in `references/initialization-api.md` (Version-Aware ILRD Init Wiring). ILRD callbacks do not fire with mock ads — a device build is needed to verify (see Step 10). For advanced options (user ID, segmentation, consent management), see `references/initialization-api.md`.
### 8. Recommend Ad Unit Strategy
Based on the optimization goal identified in Step 2, recommend an ad unit strategy.
**Recall the user's optimization goal from Step 2.** If the conversation has been long or the answer is unclear, confirm: "Earlier you mentioned your optimization goal. To confirm, are you primarily focused on revenue, user experience, or a balance of both?"
**Map the answer to a strategy** and give a brief recommendation (full detail, benchmarks, and placement guidance are in `references/best-practices.md` under "Ad Format Strategy by Goal"):
- **Revenue-focused** → **Revenue Strategy.** Rewarded (primary monetization, multiple high-value moments) → Interstitial (secondary; at transitions; frequency cap 3–5 min) → Banner (persistent during gameplay). Bid floors are an optional revenue lever configured in Step 9. Implementation priority: **Rewarded → Interstitial → Banner**.
- **UX-focused** → **UX Strategy.** Rewarded only, user-initiated (explicit opt-in), high-value rewards, **no forced ads, ever**. Interstitials optional/sparingly at session boundaries only; banners generally avoided or menu-only. Implementation priority: **Rewarded only, or Rewarded → (optional) Interstitial**.
- **Balanced** → **Balanced Strategy.** Rewarded (2–3 strategic placements) → Interstitial (moderate; natural breakpoints; frequency cap 5–7 min) → Banner (selective; menus/low-attention). Implementation priority: **Rewarded → Interstitial → Banner (selective)**.
- **"Not sure yet"** (from Step 2) → use the **Balanced Strategy**, then add: "Since you weren't sure of your goal earlier, I've gone with the Balanced approach — if you'd prefer to lean more toward revenue or user experience now that you've seen the options, just say so."
- If still unclear, ask: "Would you prioritize revenue, user experience, or a balance of both?"
The next step asks which ad formats to implement from this priority list. If the user wants a different order than recommended, accommodate that preference.
### 9. Implement Ad Units
**Read `references/best-practices.md` first** — its "Code Generation Guidelines (Step 9)" section carries the general ad lifecycle, the per-organization-approach code-gen rules, the always-include requirements (MonoBehaviour, `DestroyAd()` in `OnDestroy()`, the placement-capping show-path check (when placements are used), event unsubscription, null checks, error handling), and the bid-floor wiring examples. Incorporate those patterns into all ad implementations.
**Implementation checkpoint:**
"Before providing ad implementation code, please confirm:
- Did you complete SDK initialization in Step 7?
- Did you receive the 'LevelPlay SDK initialized successfully' log message in your Unity console?
Verify initialization is working before proceeding with ad units."
**If they answer NO or are unsure:** direct back to Step 7 and do not provide ad implementation code until initialization is confirmed working.
**Ad format checkpoint — ask before generating any code:** "Which ad formats do you want to implement? Rewarded, Interstitial, Banner, or a combination?" Only implement the formats the user selects. They can add more formats later using the 'Adding More Ad Formats Later' section.
**First, ask the user how they want to organize the ad code. Do not generate any code until they have answered:**
"How would you like to structure your ad implementation?"
1. **Separate manager scripts for each ad format** - Create individual scripts like `RewardedAdManager.cs`, `InterstitialAdManager.cs`, `BannerAdManager.cs` (good for larger projects, clear separation of concerns)
2. **One unified AdManager script** - Create a single `AdManager.cs` that handles all ad formats (simpler, everything in one place)
3. **Just show me the code snippets** - Provide implementation code without wrapping it in specific files, so you can integrate it however you prefer
4. **I already have ad manager code** - Review and help fix/update existing implementation
Based on their answer, adapt your response accordingly (see the code-gen guidelines in `references/best-practices.md`).
**If the user already has ad code (e.g., an existing manager script), ask to see it before generating any** — so you can provide targeted fixes rather than new code from scratch. This applies regardless of which organization option they picked (Option 4 is specifically for reviewing existing code, but the same "show me your code first" applies whenever the user mentions they already have some).
**Then present the optional bid floor feature (skip for Option 4 — review existing code instead):**
Present bid floor ranges only for the formats the user is implementing in this session. Reference starting ranges: Rewarded: $0.50–$2.00 | Interstitial: $0.20–$1.00 | Banner: $0.05–$0.20. Include only the ranges for formats being implemented.
"**Optional — Advanced: Bid Floors**
Most publishers skip this initially and add it once they have real dashboard data. You can safely skip now and return to it later.
If you'd like to set bid floors now: a bid floor sets a minimum bid price (USD) per ad unit — it raises your average eCPM at the cost of lower fill rate. Starting ranges:
[ranges for formats being implemented]
Reply with values per format, or just say 'skip' — you can add them any time."
**Record the answer per format.** Wire `Config.Builder().SetBidFloor(...)` into the ad construction for any format where a value was provided; formats marked 'skip' use the basic constructor (see the bid-floor examples in `references/best-practices.md`).
**If they choose Option 4 (existing code):**
- Ask: "Please share your existing ad manager code for review" and wait for it.
- Analyze the implementation: whether they use the current LevelPlay Ad Unit API (LevelPlayRewardedAd, LevelPlayInterstitialAd, LevelPlayBannerAd), whether they use **deprecated IronSource.Agent APIs**, proper callback registration/unsubscription, and missing error handling or memory leaks.
- Provide specific guidance:
- If using deprecated APIs: "You're using the old IronSource.Agent API. Here's how to migrate to the new LevelPlay Ad Unit API:" (full migration detail in `references/migration-sdk-9.md` — Scenario B for init, Scenario C per ad format including the C5 completeness checklist)
- If using current APIs with issues: "Your implementation looks good but I noticed [specific issues]. Here's how to fix them:"
- If implementation is correct: "Your implementation looks solid. Which additional ad formats would you like to add?"
- Offer fixes as code snippets or suggest refactoring. When adding new formats after review, present the bid floor prompt scoped to those new formats only, confirm whether to match their existing organization pattern or use a new one, then follow the same guidelines as Options 1–3.
For each ad format, follow the implementation guidelines in the detailed references:
- **Rewarded ads**: See `references/rewarded-api.md`
- **Interstitial ads**: See `references/interstitial-api.md`
- **Banner ads**: See `references/banner-api.md`
**Impression Level Revenue Tracking (version-aware):** See `references/ilrd-api.md` to forward impression data to the analytics platform (Firebase, AppsFlyer, Adjust, Singular, or custom backend).
- **SDK 9.5.0+ (current):** subscribe to each ad object's `OnAdImpressionDataReady` event right after you create it (and unsubscribe in `OnDestroy()`). Add this to every ad manager you generate — it is the correct ILRD path in 9.5.0+.
- **SDK 9.4.x and earlier:** ILRD uses the single global `LevelPlay.OnImpressionDataReady` event, wired in the init script (Step 7). If the user answered Yes/Not Sure in Step 7, it is already wired. If they said "No" and want it now, subscribe to `LevelPlay.OnImpressionDataReady` **before** the existing `LevelPlay.Init()` call.
### 10. Testing and Validation
LevelPlay provides two validation approaches for different stages of development. **Full detail — setup, callback-behavior tables, the Test Suite initializer template, and the iOS coroutine placement — is in `references/testing-and-validation.md`. Read it when the user is testing.**
**Early Development: Mock Ads in Unity Editor.** For rapid iteration and callback testing. Pressing Play in the Editor provides mock ads automatically — but **only if the active build target is Android or iOS** (Standalone/PC/Mac returns no ads; this is the most common "no ads in Editor" cause — see Step 4). Mock ads work with any App Key/Ad Unit ID, but recommend real credentials so they aren't forgotten. Mock ads fire most callbacks (OnAdLoaded/Displayed/Rewarded/Closed) but NOT failure, click, or impression/ILRD callbacks — so test error handling on device. Details and the full callback table: `references/testing-and-validation.md`.
**Integration Validation: LevelPlay Test Suite (Recommended).** The primary method for comprehensive validation against real ad networks on device. Key rules that must not change:
- `LevelPlay.SetMetaData("is_test_suite", "enable");` **before** `LevelPlay.Init()`
- `LevelPlay.LaunchTestSuite();` inside `OnInitSuccess`
- **Requires a device build** (does not work in the Editor); enable **Development Build** so SDK logs are visible; use the production App Key.
- **Remove both lines before production release.**
- iOS coroutine initializer: put `SetMetaData` as the first line inside `InitializeLevelPlay()` before `Init` (not in `Start()`).
Add the two lines to the existing `LevelPlayInitializer.cs` (don't create a new file). The full setup, the standalone template for users without an initializer, and the testing workflow are in `references/testing-and-validation.md`.
#### Production Release Checklist
Before releasing to production:
- [ ] Test Suite validation completed successfully on device
- [ ] All ad formats load correctly (Rewarded, Interstitial, Banner if implemented)
- [ ] All callbacks fire as expected
- [ ] App Key and ad unit IDs verified correct for production
- [ ] Tested on multiple devices (different screen sizes, OS versions)
- [ ] **iOS-specific requirements completed** (if targeting iOS):
- [ ] SKAdNetwork IDs configured in Info.plist (see `references/ios-setup.md`)
- [ ] App Tracking Transparency (ATT) framework implemented (see `references/ios-setup.md`)
- [ ] iOS privacy manifest configured if required
- [ ] Tested on physical iOS device (not just simulator)
- [ ] **Android-specific requirements completed** (if targeting Android):
- [ ] Google Play Services dependencies resolved (Step 4 completed)
- [ ] AD_ID permission added to AndroidManifest.xml if targeting API 33+ (see Step 4)
- [ ] Tested on physical Android device
- [ ] Tested with real ads in production environment
- [ ] Ad frequency capping implemented (if using interstitials)
- [ ] Error handling works correctly (test with airplane mode - ads should fail gracefully without crashing or blocking gameplay)
## Adding More Ad Formats Later
If you've already integrated some ad formats and want to add more:
1. **Skip to Step 9** - You don't need to repeat the initial setup steps. Before proceeding, verify your existing initialization still works by checking the Unity console for the 'LevelPlay SDK initialized successfully' log.
2. **Choose the additional formats** you want to implement
3. **Follow the same organization pattern** you used before:
- If you created separate manager scripts, create a new manager script for the new format
- If you used a unified AdManager, add the new format's code to your existing AdManager class
- If you used code snippets, integrate new snippets following the same pattern
4. **Follow the same implementation guidelines** from Step 9 for the new ad format
5. **Test the new format** following Step 10 testing guidelines
**Example**: If you initially implemented only Rewarded ads using separate manager scripts, and now want to add Interstitial ads: create `InterstitialAdManager.cs` following the same structure as your `RewardedAdManager.cs`, follow the interstitial guidelines from `references/interstitial-api.md`, and test in Editor and on device. Your existing ad formats keep working while you add new ones.
## Best Practices
Before implementing ad code, read `references/best-practices.md`. It covers loading strategy (per format), placement strategy, error handling and graceful degradation, memory management, frequency management, and common mistakes to avoid — incorporate these patterns into all ad implementations.
## Common Issues and Solutions
If the user reports a problem, route to the matching issue in `references/troubleshooting.md` and follow it (stop generating code where that guidance says to). Do not wait for the user to open the reference — surface the fix directly. If they haven't started integration yet, begin with Step 1.
| Symptom | Likely root cause | Action |
|---|---|---|
| `CS0246` on `Unity.Services.LevelPlay`; red underlines on all LevelPlay code | Ads Mediation package not installed | Stop giving code; check `Packages/packages-lock.json` for `com.unity.services.levelplay`; install (Step 3); restart Editor; then resume. See troubleshooting.md. |
| Android gradle / iOS build fails with dependency errors; compiles in Editor but fails at build | Native dependencies not resolved | Resolve dependencies (Step 4 / dependency-resolution.md); verify `Assets/Plugins/Android/`; rebuild. See troubleshooting.md. |
| Ads not loading | SDK not initialized, wrong App Key, ad created before init, or no connectivity | Confirm `OnInitSuccess` fires before creating ads; check App Key; test on device. See troubleshooting.md. |
| Callbacks not firing | Events registered after init, missing subscriptions, or script destroyed | Register callbacks before `Init()`; verify subscriptions; use a persistent GameObject. See troubleshooting.md. |
| Platform-specific build errors (iOS SKAdNetwork/ATT/frameworks; Android Play Services/manifest/gradle) | Platform setup incomplete | See troubleshooting.md and `references/ios-setup.md`. |
| Android build fails resolving `com.ironsource.sdk` dependencies from `android-sdk.is.com` (worked before; nothing changed) | Dependencies moved to Maven Central; the old is.com repository was shut down | Follow Scenario D in `references/migration-sdk-9.md`: delete the stale dependency XMLs, reinstall via Network Manager, verify no is.com references remain. |
## When to Read Detailed References
Read specific references based on what the user is doing:
- **`references/dependency-resolution.md`**: Resolving native dependencies (Step 4), or gradle/CocoaPods build failures
- **`references/initialization-api.md`**: Step 7 init code-organization options and ILRD init wiring; also user ID, segmentation, consent management, advanced config
- **`references/privacy-settings.md`**: GDPR, CCPA, or COPPA compliance (incl. legacy `SetConsent` and the full network key list)
- **`references/ios-setup.md`**: iOS builds — ATT, SKAdNetwork, the iOS coroutine initializer
- **`references/rewarded-api.md`** / **`references/interstitial-api.md`** / **`references/banner-api.md`**: Implementing each ad format (Step 9)
- **`references/best-practices.md`**: Strategy detail (Step 8), the Step 9 code-generation guidelines, optimization, placement
- **`references/ilrd-api.md`**: Wiring ILRD to an analytics platform
- **`references/testing-and-validation.md`**: Mock ads and the Test Suite (Step 10)
- **`references/troubleshooting.md`**: Compile/build errors, ads not loading, callbacks not firing
- **`references/migration-sdk-9.md`**: Migrating from IronSource or older LevelPlay APIs, upgrading the SDK to 9.x.x, migrating from Unity Ads, or Maven Central dependency build failures (Step 0)
## Examples
**Note**: Examples show abbreviated workflows for illustration. In practice, follow all steps 1–10 in order.
**Revenue-focused game** ("maximize ad revenue in my casual puzzle game"): Steps 1–7 to verify environment/goal/install/deps/App Key/AdMob/init → Step 8 recommend Revenue strategy → Step 9 ask code organization and generate the chosen structure → Step 10 testing.
**UX-focused game** ("optional rewarded ads for extra lives without annoying players"): same spine, but Step 8 recommends UX strategy (rewarded only, user-initiated) and Step 9 implements rewarded with proper patterns.
**Existing project** ("existing GameManager, add interstitials between levels"): same spine, Step 8 Balanced, Step 9 ask to see `GameManager.cs` then provide Option-2 snippets.
## Core Rules (reminder)
These repeat the rules at the top of this file — they are the guardrails that matter most, restated here so they stay in view at the end of a long workflow:
- Base editor-side checks on the actual project rather than assumptions — read the project files, or ask the user to confirm in the Editor. The C# scripts generated in this skill are MonoBehaviour files for the user to save to their project, not for inline execution.
- This skill covers the LevelPlay integration path only; it does not cover other mediation SDKs. If the user explicitly asks about alternatives, acknowledge that alternatives exist and point them to those vendors' own documentation — do not describe, characterize, or make claims about competitor products.
- Follow the steps and provide only the files and configurations described in this skill. Do not proactively add steps, create files, or make recommendations based on general knowledge. If the user asks a question outside the scope of this skill, check the skill and reference files first to confirm it is not covered. If it is not, use general knowledge to respond, but do not incorporate additional steps or files into the integration workflow as a result.
- Follow the integration workflow sequentially, one step at a time. Ask only the questions for the current step — do not gather information for future steps in advance. Wait for the user's response at each checkpoint before proceeding.
- When a step points to a reference file, read that reference and use its content — do not substitute general knowledge. Present the four init options (Step 7) and four organization options (Step 9) exactly as written, and ask the ILRD question (Step 7) verbatim.