UnityのアプリにLevelPlayで広告を実装する

に公開

Unity LevelPlay 広告実装ガイド(2025年版)

概要

Unity LevelPlayを使用した広告実装の手順を解説します。LevelPlay Mediation 9.0.0に対応した実装方法を紹介します。今回はリワード広告のみのテストになります。

必要なもの(私の環境)

  • Unity 2021.3 以降推奨
  • LevelPlay アカウント
  • iOS: Xcode 14.0 以降
  • Android: API Level 21 以上
  • .NET SDK (ver.9.0.200+)
区分 必須項目 対応バージョン
Unity Unity Editor Unity6000.2.8f1
iOS開発環境 Xcode Version 26.0.1 (17A400)
.NET SDK .NET SDK for macOS 9.0.200 以降(VSCode 連携や .slnx 対応のため)
LevelPlay Unity LevelPlay アカウント LevelPlay Dashboard で作成・連携

1. Unity Dashboard セットアップ

1-1. プロジェクト設定を開く

Unity エディタで Edit → Project Settings → Services を開きます。

設定画面

1-2. テストモードを有効化

開発中はテストモードを有効にして、本番広告が配信されないようにします。

テストモード設定1
テストモード設定2

2. LevelPlay Dashboard セットアップ

2-1. UnityAds(Unity Dashboard)と紐づける

Connect your accountをクリック

Unity Adsを連携させる
API KeyとOrganization core IDはUnity dashboardから取得できる

API Key
ここで取得

Organization core ID
ここで取得

2-2. Ad Unit ID を作成する

  1. LevelPlay Dashboard にログインします

LevelPlay Dashboard

  1. 左メニュー Setup → Ad Units を開きます

  2. 必要な広告形式ごとに Ad Unit を作成します

    • Rewarded - リワード動画広告用
    • Interstitial - 全画面広告用
    • Banner - バナー広告用

重要: Ad Mediation 9.0.0 以降は Ad Unit ベース API に刷新されており、各広告インスタンスの生成に Ad Unit ID が必須です。

2-3. App Key を確認

Dashboard の Setup → Apps から、iOS/Android 用の App Key を確認してメモしておきます。

2-4. Test Devices を設定する

テスト端末のIDFAを取得(iOS)

  1. Adjust Insights アプリを iPhone にインストール
  2. アプリを起動して IDFA を確認

LevelPlay Dashboard に登録

  1. Setup → Test Devices を開く
  2. 取得した IDFA(iOS)または GAID(Android)を追加
  3. 保存後、反映まで数分待機

3. Unity プロジェクトのセットアップ

3-1. 必要な SDK / パッケージ

以下のパッケージをインストールします:

  • Ads Mediation 9.0.0 以降
  • iOS 14 Advertising Support 1.2.0(iOS で ATT を扱う場合)

3-2. Ad Mediation パッケージの確認

  1. Unity エディタで Window → LevelPlay → Ad Mediation → Network Manager を開く

Network Manager1
Network Manager2

  1. 以下を確認:

3-3. SKAdNetwork IDs の自動追加を有効化(iOS)

Network ManagerSKAdNetwork IDs Manager タブで、自動追加を有効にします。これにより、主要な広告ネットワークの SKAdNetwork ID が自動で Info.plist に追加されます。

4. 広告マネージャーの実装

4-1. AdManager スクリプトの作成

Assets/Scripts/AdManager.cs を作成し、以下のコードを実装します。

実装している機能

✅ ATT(App Tracking Transparency)許諾ダイアログ(iOS)
✅ Rewarded(動画リワード)広告の表示と自動再ロード
✅ Interstitial(全画面)広告の表示
✅ Banner 広告(位置指定対応)
✅ Test Suite(デバッグ用ツール)起動対応
✅ 「Load is already called」(Code 627)対策

// Assets/Scripts/AdManager.cs
using UnityEngine;
using System;
using Unity.Services.LevelPlay;

#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif

#if UNITY_IOS
using Unity.Advertisement.IosSupport;
#endif

