← Files UnityARCHIVED FILE

skills/levelplay-unity-integration/references/best-practices.md

19 KB · Oct 5, 2026 · 18:34 UTC

↓ Download file

# 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

SHA-256: 37e155a3b55005328cd962e2322e2449d6e90af16884f754a6b26672b743f2ac