diff --git a/skills/levelplay-unity-integration/SKILL.md b/skills/levelplay-unity-integration/SKILL.md index 5f00798..b0d9044 100644 --- a/skills/levelplay-unity-integration/SKILL.md +++ b/skills/levelplay-unity-integration/SKILL.md @@ -1,5 +1,5 @@ --- -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. +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 @@ -18,6 +18,14 @@ LevelPlay is Unity's ad mediation platform: it connects your game to multiple ad ## 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?" @@ -74,6 +82,13 @@ user. Read them. > 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. @@ -257,7 +272,7 @@ The next step asks which ad formats to implement from this priority list. If the ### 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()`, event unsubscription, null checks, error handling), and the bid-floor wiring examples. Incorporate those patterns into all ad implementations. +**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:** @@ -306,7 +321,7 @@ Reply with values per format, or just say 'skip' — you can add them any time." - 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:" (migration detail in `references/initialization-api.md` "Migration from IronSource.* APIs" and the per-format references) + - 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. @@ -388,6 +403,7 @@ If the user reports a problem, route to the matching issue in `references/troubl | 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 @@ -402,6 +418,7 @@ Read specific references based on what the user is doing: - **`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 diff --git a/skills/levelplay-unity-integration/references/banner-api.md b/skills/levelplay-unity-integration/references/banner-api.md index 0967108..1437687 100644 --- a/skills/levelplay-unity-integration/references/banner-api.md +++ b/skills/levelplay-unity-integration/references/banner-api.md @@ -830,21 +830,21 @@ void OnDestroy() ### Issue: Repeated LoadAd() calls -**Cause:** Calling `LoadAd()` repeatedly in `Update()` or other high-frequency loops. +**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 (see note below). + // 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:** While banner ads don't throw errors for repeated loads like interstitial/rewarded ads do, calling `LoadAd()` multiple times per second wastes resources and can cause unexpected behavior. +**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 @@ -853,7 +853,7 @@ void Start() bannerAd = new LevelPlayBannerAd(adUnitId); bannerAd.OnAdLoaded += OnAdLoaded; bannerAd.OnAdLoadFailed += OnAdLoadFailed; - bannerAd.LoadAd(); // Call once + bannerAd.LoadAd(); // The only LoadAd() for this object's lifetime } void OnAdLoadFailed(LevelPlayAdError error) @@ -868,7 +868,7 @@ void RetryLoad() } ``` -**Note:** Banner ads auto-refresh based on platform settings, so you typically only need to call `LoadAd()` once at initialization. +**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 diff --git a/skills/levelplay-unity-integration/references/best-practices.md b/skills/levelplay-unity-integration/references/best-practices.md index ce7a27d..c4e800f 100644 --- a/skills/levelplay-unity-integration/references/best-practices.md +++ b/skills/levelplay-unity-integration/references/best-practices.md @@ -1,603 +1,604 @@ -# 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 -- 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 +# 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 diff --git a/skills/levelplay-unity-integration/references/initialization-api.md b/skills/levelplay-unity-integration/references/initialization-api.md index 5ff1c90..ea9ad74 100644 --- a/skills/levelplay-unity-integration/references/initialization-api.md +++ b/skills/levelplay-unity-integration/references/initialization-api.md @@ -786,6 +786,8 @@ IEnumerator InitializeAfterOnboarding() ## 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):** diff --git a/skills/levelplay-unity-integration/references/interstitial-api.md b/skills/levelplay-unity-integration/references/interstitial-api.md index 64a93f2..c394f99 100644 --- a/skills/levelplay-unity-integration/references/interstitial-api.md +++ b/skills/levelplay-unity-integration/references/interstitial-api.md @@ -183,6 +183,9 @@ public class FrequencyCappedInterstitialManager : MonoBehaviour interstitialAd.OnAdDisplayed -= OnAdDisplayed; interstitialAd.OnAdClosed -= OnAdClosed; interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed; + + // Destroy the ad to free its native resources + interstitialAd.DestroyAd(); } } @@ -293,6 +296,9 @@ public class MultiPlacementInterstitialManager : MonoBehaviour interstitialAd.OnAdDisplayed -= OnAdDisplayed; interstitialAd.OnAdClosed -= OnAdClosed; interstitialAd.OnAdDisplayFailed -= OnAdDisplayFailed; + + // Destroy the ad to free its native resources + interstitialAd.DestroyAd(); } } @@ -796,6 +802,9 @@ void OnDestroy() interstitialAd.OnAdClicked -= OnAdClicked; interstitialAd.OnAdClosed -= OnAdClosed; interstitialAd.OnAdInfoChanged -= OnAdInfoChanged; + + // Destroy the ad to free its native resources + interstitialAd.DestroyAd(); } } ``` diff --git a/skills/levelplay-unity-integration/references/migration-sdk-9.md b/skills/levelplay-unity-integration/references/migration-sdk-9.md new file mode 100644 index 0000000..9513a18 --- /dev/null +++ b/skills/levelplay-unity-integration/references/migration-sdk-9.md @@ -0,0 +1,666 @@ +# 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 `` tags + +**If found** → Manual migration required (see below) +**If not found** → Already migrated, no action needed + +### Detect Old Format +```xml + + + + https://android-sdk.is.com/ + + +``` + +### Expected New Format +```xml + + + +``` + +### 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` | diff --git a/skills/levelplay-unity-integration/references/privacy-settings.md b/skills/levelplay-unity-integration/references/privacy-settings.md index d8ffeb2..4e31a20 100644 --- a/skills/levelplay-unity-integration/references/privacy-settings.md +++ b/skills/levelplay-unity-integration/references/privacy-settings.md @@ -443,7 +443,7 @@ public class PrivacyComplianceManager : MonoBehaviour private void ShowGDPRConsentDialog() { - // Show your consent UI, then call OnGDPRConsentsReceived() + // Show your consent UI, then call OnGDPRConsentReceived() } private void OnGDPRConsentReceived(bool userConsented)