/// <summary>
/// Unity LevelPlay Mediation 9.x 兼用 広告マネージャ
/// - ATT許諾ダイアログ (iOS)
/// - Rewarded / Interstitial / Banner
/// - Test Suite 起動
/// - No fill & 連続Load防止(Code 627対策)
/// </summary>
public class AdManager : MonoBehaviour
{
    [Header("App Keys")]
    [SerializeField] private string androidAppKey = "YOUR_ANDROID_APP_KEY";
#pragma warning disable 0414
    [SerializeField] private string iosAppKey = "YOUR_IOS_APP_KEY";
#pragma warning restore 0414

    [Header("Rewarded Ad Unit IDs")]
    [SerializeField] private string rewardedAdUnitIdAndroid = "YOUR_REWARDED_AD_UNIT_ID_ANDROID";
    [SerializeField] private string rewardedAdUnitIdIOS = "YOUR_REWARDED_AD_UNIT_ID_IOS";

    [Header("Interstitial Ad Unit IDs")]
    [SerializeField] private string interstitialAdUnitIdAndroid = "YOUR_INTERSTITIAL_AD_UNIT_ID_ANDROID";
    [SerializeField] private string interstitialAdUnitIdIOS = "YOUR_INTERSTITIAL_AD_UNIT_ID_IOS";

    [Header("Banner Ad Unit IDs")]
    [SerializeField] private string bannerAdUnitIdAndroid = "YOUR_BANNER_AD_UNIT_ID_ANDROID";
    [SerializeField] private string bannerAdUnitIdIOS = "YOUR_BANNER_AD_UNIT_ID_IOS";

    [Header("Banner Settings")]
    [SerializeField] private bool showBannerOnStart = false;
    [SerializeField] private LevelPlayBannerPosition bannerPosition = LevelPlayBannerPosition.BottomCenter;

    [Header("Debug / Testing")]
    [Tooltip("Test Suite(メディエーションデバッガ)を有効化。Init前にメタデータを付与します。")]
    [SerializeField] private bool enableTestSuite = true;

    [Tooltip("Developmentビルド時、Init成功直後に自動でTest Suiteを起動します。")]
    [SerializeField] private bool autoLaunchTestSuiteInDevelopment = false;

    private static AdManager instance;
    public static AdManager Instance => instance;

    private bool isInitialized = false;
    private bool attChecked = false;

    private LevelPlayRewardedAd rewardedAd;
    private LevelPlayInterstitialAd interstitialAd;
    private LevelPlayBannerAd bannerAd;

    private bool rewardedLoading = false;
    private bool interstitialLoading = false;

    public event Action OnRewardedVideoCompleted;
    public event Action OnInterstitialClosed;
    public event Action OnBannerLoaded;

    private void Awake()
    {
        if (instance != null && instance != this) { Destroy(gameObject); return; }
        instance = this;
        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        RequestTrackingAuthorization();
    }

#if UNITY_EDITOR
    private void Update()
    {
        if (!enableTestSuite) return;
#if ENABLE_INPUT_SYSTEM
        if (Keyboard.current != null && Keyboard.current.tKey.wasPressedThisFrame)
            LaunchTestSuite();
#elif ENABLE_LEGACY_INPUT_MANAGER
        if (Input.GetKeyDown(KeyCode.T))
            LaunchTestSuite();
#endif
    }
#endif

#if UNITY_IOS && !UNITY_EDITOR
    private void RequestTrackingAuthorization()
    {
        var status = ATTrackingStatusBinding.GetAuthorizationTrackingStatus();
        if (status == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED)
        {
            Debug.Log("[ATT] ダイアログを表示します...");
            ATTrackingStatusBinding.RequestAuthorizationTracking((newStatus) =>
            {
                Debug.Log($"[ATT] 結果: {newStatus}");
                attChecked = true;
                InitializeAds();
            });
        }
        else
        {
            Debug.Log($"[ATT] 既に設定済み: {status}");
            attChecked = true;
            InitializeAds();
        }
    }
#else
    private void RequestTrackingAuthorization()
    {
        attChecked = true;
        InitializeAds();
    }
#endif

