Ad Integration Guide¶
Complete guide to integrating ads with LevelPlay, Appodeal, or AdMob.
Overview¶
The IntelliVerseX SDK abstracts ad providers, letting you switch networks without code changes.
graph TD
A[Your Game] --> B[IVXAdsManager]
B --> C{Provider}
C --> D[LevelPlay]
C --> E[Appodeal]
C --> F[AdMob] Quick Start¶
1. Configure Provider¶
In IntelliVerseX > Game Config, select your ad provider.
2. Add App Keys¶
Enter your app keys for each platform (Android/iOS).
3. Initialize¶
using IntelliVerseX.Monetization;
// Ads initialize automatically with SDK
// Or manually:
await IVXAdsManager.Instance.InitializeAsync();
Banner Ads¶
Show Banner¶
Hide Banner¶
Banner Events¶
void Start()
{
IVXAdsManager.OnBannerLoaded += () => Debug.Log("Banner loaded");
IVXAdsManager.OnBannerFailed += (error) => Debug.Log($"Banner failed: {error}");
IVXAdsManager.OnBannerClicked += () => Debug.Log("Banner clicked");
}
Interstitial Ads¶
Load Interstitial¶
// Preload for faster display
public void PreloadInterstitial()
{
IVXAdsManager.Instance.LoadInterstitial();
}
Show Interstitial¶
public void ShowInterstitial()
{
if (IVXAdsManager.Instance.IsInterstitialReady())
{
IVXAdsManager.Instance.ShowInterstitial();
}
else
{
Debug.Log("Interstitial not ready");
// Load for next time
IVXAdsManager.Instance.LoadInterstitial();
}
}
Interstitial Events¶
void Start()
{
IVXAdsManager.OnInterstitialLoaded += () =>
{
Debug.Log("Interstitial ready");
};
IVXAdsManager.OnInterstitialFailed += (error) =>
{
Debug.Log($"Interstitial failed: {error}");
// Try loading again after delay
Invoke(nameof(LoadInterstitial), 30f);
};
IVXAdsManager.OnInterstitialShown += () =>
{
Debug.Log("Interstitial shown");
// Pause game audio
AudioListener.pause = true;
};
IVXAdsManager.OnInterstitialClosed += () =>
{
Debug.Log("Interstitial closed");
// Resume game
AudioListener.pause = false;
// Preload next
IVXAdsManager.Instance.LoadInterstitial();
};
}
Rewarded Video Ads¶
Load Rewarded Video¶
Show Rewarded Video¶
public void ShowRewardedVideo()
{
if (IVXAdsManager.Instance.IsRewardedVideoReady())
{
IVXAdsManager.Instance.ShowRewardedVideo();
}
else
{
ShowToast("Video not available. Try again later.");
IVXAdsManager.Instance.LoadRewardedVideo();
}
}
Handle Rewards¶
void Start()
{
IVXAdsManager.OnRewardedVideoCompleted += (reward) =>
{
// Player earned the reward!
Debug.Log($"Reward earned: {reward.Amount} {reward.Currency}");
// Grant reward
AddCoins(reward.Amount);
ShowRewardAnimation();
};
IVXAdsManager.OnRewardedVideoClosed += (wasRewarded) =>
{
if (!wasRewarded)
{
Debug.Log("User skipped - no reward");
ShowToast("Watch the full video to earn rewards!");
}
// Preload next
IVXAdsManager.Instance.LoadRewardedVideo();
};
}
Complete Implementation¶
AdController.cs¶
using IntelliVerseX.Monetization;
using UnityEngine;
using UnityEngine.UI;
public class AdController : MonoBehaviour
{
[SerializeField] private Button _watchAdButton;
[SerializeField] private Text _rewardText;
private int _pendingReward;
void Start()
{
SetupEventHandlers();
PreloadAds();
UpdateUI();
}
void SetupEventHandlers()
{
// Rewarded video events
IVXAdsManager.OnRewardedVideoLoaded += OnRewardedLoaded;
IVXAdsManager.OnRewardedVideoCompleted += OnRewardEarned;
IVXAdsManager.OnRewardedVideoClosed += OnRewardedClosed;
IVXAdsManager.OnRewardedVideoFailed += OnRewardedFailed;
// Interstitial events
IVXAdsManager.OnInterstitialClosed += OnInterstitialClosed;
}
void OnDestroy()
{
// Unsubscribe to prevent memory leaks
IVXAdsManager.OnRewardedVideoLoaded -= OnRewardedLoaded;
IVXAdsManager.OnRewardedVideoCompleted -= OnRewardEarned;
IVXAdsManager.OnRewardedVideoClosed -= OnRewardedClosed;
IVXAdsManager.OnRewardedVideoFailed -= OnRewardedFailed;
IVXAdsManager.OnInterstitialClosed -= OnInterstitialClosed;
}
void PreloadAds()
{
IVXAdsManager.Instance.LoadRewardedVideo();
IVXAdsManager.Instance.LoadInterstitial();
}
void UpdateUI()
{
bool hasAd = IVXAdsManager.Instance.IsRewardedVideoReady();
_watchAdButton.interactable = hasAd;
_rewardText.text = hasAd ? "Watch to earn 100 coins!" : "Loading...";
}
public void OnWatchAdClicked()
{
if (IVXAdsManager.Instance.IsRewardedVideoReady())
{
_pendingReward = 100;
IVXAdsManager.Instance.ShowRewardedVideo();
}
}
void OnRewardedLoaded()
{
UpdateUI();
}
void OnRewardEarned(RewardInfo reward)
{
// Give the reward
GameManager.Instance.AddCoins(_pendingReward);
ShowRewardPopup(_pendingReward);
}
void OnRewardedClosed(bool wasRewarded)
{
// Preload next ad
IVXAdsManager.Instance.LoadRewardedVideo();
UpdateUI();
}
void OnRewardedFailed(string error)
{
Debug.LogWarning($"Rewarded ad failed: {error}");
_watchAdButton.interactable = false;
// Retry after delay
Invoke(nameof(RetryLoad), 30f);
}
void OnInterstitialClosed()
{
// Preload next
IVXAdsManager.Instance.LoadInterstitial();
}
void RetryLoad()
{
IVXAdsManager.Instance.LoadRewardedVideo();
}
void ShowRewardPopup(int amount)
{
// Your reward UI
}
}
LevelPlay Specific¶
Setup¶
- Download LevelPlay SDK from Unity Asset Store or ironSource
- Import into project
- In IntelliVerseXConfig:
- Set Ad Provider: LevelPlay
- Enter App Key
Mediation Setup¶
Configure networks in LevelPlay dashboard:
- AdMob
- Facebook Audience Network
- Unity Ads
- Vungle
- AppLovin
Appodeal Specific¶
Setup¶
- Import Appodeal SDK
- In IntelliVerseXConfig:
- Set Ad Provider: Appodeal
- Enter App Key
Consent¶
// Appodeal requires explicit consent handling
IVXAdsManager.Instance.SetGDPRConsent(userConsented);
IVXAdsManager.Instance.SetCCPAConsent(userOptedIn);
AdMob Specific¶
Setup¶
- Add AdMob App ID to Player Settings
- In IntelliVerseXConfig:
- Set Ad Provider: AdMob
- Enter Ad Unit IDs
Test Ads¶
// Always test with AdMob test ad units
// Banner: ca-app-pub-3940256099942544/6300978111
// Interstitial: ca-app-pub-3940256099942544/1033173712
// Rewarded: ca-app-pub-3940256099942544/5224354917
Best Practices¶
1. Smart Loading¶
// Load at appropriate times
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// Preload for upcoming opportunities
IVXAdsManager.Instance.LoadInterstitial();
IVXAdsManager.Instance.LoadRewardedVideo();
}
2. Frequency Capping¶
private int _adsShownThisSession = 0;
private const int MAX_ADS_PER_SESSION = 5;
public void ShowInterstitialIfAllowed()
{
if (_adsShownThisSession < MAX_ADS_PER_SESSION)
{
IVXAdsManager.Instance.ShowInterstitial();
_adsShownThisSession++;
}
}
3. User Experience¶
// DON'T show ads:
// - During tutorial
// - During intense gameplay
// - Immediately after purchase
// - Back-to-back (add cooldown)
// DO show ads:
// - Between levels
// - After game over
// - Natural pauses
// - User-initiated (rewarded)
4. Handle No Fill¶
IVXAdsManager.OnInterstitialFailed += (error) =>
{
if (error.Contains("no fill") || error.Contains("no ads"))
{
// Normal - no ads available
// Try again later, don't spam requests
}
};
Revenue Tracking¶
Track Ad Revenue¶
IVXAdsManager.OnAdRevenue += (revenueData) =>
{
// Track for analytics
IVXAnalyticsManager.Instance.TrackEvent("ad_revenue", new Dictionary<string, object>
{
{ "revenue", revenueData.Revenue },
{ "currency", revenueData.Currency },
{ "network", revenueData.NetworkName },
{ "ad_type", revenueData.AdType }
});
};
Troubleshooting¶
Ads Not Loading¶
- Check app keys are correct
- Check internet connection
- Verify dashboard setup complete
- Check test mode settings
- Review console for errors
Low Fill Rate¶
- Add more networks to mediation
- Check ad unit setup in dashboard
- Consider geo-targeting
- Review eCPM floor settings
Crashes on Ad Show¶
- Update ad SDK to latest
- Check ProGuard/R8 rules
- Review AndroidManifest
- Test on multiple devices
Server-Side Reward Validation¶
Why Server Validation Matters¶
Client-side reward granting is vulnerable to fraud: modified clients, network replay attacks, and memory manipulation can all produce fake reward events. Server-side validation ensures every rewarded-ad payout is backed by a legitimate, verified ad completion.
Without Server Validation
A modified client can fire OnRewardedVideoCompleted without the user watching an ad, granting unlimited free currency. Always validate on the server for competitive or economy-sensitive games.
Nakama rewarded_ads Module¶
The IntelliVerseX backend includes a Nakama RPC module rewarded_ad_complete that:
- Receives the ad completion data (network name, placement ID, signature)
- Validates the ad network's callback signature
- Checks for duplicate transaction IDs
- Grants the reward to the player's wallet
- Logs the event for analytics and audit
Validation Flow¶
sequenceDiagram
participant Player
participant Client as Game Client
participant AdNetwork as Ad Network SDK
participant Nakama as Nakama Server
Player->>Client: Taps "Watch Ad for Coins"
Client->>AdNetwork: ShowRewardedVideo()
AdNetwork->>Player: Plays video ad
Player->>AdNetwork: Completes full video
AdNetwork->>Client: OnRewardedVideoCompleted(reward, signature)
Client->>Nakama: RPC "rewarded_ad_complete" (placement, txn_id, signature)
Nakama->>Nakama: Validate signature against ad network secret
Nakama->>Nakama: Check duplicate txn_id
Nakama->>Nakama: Grant currency to player wallet
Nakama->>Client: Response (success, new_balance)
Client->>Player: Show reward animation Implementation¶
using IntelliVerseX.Monetization;
using IntelliVerseX.Backend;
using System.Collections.Generic;
using UnityEngine;
public class ServerValidatedAdController : MonoBehaviour
{
private void OnEnable()
{
IVXAdsManager.OnRewardedVideoCompleted += OnAdCompleted;
}
private void OnDisable()
{
IVXAdsManager.OnRewardedVideoCompleted -= OnAdCompleted;
}
private async void OnAdCompleted(RewardInfo reward)
{
var payload = new Dictionary<string, object>
{
{ "placement_id", reward.PlacementId },
{ "ad_network", reward.NetworkName },
{ "transaction_id", reward.TransactionId },
{ "signature", reward.Signature },
{ "reward_type", reward.Currency },
{ "reward_amount", reward.Amount }
};
var response = await IVXNakamaClient.Instance.RpcAsync(
"rewarded_ad_complete", payload
);
if (response.Success)
{
int newBalance = response.GetInt("new_balance");
Debug.Log($"[Ads] Server validated. New balance: {newBalance}");
UpdateCoinDisplay(newBalance);
ShowRewardAnimation(reward.Amount);
}
else
{
Debug.LogWarning($"[Ads] Server rejected reward: {response.Error}");
}
}
private void UpdateCoinDisplay(int balance) { /* your UI update */ }
private void ShowRewardAnimation(int amount) { /* your animation */ }
}
Enabling Server Validation¶
In your IVXAdsConfig ScriptableObject, enable the server validation toggle:
| Field | Type | Description |
|---|---|---|
EnableServerValidation | bool | Route all rewarded-ad completions through Nakama |
ValidationTimeoutSec | float | Max seconds to wait for server response (default: 10) |
GrantOnTimeout | bool | Grant reward locally if server times out (default: false) |
Timeout Strategy
Set GrantOnTimeout = false for competitive games where economy integrity is critical. Set it to true for casual games where a good user experience takes priority over occasional unverified rewards.
When server validation is enabled, the client does not grant rewards locally. All reward granting happens inside the Nakama RPC, ensuring a single source of truth.
See Ads Configuration for detailed setup instructions.