Unity LevelPlayコード実装方法

Unityでの広告実装において、UnityLevelPlayという新しいものを使ってみようとしたのですが、
コードで少し困惑したところがあったので、備忘録です。

# UnityLevelPlayとは？
* Unity公式のメディエーションツール
    * IronSource製
    * 複数のアドネットワークSDKをまとめるラッパーみたいなもの
* UnityがIronSourceと合併した後に提供されたもの
https://www.is.com/ja/mediation/ 
* 元々は、UnityMediationというものがあったがこちらは統合により開発中止
  * また、以下の記載があるため、UnityMediationを使っている方は移行必須かと思われます。
  `2023 年 10 月 1 日に Unity Mediation のサポートを終了し、広告配信を停止します。`

# 前提
* Unity2021.3.10f1
* 元々アプリにはUnityAdsを実装していた。
* CPMの改善対応として、メディエーションツールの採用をすることになった。
* UnityやIronSourceのダッシュボード上での設定は、今回触れません。

# 今回起きた問題

UnityLevelPlayを導入するにあたって、
リワード広告の視聴広告完了後のイベントを呼び出す処理を修正したが、上手く動作しませんでした。

原因としては、メソッド名からなんとなく予測して動画視聴完了後に呼ばれそうなところに書いたからでした。
また、UnityLevelPlayはUnityAdsと違いエディター環境では何も表示されないため、実機でしか動作確認することができません。

最終的には以下のコードになりました。
SDKのデモスクリプトを元に作成しています。
修正前は、`RewardedVideoAdEndedEvent()`で広告動画終了時のイベントを呼んでいたのですが、
`RewardedVideoAdClosedEvent`でイベントを呼ぶようにすることで正常に動作することが確認できました。

<details><summary>最終的なコード</summary><div>