    public void InitializeAds()
    {
        if (isInitialized || !attChecked) return;

        string appKey = GetAppKey();
        if (string.IsNullOrEmpty(appKey) || appKey.Contains("YOUR_"))
        {
            Debug.LogError("App Keyが設定されていません!");
            return;
        }

        LevelPlay.OnInitSuccess += OnInitSuccess;
        LevelPlay.OnInitFailed += OnInitFailed;

        if (enableTestSuite)
        {
            LevelPlay.SetMetaData("is_test_suite", "enable");
            Debug.Log("[Ads][TestSuite] MetaData set (is_test_suite=enable)");
        }

        Debug.Log($"[Ads] Initialize platform={Application.platform} appKey={appKey}");
        LevelPlay.Init(appKey);
    }

    private void OnInitSuccess(LevelPlayConfiguration config)
    {
        isInitialized = true;
        Debug.Log("[Ads] LevelPlay initialized");

        if (enableTestSuite && autoLaunchTestSuiteInDevelopment && Debug.isDebugBuild)
            TryLaunchTestSuite();

        CreateAds();
    }

    private void OnInitFailed(LevelPlayInitError error)
    {
        Debug.LogError($"[Ads] LevelPlay初期化エラー: {error.ErrorMessage}");
    }

    public void LaunchTestSuite()
    {
        if (!enableTestSuite)
        {
            Debug.LogWarning("[Ads][TestSuite] enableTestSuite=false のため起動しません。");
            return;
        }
        TryLaunchTestSuite();
    }

    private void TryLaunchTestSuite()
    {
        if (!isInitialized)
        {
            Debug.LogWarning("[Ads][TestSuite] SDK未初期化のため起動できません。");
            return;
        }
        try
        {
            LevelPlay.LaunchTestSuite();
            Debug.Log("[Ads][TestSuite] Launched.");
        }
        catch (Exception e)
        {
            Debug.LogError($"[Ads][TestSuite] 起動エラー: {e.Message}");
        }
    }

    private void CreateAds()
    {
        if (!string.IsNullOrEmpty(GetRewardedAdUnitId()) && !GetRewardedAdUnitId().Contains("YOUR_"))
            CreateRewardedAd();

        if (!string.IsNullOrEmpty(GetInterstitialAdUnitId()) && !GetInterstitialAdUnitId().Contains("YOUR_"))
            CreateInterstitialAd();

        if (showBannerOnStart && !string.IsNullOrEmpty(GetBannerAdUnitId()) && !GetBannerAdUnitId().Contains("YOUR_"))
            LoadBanner();
    }

    private string GetAppKey()
    {
#if UNITY_ANDROID
        return androidAppKey;
#elif UNITY_IOS
        return iosAppKey;
#else
        return androidAppKey;
#endif
    }

    private string GetRewardedAdUnitId() => Application.platform == RuntimePlatform.IPhonePlayer ? rewardedAdUnitIdIOS : rewardedAdUnitIdAndroid;
    private string GetInterstitialAdUnitId() => Application.platform == RuntimePlatform.IPhonePlayer ? interstitialAdUnitIdIOS : interstitialAdUnitIdAndroid;
    private string GetBannerAdUnitId() => Application.platform == RuntimePlatform.IPhonePlayer ? bannerAdUnitIdIOS : bannerAdUnitIdAndroid;

    // ========================================
    // Rewarded Ad
    // ========================================
    private void CreateRewardedAd()
    {
        try
        {
            rewardedAd = new LevelPlayRewardedAd(GetRewardedAdUnitId());
            
            rewardedAd.OnAdLoaded += OnRewardedAdLoaded;
            rewardedAd.OnAdLoadFailed += OnRewardedAdLoadFailed;
            rewardedAd.OnAdDisplayed += OnRewardedAdDisplayed;
            rewardedAd.OnAdDisplayFailed += OnRewardedAdDisplayFailed;
            rewardedAd.OnAdClosed += OnRewardedAdClosed;
            rewardedAd.OnAdRewarded += OnRewardedAdRewarded;
            
            LoadRewardedAd();
        }
        catch (Exception e)
        {
            Debug.LogError($"[Ads][Rewarded] 作成エラー: {e.Message}");
        }
    }

    private void LoadRewardedAd()
    {
        if (rewardedAd == null || rewardedLoading) return;
        
        rewardedLoading = true;
        Debug.Log("[Ads][Rewarded] Loading...");
        rewardedAd.LoadAd();
    }

    public void ShowRewardedAd()
    {
        if (rewardedAd == null)
        {
            Debug.LogWarning("[Ads][Rewarded] まだ作成されていません。");
            return;
        }

        if (rewardedAd.IsAdReady())
        {
            Debug.Log("[Ads][Rewarded] Showing...");
            rewardedAd.ShowAd();
        }
        else
        {
            Debug.LogWarning("[Ads][Rewarded] 広告が準備できていません。");
            LoadRewardedAd();
        }
    }

    private void OnRewardedAdLoaded(LevelPlayAdInfo adInfo)
    {
        rewardedLoading = false;
        Debug.Log($"[Ads][Rewarded] Loaded: {adInfo}");
    }

    private void OnRewardedAdLoadFailed(LevelPlayAdError error)
    {
        rewardedLoading = false;
        Debug.LogWarning($"[Ads][Rewarded] Load Failed: {error.ErrorMessage} (Code: {error.ErrorCode})");
    }

    private void OnRewardedAdDisplayed(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Rewarded] Displayed: {adInfo}");
    }

    private void OnRewardedAdDisplayFailed(LevelPlayAdDisplayInfoError error)
    {
        Debug.LogError($"[Ads][Rewarded] Display Failed: {error.ErrorMessage}");
        LoadRewardedAd();
    }

    private void OnRewardedAdClosed(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Rewarded] Closed: {adInfo}");
        LoadRewardedAd();
    }

    private void OnRewardedAdRewarded(LevelPlayAdInfo adInfo, LevelPlayReward reward)
    {
        Debug.Log($"[Ads][Rewarded] ユーザーに報酬付与: {reward.Name} x {reward.Amount}");
        OnRewardedVideoCompleted?.Invoke();
    }

    // ========================================
    // Interstitial Ad
    // ========================================
    private void CreateInterstitialAd()
    {
        try
        {
            interstitialAd = new LevelPlayInterstitialAd(GetInterstitialAdUnitId());
            
            interstitialAd.OnAdLoaded += OnInterstitialAdLoaded;
            interstitialAd.OnAdLoadFailed += OnInterstitialAdLoadFailed;
            interstitialAd.OnAdDisplayed += OnInterstitialAdDisplayed;
            interstitialAd.OnAdDisplayFailed += OnInterstitialAdDisplayFailed;
            interstitialAd.OnAdClosed += OnInterstitialAdClosed;
            interstitialAd.OnAdInfoChanged += OnInterstitialAdInfoChanged;
            
            LoadInterstitialAd();
        }
        catch (Exception e)
        {
            Debug.LogError($"[Ads][Interstitial] 作成エラー: {e.Message}");
        }
    }

    private void LoadInterstitialAd()
    {
        if (interstitialAd == null || interstitialLoading) return;
        
        interstitialLoading = true;
        Debug.Log("[Ads][Interstitial] Loading...");
        interstitialAd.LoadAd();
    }

    public void ShowInterstitialAd()
    {
        if (interstitialAd == null)
        {
            Debug.LogWarning("[Ads][Interstitial] まだ作成されていません。");
            return;
        }

        if (interstitialAd.IsAdReady())
        {
            Debug.Log("[Ads][Interstitial] Showing...");
            interstitialAd.ShowAd();
        }
        else
        {
            Debug.LogWarning("[Ads][Interstitial] 広告が準備できていません。");
            LoadInterstitialAd();
        }
    }

    private void OnInterstitialAdLoaded(LevelPlayAdInfo adInfo)
    {
        interstitialLoading = false;
        Debug.Log($"[Ads][Interstitial] Loaded: {adInfo}");
    }

    private void OnInterstitialAdLoadFailed(LevelPlayAdError error)
    {
        interstitialLoading = false;
        Debug.LogWarning($"[Ads][Interstitial] Load Failed: {error.ErrorMessage} (Code: {error.ErrorCode})");
    }

    private void OnInterstitialAdDisplayed(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Interstitial] Displayed: {adInfo}");
    }

    private void OnInterstitialAdDisplayFailed(LevelPlayAdDisplayInfoError error)
    {
        Debug.LogError($"[Ads][Interstitial] Display Failed: {error.ErrorMessage}");
        LoadInterstitialAd();
    }

    private void OnInterstitialAdClosed(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Interstitial] Closed: {adInfo}");
        OnInterstitialClosed?.Invoke();
        LoadInterstitialAd();
    }

    private void OnInterstitialAdInfoChanged(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Interstitial] Info Changed: {adInfo}");
    }

    // ========================================
    // Banner Ad
    // ========================================
    public void LoadBanner()
    {
        if (bannerAd != null)
        {
            Debug.LogWarning("[Ads][Banner] 既にロード済みです。");
            return;
        }

        try
        {
            LevelPlayAdSize adSize = LevelPlayAdSize.BANNER;
            LevelPlayBannerAdViewListener listener = new LevelPlayBannerAdViewListener();
            
            listener.OnAdLoaded += OnBannerAdLoaded;
            listener.OnAdLoadFailed += OnBannerAdLoadFailed;
            listener.OnAdDisplayed += OnBannerAdDisplayed;
            listener.OnAdDisplayFailed += OnBannerAdDisplayFailed;
            listener.OnAdClicked += OnBannerAdClicked;
            listener.OnAdCollapsed += OnBannerAdCollapsed;
            listener.OnAdExpanded += OnBannerAdExpanded;
            listener.OnAdLeftApplication += OnBannerAdLeftApplication;
            
            bannerAd = new LevelPlayBannerAd(GetBannerAdUnitId(), adSize, bannerPosition, listener);
            
            Debug.Log("[Ads][Banner] Loading...");
            bannerAd.LoadAd();
        }
        catch (Exception e)
        {
            Debug.LogError($"[Ads][Banner] 作成エラー: {e.Message}");
        }
    }

    public void ShowBanner()
    {
        if (bannerAd == null)
        {
            Debug.LogWarning("[Ads][Banner] まだロードされていません。LoadBanner() を先に呼んでください。");
            return;
        }
        
        Debug.Log("[Ads][Banner] Showing...");
        bannerAd.ShowAd();
    }

    public void HideBanner()
    {
        if (bannerAd == null) return;
        
        Debug.Log("[Ads][Banner] Hiding...");
        bannerAd.HideAd();
    }

    public void DestroyBanner()
    {
        if (bannerAd == null) return;
        
        Debug.Log("[Ads][Banner] Destroying...");
        bannerAd.DestroyAd();
        bannerAd = null;
    }

    private void OnBannerAdLoaded(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Banner] Loaded: {adInfo}");
        OnBannerLoaded?.Invoke();
    }

    private void OnBannerAdLoadFailed(LevelPlayAdError error)
    {
        Debug.LogError($"[Ads][Banner] Load Failed: {error.ErrorMessage} (Code: {error.ErrorCode})");
    }

    private void OnBannerAdDisplayed(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Banner] Displayed: {adInfo}");
    }

    private void OnBannerAdDisplayFailed(LevelPlayAdDisplayInfoError error)
    {
        Debug.LogError($"[Ads][Banner] Display Failed: {error.ErrorMessage}");
    }

    private void OnBannerAdClicked(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Banner] Clicked: {adInfo}");
    }

    private void OnBannerAdCollapsed(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Banner] Collapsed: {adInfo}");
    }

    private void OnBannerAdExpanded(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Banner] Expanded: {adInfo}");
    }

    private void OnBannerAdLeftApplication(LevelPlayAdInfo adInfo)
    {
        Debug.Log($"[Ads][Banner] Left Application: {adInfo}");
    }
}