```csharp
using System;
using UnityEngine;
using Unity.Services.Core;
using UnityEngine.Events;

public class AdsManager : MonoBehaviour
{
    //================================================================================
    // インスペクタ
    //================================================================================
#if UNITY_ANDROID
    private string ironSourceAppKey = "XXXXXX";
#elif UNITY_IPHONE
    private string ironSourceAppKey = "XXXXXX";
#else
    private string ironSourceAppKey = "";
#endif
    //================================================================================
    // ローカル
    //================================================================================
    private UnityAction onCompleteAd = null;
    private UnityAction onFailedAd = null;
    //================================================================================
    // メソッド
    //================================================================================
    //================================================================================
    // 広告初期化
    //================================================================================
    public void InitializeAds()
    {
        UnityServices.InitializeAsync();
        IronSource.Agent.validateIntegration();
        // SDK init
        IronSource.Agent.init(ironSourceAppKey);
        InitAdsEvent();
    }

    void InitAdsEvent()
    {
        //Add Init Event
        IronSourceEvents.onSdkInitializationCompletedEvent += SdkInitializationCompletedEvent;

        //Add Rewarded Video Events
        IronSourceEvents.onRewardedVideoAdOpenedEvent += RewardedVideoAdOpenedEvent;
        IronSourceEvents.onRewardedVideoAdClosedEvent += RewardedVideoAdClosedEvent;
        IronSourceEvents.onRewardedVideoAvailabilityChangedEvent += RewardedVideoAvailabilityChangedEvent;
        IronSourceEvents.onRewardedVideoAdStartedEvent += RewardedVideoAdStartedEvent;
        IronSourceEvents.onRewardedVideoAdEndedEvent += RewardedVideoAdEndedEvent;
        IronSourceEvents.onRewardedVideoAdRewardedEvent += RewardedVideoAdRewardedEvent;
        IronSourceEvents.onRewardedVideoAdShowFailedEvent += RewardedVideoAdShowFailedEvent;
        IronSourceEvents.onRewardedVideoAdClickedEvent += RewardedVideoAdClickedEvent;

        // Add Interstitial Events
        IronSourceEvents.onInterstitialAdReadyEvent += InterstitialAdReadyEvent;
        IronSourceEvents.onInterstitialAdLoadFailedEvent += InterstitialAdLoadFailedEvent;
        IronSourceEvents.onInterstitialAdShowSucceededEvent += InterstitialAdShowSucceededEvent;
        IronSourceEvents.onInterstitialAdShowFailedEvent += InterstitialAdShowFailedEvent;
        IronSourceEvents.onInterstitialAdClickedEvent += InterstitialAdClickedEvent;
        IronSourceEvents.onInterstitialAdOpenedEvent += InterstitialAdOpenedEvent;
        IronSourceEvents.onInterstitialAdClosedEvent += InterstitialAdClosedEvent;

        // Add Banner Events
        IronSourceEvents.onBannerAdLoadedEvent += BannerAdLoadedEvent;
        IronSourceEvents.onBannerAdLoadFailedEvent += BannerAdLoadFailedEvent;
        IronSourceEvents.onBannerAdClickedEvent += BannerAdClickedEvent;
        IronSourceEvents.onBannerAdScreenPresentedEvent += BannerAdScreenPresentedEvent;
        IronSourceEvents.onBannerAdScreenDismissedEvent += BannerAdScreenDismissedEvent;
        IronSourceEvents.onBannerAdLeftApplicationEvent += BannerAdLeftApplicationEvent;

        //Add AdInfo Rewarded Video Events
        IronSourceRewardedVideoEvents.onAdOpenedEvent += RewardedVideoOnAdOpenedEvent;
        IronSourceRewardedVideoEvents.onAdClosedEvent += RewardedVideoOnAdClosedEvent;
        IronSourceRewardedVideoEvents.onAdAvailableEvent += RewardedVideoOnAdAvailable;
        IronSourceRewardedVideoEvents.onAdUnavailableEvent += RewardedVideoOnAdUnavailable;
        IronSourceRewardedVideoEvents.onAdShowFailedEvent += RewardedVideoOnAdShowFailedEvent;
        IronSourceRewardedVideoEvents.onAdRewardedEvent += RewardedVideoOnAdRewardedEvent;
        IronSourceRewardedVideoEvents.onAdClickedEvent += RewardedVideoOnAdClickedEvent;

        //Add AdInfo Interstitial Events
        IronSourceInterstitialEvents.onAdReadyEvent += InterstitialOnAdReadyEvent;
        IronSourceInterstitialEvents.onAdLoadFailedEvent += InterstitialOnAdLoadFailed;
        IronSourceInterstitialEvents.onAdOpenedEvent += InterstitialOnAdOpenedEvent;
        IronSourceInterstitialEvents.onAdClickedEvent += InterstitialOnAdClickedEvent;
        IronSourceInterstitialEvents.onAdShowSucceededEvent += InterstitialOnAdShowSucceededEvent;
        IronSourceInterstitialEvents.onAdShowFailedEvent += InterstitialOnAdShowFailedEvent;
        IronSourceInterstitialEvents.onAdClosedEvent += InterstitialOnAdClosedEvent;

        //Add AdInfo Banner Events
        IronSourceBannerEvents.onAdLoadedEvent += BannerOnAdLoadedEvent;
        IronSourceBannerEvents.onAdLoadFailedEvent += BannerOnAdLoadFailedEvent;
        IronSourceBannerEvents.onAdClickedEvent += BannerOnAdClickedEvent;
        IronSourceBannerEvents.onAdScreenPresentedEvent += BannerOnAdScreenPresentedEvent;
        IronSourceBannerEvents.onAdScreenDismissedEvent += BannerOnAdScreenDismissedEvent;
        IronSourceBannerEvents.onAdLeftApplicationEvent += BannerOnAdLeftApplicationEvent;
    }

    void OnApplicationPause(bool isPaused)
    {
        Debug.Log("unity-script: OnApplicationPause = " + isPaused);
        IronSource.Agent.onApplicationPause(isPaused);
    }

    //=================================================================================
    // 初期化コールバック
    //=================================================================================
    /// <summary>
    /// 広告初期化
    /// </summary>
    void SdkInitializationCompletedEvent()
    {
        // NOTE: 一番最初に呼び出される
        Debug.Log("unity-script: I got SdkInitializationCompletedEvent");
    }

    //=================================================================================
    // リワード広告イベント
    //=================================================================================
    public void LoadRewardAd(UnityAction onCompleteAdCallbak, UnityAction onFailedAd)
    {
        // IMPORTANT! Only load content AFTER initialization (in this example, initialization is handled in a different script).
        Debug.Log($"Loading Ad: {rewardId}");
        this.onCompleteAd = onCompleteAdCallbak;
        this.onFailedAd = onFailedAd;
        ShowRewardedAd();
    }

    public void ShowRewardedAd()
    {
        if (IronSource.Agent.isRewardedVideoAvailable())
        {
            IronSource.Agent.showRewardedVideo();
        }
        else
        {
            Debug.Log("unity-script: IronSource.Agent.isRewardedVideoAvailable - False");
#if UNITY_EDITOR
            onCompleteAd?.Invoke();
            onCompleteAd = null;
#else
            onFailedAd?.Invoke();
            onFailedAd = null;
#endif
        }
    }

    void RewardedVideoAdOpenedEvent()
    {
        Debug.Log("unity-script: I got RewardedVideoAdOpenedEvent");
    }

    void RewardedVideoAdRewardedEvent(IronSourcePlacement ssp)
    {
        // ログの例
        //  unity-script: I got RewardedVideoAdRewardedEvent, amount = 1 name = Virtual Item
        Debug.Log("unity-script: I got RewardedVideoAdRewardedEvent, amount = " + ssp.getRewardAmount() + " name = " + ssp.getRewardName());
    }

    void RewardedVideoAdClosedEvent()
    {
        // NOTE: リワード広告の右上のバツを押した時に呼ばれる
        Debug.Log("unity-script: I got RewardedVideoAdClosedEvent");
        onCompleteAd?.Invoke();
        onCompleteAd = null;
    }

    void RewardedVideoAdStartedEvent()
    {
        Debug.Log("unity-script: I got RewardedVideoAdStartedEvent");
    }

    void RewardedVideoAdEndedEvent()
    {
        Debug.Log("unity-script: I got RewardedVideoAdEndedEvent");
    }

    void RewardedVideoAvailabilityChangedEvent(bool canShowAd)
    {
        // 利用可能な広告があるかどうか
        // 初期化時とリワード広告の時に呼ばれる
        Debug.Log("unity-script: I got RewardedVideoAvailabilityChangedEvent, value = " + canShowAd);
    }

    void RewardedVideoAdShowFailedEvent(IronSourceError error)
    {
        Debug.Log("unity-script: I got RewardedVideoAdShowFailedEvent, code :  " + error.getCode() + ", description : " + error.getDescription());
        onFailedAd?.Invoke();
        onFailedAd = null;
    }

    void RewardedVideoAdClickedEvent(IronSourcePlacement ssp)
    {
        Debug.Log("unity-script: I got RewardedVideoAdClickedEvent, name = " + ssp.getRewardName());
    }

    void RewardedVideoOnAdOpenedEvent(IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got RewardedVideoOnAdOpenedEvent With AdInfo " + adInfo.ToString());
    }
    void RewardedVideoOnAdClosedEvent(IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got RewardedVideoOnAdClosedEvent With AdInfo " + adInfo.ToString());
    }
    void RewardedVideoOnAdAvailable(IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got RewardedVideoOnAdAvailable With AdInfo " + adInfo.ToString());
    }
    void RewardedVideoOnAdUnavailable()
    {
        // NOTE: リワード広告を用意できなかった時に呼び出される(通信接続なし等)
        Debug.Log("unity-script: I got RewardedVideoOnAdUnavailable");
    }
    void RewardedVideoOnAdShowFailedEvent(IronSourceError ironSourceError, IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got RewardedVideoAdOpenedEvent With Error" + ironSourceError.ToString() + "And AdInfo " + adInfo.ToString());
        onFailedAd?.Invoke();
        onFailedAd = null;
    }
    void RewardedVideoOnAdRewardedEvent(IronSourcePlacement ironSourcePlacement, IronSourceAdInfo adInfo)
    {
        // ログの例
        // unity-script: I got ReardedVideoOnAdAvailable With AdInfo IronSourceAdInfo {auctionId='088414f0-db94-11ed-9ef3-814f4df973b9_558563228', adUnit='rewarded_video', country='JP', ab='A', segmentName='', adNetwork='ironsource', instanceName='Bidding', instanceId='13499851', revenue=0.099, precision='BID', lifetimeRevenue=0.2976825, encryptedCPM=''}
        Debug.Log("unity-script: I got RewardedVideoOnAdRewardedEvent With Placement" + ironSourcePlacement.ToString() + "And AdInfo " + adInfo.ToString());
    }
    void RewardedVideoOnAdClickedEvent(IronSourcePlacement ironSourcePlacement, IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got RewardedVideoOnAdClickedEvent With Placement" + ironSourcePlacement.ToString() + "And AdInfo " + adInfo.ToString());
    }

    //=================================================================================
    // インタースティシャル広告イベント
    //=================================================================================
    public void LoadInterstitialAd()
    {
        IronSource.Agent.loadInterstitial();
    }

    public void ShowInterstitialAd(UnityAction onCompleteAdCallback, UnityAction onFailedAd)
    {
        if (IronSource.Agent.isInterstitialReady())
        {
            this.onCompleteAd = onCompleteAdCallback;
            this.onFailedAd = onFailedAd;
            IronSource.Agent.showInterstitial();
        }
        else
        {
            Debug.Log("unity-script: IronSource.Agent.isInterstitialReady - False");
            onFailedAd?.Invoke();
            onFailedAd = null;
        }
    }

    void InterstitialAdReadyEvent()
    {
        // インタースティシャル広告開始時に呼び出される
        Debug.Log("unity-script: I got InterstitialAdReadyEvent");
    }

    void InterstitialAdLoadFailedEvent(IronSourceError error)
    {
        Debug.Log("unity-script: I got InterstitialAdLoadFailedEvent, code: " + error.getCode() + ", description : " + error.getDescription());
        onFailedAd?.Invoke();
        onFailedAd = null;
    }

    void InterstitialAdShowSucceededEvent()
    {
        Debug.Log("unity-script: I got InterstitialAdShowSucceededEvent");
    }

    void InterstitialAdShowFailedEvent(IronSourceError error)
    {
        Debug.Log("unity-script: I got InterstitialAdShowFailedEvent, code :  " + error.getCode() + ", description : " + error.getDescription());
        onFailedAd?.Invoke();
        onFailedAd = null;
    }

    void InterstitialAdClickedEvent()
    {
        Debug.Log("unity-script: I got InterstitialAdClickedEvent");
    }

    void InterstitialAdOpenedEvent()
    {
        Debug.Log("unity-script: I got InterstitialAdOpenedEvent");
    }

    void InterstitialAdClosedEvent()
    {
        // NOTE:インタースティシャル広告の右上の×ボタンを押した時に呼び出される
        Debug.Log("unity-script: I got InterstitialAdClosedEvent");
        onCompleteAd?.Invoke();
        onCompleteAd = null;
    }

    void InterstitialOnAdReadyEvent(IronSourceAdInfo adInfo)
    {
        // NOTE: インタースティシャル広告開始時に呼び出される
        // ログの例
        // unity-script: I got InterstitialOnAdReadyEvent With AdInfo IronSourceAdInfo {auctionId='53a62c50-db96-11ed-8837-59c0b9849e65_933730263', adUnit='interstitial', country='JP', ab='A', segmentName='', adNetwork='unityads', instanceName='Default', instanceId='Interstitial_Android', revenue=0.0011658, precision='CPM', lifetimeRevenue=0.2001808, encryptedCPM=''}
        Debug.Log("unity-script: I got InterstitialOnAdReadyEvent With AdInfo " + adInfo.ToString());
    }

    void InterstitialOnAdLoadFailed(IronSourceError ironSourceError)
    {
        // NOTE: インタースティシャル広告が用意できなかった時に呼び出される(通信接続なし等)
        // ログの例
        // unity-script: I got InterstitialOnAdLoadFailed With Error 508 : Interstitial - init() had failed
        Debug.Log("unity-script: I got InterstitialOnAdLoadFailed With Error " + ironSourceError.ToString());
    }

    void InterstitialOnAdOpenedEvent(IronSourceAdInfo adInfo)
    {
        // NOTE: インタースティシャル広告を閉じる時に呼び出される
        // ログの例
        // unity-script: I got InterstitialOnAdOpenedEvent With AdInfo IronSourceAdInfo {auctionId='53a62c50-db96-11ed-8837-59c0b9849e65_933730263', adUnit='interstitial', country='JP', ab='A', segmentName='', adNetwork='unityads', instanceName='Default', instanceId='Interstitial_Android', revenue=0.0011658, precision='CPM', lifetimeRevenue=0.2001808, encryptedCPM=''}
        Debug.Log("unity-script: I got InterstitialOnAdOpenedEvent With AdInfo " + adInfo.ToString());
    }

    void InterstitialOnAdClickedEvent(IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got InterstitialOnAdClickedEvent With AdInfo " + adInfo.ToString());
    }

    void InterstitialOnAdShowSucceededEvent(IronSourceAdInfo adInfo)
    {
        // NOTE: インタースティシャル広告が閉じる時に呼び出される
        Debug.Log("unity-script: I got InterstitialOnAdShowSucceededEvent With AdInfo " + adInfo.ToString());
    }

    void InterstitialOnAdShowFailedEvent(IronSourceError ironSourceError, IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got InterstitialOnAdShowFailedEvent With Error " + ironSourceError.ToString() + " And AdInfo " + adInfo.ToString());
    }

    void InterstitialOnAdClosedEvent(IronSourceAdInfo adInfo)
    {
        // NOTE: 閉じる時に呼び出される
        // ログの例
        // unity-script: I got InterstitialOnAdClosedEvent With AdInfo IronSourceAdInfo {auctionId='53a62c50-db96-11ed-8837-59c0b9849e65_933730263', adUnit='interstitial', country='JP', ab='A', segmentName='', adNetwork='unityads', instanceName='Default', instanceId='Interstitial_Android', revenue=0.0011658, precision='CPM', lifetimeRevenue=0.2001808, encryptedCPM=''}
        Debug.Log("unity-script: I got InterstitialOnAdClosedEvent With AdInfo " + adInfo.ToString());
    }

    //=================================================================================
    // バナー広告イベント
    //=================================================================================
    public void LoadBanner(IronSourceBannerPosition bannerPosition = IronSourceBannerPosition.BOTTOM)
    {
        IronSource.Agent.loadBanner(IronSourceBannerSize.BANNER, bannerPosition);
    }

    public void HideBannerAd()
    {
        IronSource.Agent.destroyBanner();
    }

    void BannerAdLoadedEvent()
    {
        Debug.Log("unity-script: I got BannerAdLoadedEvent");
    }

    void BannerAdLoadFailedEvent(IronSourceError error)
    {
        Debug.Log("unity-script: I got BannerAdLoadFailedEvent, code: " + error.getCode() + ", description : " + error.getDescription());
    }

    void BannerAdClickedEvent()
    {
        Debug.Log("unity-script: I got BannerAdClickedEvent");
    }

    void BannerAdScreenPresentedEvent()
    {
        Debug.Log("unity-script: I got BannerAdScreenPresentedEvent");
    }

    void BannerAdScreenDismissedEvent()
    {
        Debug.Log("unity-script: I got BannerAdScreenDismissedEvent");
    }

    void BannerAdLeftApplicationEvent()
    {
        Debug.Log("unity-script: I got BannerAdLeftApplicationEvent");
    }

    void BannerOnAdLoadedEvent(IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got BannerOnAdLoadedEvent With AdInfo " + adInfo.ToString());
    }

    void BannerOnAdLoadFailedEvent(IronSourceError ironSourceError)
    {
        Debug.Log("unity-script: I got BannerOnAdLoadFailedEvent With Error " + ironSourceError.ToString());
    }

    void BannerOnAdClickedEvent(IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got BannerOnAdClickedEvent With AdInfo " + adInfo.ToString());
        FirebaseManager.Instance.LogAnalyticsAdClickEvent(FirebaseManager.AdType.Banner);
    }

    void BannerOnAdScreenPresentedEvent(IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got BannerOnAdScreenPresentedEvent With AdInfo " + adInfo.ToString());
    }

    void BannerOnAdScreenDismissedEvent(IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got BannerOnAdScreenDismissedEvent With AdInfo " + adInfo.ToString());
    }

    void BannerOnAdLeftApplicationEvent(IronSourceAdInfo adInfo)
    {
        Debug.Log("unity-script: I got BannerOnAdLeftApplicationEvent With AdInfo " + adInfo.ToString());
    }
}

```