4-2. AdManager の使い方

  1. 空の GameObject を作成し、AdManager スクリプトをアタッチ

  2. Inspector で以下を設定:

    • App Keys: Dashboard で確認した Android / iOS の App Key
    • Ad Unit IDs: Dashboard で作成した各広告形式の Ad Unit ID
    • Banner Settings: 起動時に表示する場合は Show Banner On Start をチェック
    • Debug / Testing: 開発中は Enable Test Suite をチェック
  3. 広告を表示するには、他のスクリプトから以下のように呼び出します:

// リワード広告を表示
AdManager.Instance.ShowRewardedAd();

// インタースティシャル広告を表示
AdManager.Instance.ShowInterstitialAd();

// バナー広告を表示
AdManager.Instance.LoadBanner();
AdManager.Instance.ShowBanner();

// バナー広告を非表示
AdManager.Instance.HideBanner();

// バナー広告を削除
AdManager.Instance.DestroyBanner();
  1. イベントをリスニングする:
void Start()
{
    AdManager.Instance.OnRewardedVideoCompleted += HandleRewardGranted;
    AdManager.Instance.OnInterstitialClosed += HandleInterstitialClosed;
    AdManager.Instance.OnBannerLoaded += HandleBannerLoaded;
}

void HandleRewardGranted()
{
    Debug.Log("ユーザーに報酬を付与!");
    // ゲーム内通貨を付与するなど
}

void HandleInterstitialClosed()
{
    Debug.Log("インタースティシャル広告が閉じられた");
    // 次のシーンへ遷移するなど
}

void HandleBannerLoaded()
{
    Debug.Log("バナー広告が読み込まれた");
    AdManager.Instance.ShowBanner();
}

5. iOS ビルド設定の自動化

5-1. PostBuild スクリプトの作成

iOS ビルド後に Info.plist を自動整備するスクリプトを作成します。

フォルダ構成:

Assets/
 └─ Editor/
     └─ OnPostBuildProcess.cs

注意: Editor フォルダ内に配置することで、ビルド時のみ実行されます(ゲーム実行時には含まれません)。

5-2. スクリプトの実装

// Assets/Editor/OnPostBuildProcess.cs
#if UNITY_IOS
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using UnityEngine;
using System.IO;
using System.Linq;

public static class OnPostBuildProcess
{
    // ATTの説明文
    private const string TrackingUsageText =
        "当アプリでは広告の最適化と分析のために、デバイス識別子を利用する場合があります。";

    // ATSポリシー(開発中はtrueを推奨、本番環境ではfalseに変更してドメイン指定)
    private static readonly bool AtsWideOpen = true;

    // Strict運用時に許可するドメイン
    private static readonly string[] ExceptionDomains = new string[0];