</div></details>

また、実機でのイベントの呼ばれ方についても調べたので、記載します。
失敗や例外系は、検証しきれていないので、ご注意してください。

* 共通
  * 初期化時: `SdkInitializationCompletedEvent`
  * アプリケーション開閉: `OnApplicationPause`

* バナー 
  * 初期化時: `BannerAdLoadedEvent`
  * 表示時: `BannerOnAdLoadedEvent`
  * クリック時: `BannerAdClickedEvent`
  * クリック時: `BannerOnAdClickedEvent`
  * クリック後にアプリに戻った時: `BannerAdLeftApplicationEvent`
  * クリック後にアプリに戻った時: `BannerAdLeftApplicationEvent`
  * 通信無し等: `BannerAdLoadFailedEvent`
  * 通信無し等: `BannerOnAdLoadFailedEvent`

* リワード広告
  * 初期化時: `RewardedVideoAvailabilityChangedEvent`
  * 初期化時: `RewardedVideoOnAdAvailable`
  * 動画再生後: `RewardedVideoOnAdUnavailable`
  * 動画再生後: `RewardedVideoAvailabilityChangedEvent`
  * 動画再生後: `RewardedVideoAdOpenedEvent`
  * 動画再生後: `RewardedVideoOnAdOpenedEvent`
  * 動画再生後: `RewardedVideoOnAdAvailable`
  * 動画再生後: `RewardedVideoAdRewardedEvent`
  * 動画再生後: `RewardedVideoOnAdRewardedEvent`
  * 動画再生後: `RewardedVideoAdClosedEvent`
  * 動画再生後: `RewardedVideoOnAdClosedEvent`


* インタースティシャル広告
  * 動画再生後: `InterstitialAdShowSucceededEvent`
  * 動画再生後: `InterstitialAdOpenedEvent`
  * 動画再生後: `InterstitialOnAdShowSucceededEvent`
  * 動画再生後: `InterstitialOnAdOpenedEvent`
  * 動画再生後: `InterstitialAdClosedEvent`
  * 動画再生後: `InterstitialOnAdClosedEvent`
  * 動画再生後: `InterstitialOnAdClosedEvent`