    // SKAdNetwork ID を追加で指定(Network Manager の自動追加で不足する場合のみ)
    private static readonly string[] ExtraSkAdNetworkIds = new[]
    {
        "k674qkevps.skadnetwork",
        "294l99pt4k.skadnetwork",
        "m8dbw4sv7c.skadnetwork",
        "2u9pt9hc89.skadnetwork",
        "p78axxw29g.skadnetwork",
        "424m5254lk.skadnetwork",
        "238da6jt44.skadnetwork",
        "ydx93a7ass.skadnetwork",
        "s39g8k73mm.skadnetwork",
        "v9wttpbfk9.skadnetwork",
        "44jx6755aq.skadnetwork",
        "578prtvx9j.skadnetwork",
        "yclnxrl5pm.skadnetwork",
        "k6y4y55b64.skadnetwork",
        "5a6flpkh64.skadnetwork",
        "3rd42ekr43.skadnetwork",
        "f38h382jlk.skadnetwork",
        "cstr6suwn9.skadnetwork",
        "ppxm28t8ap.skadnetwork",
        "f7s53z58qe.skadnetwork",
        "zq492l623r.skadnetwork",
        "t38b2kh725.skadnetwork",
        "mp6xlyr22a.skadnetwork",
        "3qy4746246.skadnetwork",
        "5l3tpt7t6e.skadnetwork",
        "c6k4g5qg8m.skadnetwork",
        "w9q455wk68.skadnetwork",
        "4pfyvq9l8r.skadnetwork",
        "wzmmz9fp6w.skadnetwork",
        "8s468mfl3y.skadnetwork",
        "9nlqeag3gk.skadnetwork",
        "hs6bdukanm.skadnetwork",
        "9rd848q2bz.skadnetwork",
        "22mmun2rn5.skadnetwork",
        "f73kdq92p3.skadnetwork",
        "tl55sbb4fm.skadnetwork",
        "prcb7njmu6.skadnetwork",
        "n9x2a789qt.skadnetwork",
        "97r2b46745.skadnetwork",
        "zmvfpc5aq8.skadnetwork",
        "av6w8kgt66.skadnetwork",
        "uw77j35x4d.skadnetwork",
        "pwa73g5rt2.skadnetwork",
        "e5fvkxwrpn.skadnetwork",
        "klf5c3l5u5.skadnetwork",
        "xga6mpmplv.skadnetwork",
        "wg4vff78zm.skadnetwork",
        "lr83yxwka7.skadnetwork",
        "5tjdwbrq8w.skadnetwork",
        "7ug5zh24hu.skadnetwork",
        "glqzh8vgby.skadnetwork",
        "feyaarzu9v.skadnetwork",
        "6yxyv74ff7.skadnetwork",
        "mqn7fxpca7.skadnetwork",
        "vhf287vqwu.skadnetwork",
        "g6gcrrvk4p.skadnetwork",
        "v79kvwwj4g.skadnetwork",
        "32z4fx6l9h.skadnetwork",
        "2fnua5tdw4.skadnetwork",
        "a8cz6cu7e5.skadnetwork",
        "v72qych5uu.skadnetwork",
        "a2p9lx4jpn.skadnetwork",
        "5f5u5tfb26.skadnetwork",
        "4fzdc2evr5.skadnetwork",
        "5lm9lj6jb7.skadnetwork",
        "4w7y6s5ca2.skadnetwork",
        "3sh42y64q3.skadnetwork",
        "4468km3ulz.skadnetwork",
        "mj797d8u6f.skadnetwork",
        "488r3q3dtq.skadnetwork",
        "mlmmfzh3r3.skadnetwork",
        "9t245vhmpl.skadnetwork",
        "x44k69ngh6.skadnetwork",
        "kbd757ywx3.skadnetwork",
        "4dzt52r2t5.skadnetwork"
    };

    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
    {
        if (target != BuildTarget.iOS) return;

        var plistPath = Path.Combine(pathToBuiltProject, "Info.plist");
        var plist = new PlistDocument();
        plist.ReadFromFile(plistPath);
        var root = plist.root;

        int skAdded = 0;

        // --- 1) ATT の使用理由 ---
        const string kATTKey = "NSUserTrackingUsageDescription";
        if (!root.values.ContainsKey(kATTKey))
        {
            root.SetString(kATTKey, TrackingUsageText);
            Debug.Log("[PostBuild][ATT] NSUserTrackingUsageDescription added.");
        }
        else
        {
            Debug.Log("[PostBuild][ATT] NSUserTrackingUsageDescription already exists (kept).");
        }

        // --- 2) ATS (App Transport Security) ---
        const string kATSKey = "NSAppTransportSecurity";
        PlistElementDict ats = root.values.ContainsKey(kATSKey)
            ? root[kATSKey].AsDict()
            : root.CreateDict(kATSKey);

        if (AtsWideOpen)
        {
            ats.SetBoolean("NSAllowsArbitraryLoads", true);
            Debug.Log("[PostBuild][ATS] Wide open (NSAllowsArbitraryLoads=true).");
        }
        else
        {
            // 任意許可OFF + 例外ドメイン列挙
            ats.SetBoolean("NSAllowsArbitraryLoads", false);

            var ex = ats.values.ContainsKey("NSExceptionDomains")
                ? ats["NSExceptionDomains"].AsDict()
                : ats.CreateDict("NSExceptionDomains");

            foreach (var domain in ExceptionDomains.Where(d => !string.IsNullOrWhiteSpace(d)))
            {
                if (!ex.values.ContainsKey(domain))
                {
                    var d = ex.CreateDict(domain);
                    d.SetBoolean("NSIncludesSubdomains", true);
                    d.SetBoolean("NSExceptionAllowsInsecureHTTPLoads", true);
                    d.SetBoolean("NSRequiresCertificateTransparency", false);
                }
            }

            Debug.Log($"[PostBuild][ATS] Strict with {ExceptionDomains.Length} exception domain(s).");
        }

        // --- 3) SKAdNetworkItems(追加がある場合のみ) ---
        if (ExtraSkAdNetworkIds.Length > 0)
        {
            PlistElementArray skArray = root.values.ContainsKey("SKAdNetworkItems")
                ? root["SKAdNetworkItems"].AsArray()
                : root.CreateArray("SKAdNetworkItems");

            foreach (var raw in ExtraSkAdNetworkIds)
            {
                var id = (raw ?? "").Trim();
                if (string.IsNullOrEmpty(id)) continue;
                if (!ContainsSkAdId(skArray, id))
                {
                    var dict = skArray.AddDict();
                    dict.SetString("SKAdNetworkIdentifier", id);
                    skAdded++;
                }
            }
        }

        // --- 保存 ---
        File.WriteAllText(plistPath, plist.WriteToString());
        Debug.Log($"[PostBuild] Info.plist updated. SKAN added: {skAdded}, ATS mode: {(AtsWideOpen ? "Wide" : "Strict")}.");
    }

    private static bool ContainsSkAdId(PlistElementArray arr, string id)
    {
        for (int i = 0; i < arr.values.Count; i++)
        {
            var d = arr.values[i].AsDict();
            if (d != null &&
                d.values.ContainsKey("SKAdNetworkIdentifier") &&
                d["SKAdNetworkIdentifier"].AsString() == id)
            {
                return true;
            }
        }
        return false;
    }
}
#endif

5-3. スクリプトの動作

このスクリプトは iOS ビルド完了後に自動で実行され、以下を Info.plist に追加します:

  1. NSUserTrackingUsageDescription: ATT 許諾ダイアログの説明文
  2. NSAppTransportSecurity: HTTP 通信を許可(開発中)
  3. SKAdNetworkItems: 広告ネットワークの SKAdNetwork ID

6. XCodeでビルドを行う

  1. teamを選択
  2. In-App Purchaseをゴミ箱ボタンをクリックし、削除する(今回は使わないため)
  3. ビルドする端末を選択し、Playボタンをクリックする

7. テスト方法

7-1. Test Suite の起動

開発ビルドで実機テストを行う際は、Test Suite を使用してメディエーションの動作を確認できます。

起動方法:

  • Unity エディタ: T キーを押す
  • 実機: AdManager の Inspector で Auto Launch Test Suite In Development をチェック

Test Suite では以下を確認できます:

  • 各広告ネットワークの初期化状態
  • 広告のロード状況
  • eCPM(予測収益)
  • 実際の広告表示テスト

7-2. テスト広告の表示確認

  1. LevelPlay Dashboard でテスト端末を登録済みか確認
  2. Unity でテストモードを有効化
  3. ビルドして実機で確認
  4. 広告が表示されたら、右上または左上に "Test" の表示があることを確認

8. トラブルシューティング

よくあるエラーと対処法

エラー 627: "Load is already called"

原因: 前回の Load が完了する前に再度 Load が呼ばれた

対処法: AdManager では rewardedLoading / interstitialLoading フラグで二重ロードを防止しています。

広告が表示されない

確認ポイント:

  1. App Key と Ad Unit ID が正しく設定されているか
  2. テスト端末が LevelPlay Dashboard に登録されているか
  3. Unity のテストモードが有効か
  4. Test Suite でネットワークが初期化されているか
  5. Console ログでエラーが出ていないか

iOS で ATT ダイアログが表示されない

確認ポイント:

  1. Advertisement iOS Support パッケージがインストールされているか
  2. Info.plist に NSUserTrackingUsageDescription が含まれているか
  3. iOS 設定で「トラッキング」が許可されているか
  4. 既に許諾済みの端末では再表示されない(設定アプリでリセット可能)

確認ポイント:

  1. LoadBanner() を先に呼んでから ShowBanner() を呼んでいるか
  2. Banner Ad Unit ID が正しく設定されているか
  3. Console で OnBannerAdLoaded が呼ばれているか確認

9. まとめ

この記事では、Unity LevelPlay Mediation 9.0.0でテスト環境でテスト広告を表示するところまでを実装しました。
実環境では設定を更に変える必要なところがあるので注意が必要です。

Discussion