Google Mobile Ads SDK for Unity でサンプル広告をやってみたら OnAdFailedToLoad にハマった話

## 概要

初めてUnityにAdMobを導入した際にハマったことのまとめです。
今回はリワード広告を使い、動作確認したところ`RewardedAd`にセットした`OnAdFailedToLoad`（広告の読み込みに失敗したときに呼び出されるメソッド）にしかならず、ググってもなかなか原因がわからないまま数日を無駄にした感じです笑汗

先にネタばらしすると、[サンプル広告（デバッグ用の広告）](https://developers.google.com/admob/unity/test-ads?hl=ja#sample_ad_units)を使って動作確認していて、**テストデバイスの登録が必要で登録のし忘れによることが原因**でした。
ちゃんと公式を呼ばずにサンプル広告ユニットIDだけセットして動作確認していたので、改めてドキュメントとかマニュアルは全部確認してからやらないとなと反省です笑汗


## 前提

すでにUnityにAdMobを入れて作業している前提で話をまとめていくので、まだ導入前の場合は[公式のセットアップ](https://developers.google.com/admob/unity/quick-start?hl=ja)と参考にしていた次の記事を共有しておきます。

- [Unity で iOS 14 のトラッキング許可ダイアログを表示する](https://www.matatabi-ux.com/entry/2021/04/08/100000)
- [Unity + AdMob 利用時に iOS 14 の ATT 対応する](https://www.matatabi-ux.com/entry/2021/04/15/100000)

## やっていたこと

簡単にスクリプトを解説します。
上記の記事や他の記事を参考に作っているので、設定中の方は参考になればなと。

まず、広告IDなどは次のように設定します。
`DEVELOPMENT_BUILD`の中にセットしている広告IDがサンプルで公式ページに載っているやつです。

```csharp
namespace Google
{
    namespace Mobile
    {
        namespace Ads
        {
            public class Common
            {
#if DEVELOPMENT_BUILD
#if UNITY_EDITOR
                public static readonly string APP_ID = "-1";
                public static readonly string AD_REWARD_UNIT_ID = "-1";
#elif UNITY_ANDROID
                public static readonly string APP_ID = "ca-app-pub-3940256099942544~3347511713";
                public static readonly string AD_REWARD_UNIT_ID = "ca-app-pub-3940256099942544/5224354917";
#elif UNITY_IOS
                public static readonly string APP_ID = "ca-app-pub-3940256099942544~3347511713";
                public static readonly string AD_REWARD_UNIT_ID = "ca-app-pub-3940256099942544/1712485313";
#endif
#else
#if UNITY_EDITOR
                public static readonly string APP_ID = "-1";
                public static readonly string AD_REWARD_UNIT_ID = "-1";
#elif UNITY_ANDROID
                public static readonly string APP_ID = "ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy";
                public static readonly string AD_REWARD_UNIT_ID = "ca-app-pub-xxxxxxxxxxxxxxxx/aaaaaaaaaa";
#elif UNITY_IOS
                public static readonly string APP_ID = "ca-app-pub-xxxxxxxxxxxxxxxx~zzzzzzzzzz";
                public static readonly string AD_REWARD_UNIT_ID = "ca-app-pub-xxxxxxxxxxxxxxxx/iiiiiiiiii";
#endif
#endif
            }
        }
    }
}
```

次にリワードに関する処理のスクリプトです。

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using GoogleMobileAds.Api;
using GoogleMobileAds.Common;
using System;

namespace Google
{
    namespace Mobile
    {
        namespace Ads
        {
            public class Reward : MonoBehaviour
            {
                RewardedAd rewarded;

                public bool IsLoaded { get { return rewarded.IsLoaded(); } }

                public delegate void RewardHundler(bool result, string response);
                RewardHundler _callback;

                public void CreateAndLoadRewardedAd(RewardHundler callback)
                {
                    _callback = callback;
                    rewarded = new RewardedAd(Common.AD_REWARD_UNIT_ID);

                    rewarded.OnAdLoaded += HandleRewardedAdLoaded;
                    rewarded.OnAdFailedToLoad += HandleRewardedAdFailedToLoad;
                    rewarded.OnAdFailedToShow += HandleRewardedAdFailedToShow;
                    rewarded.OnUserEarnedReward += HandleUserEarnedReward;
                    rewarded.OnAdOpening += HandleRewardedAdOpening;
                    rewarded.OnAdClosed += HandleRewardedAdClosed;

                    AdRequest request = new AdRequest.Builder().Build();
                    rewarded.LoadAd(request);
                }

                public void UserChoseToWatchAd()
                {
                    if (rewarded.IsLoaded())
                        rewarded.Show();
                }

                private void HandleRewardedAdLoaded(object sender, EventArgs args)
                {
                    Debug.Log("HandleRewardedAdLoaded event received");
                }

                private void HandleRewardedAdFailedToLoad(object sender, AdFailedToLoadEventArgs args)
                {
                    Debug.Log("HandleRewardedAdFailedToLoad event received with message: " + args.ToString());
                    if (_callback == null)
                        return;
                    Debug.Log("HandleRewardedAdFailedToLoad callback.");
                    _callback(false, args.ToString());
                }

                private void HandleRewardedAdFailedToShow(object sender, AdErrorEventArgs args)
                {
                    Debug.Log("HandleRewardedAdFailedToShow event received with message: " + args.Message);
                    if (_callback == null)
                        return;
                    Debug.Log("HandleRewardedAdFailedToShow callback.");
                    _callback(false, args.ToString());
                }

                private void HandleRewardedAdClosed(object sender, EventArgs args)
                {
                    Debug.Log("HandleRewardedAdClosed event received");
                    if (_callback == null)
                        return;
                    Debug.Log("HandleRewardedAdClosed callback.");
                    _callback(true, args.ToString());
                }

                private void HandleRewardedAdOpening(object sender, EventArgs args)
                {
                    Debug.Log("HandleRewardedAdOpening event received");
                    // TODO: 必要に応じて制御を追記
                }

                private void HandleUserEarnedReward(object sender, GoogleMobileAds.Api.Reward args)
                {
                    Debug.Log("HandleRewardedAdRewarded event received for " + args.Amount.ToString() + " " + args.Type);
                    // TODO: 必要に応じて制御を追記
                }
            }
        }
    }
}
```

最後に初期化やリワードを開始するスクリプトです。

```csharp
using UnityEngine;
using UnityEngine.SceneManagement;

public class StartButton : MonoBehaviour
{
    [SerializeField]
    Google.Mobile.Ads.Reward _reward;
    [SerializeField]
    AudioSource _audio;

    void Start()
    {
        // リワード　初期化
        _reward.CreateAndLoadRewardedAd((result, response) =>
        {
            Debug.Log("reward.callback response: " + response);
            if (result)
                SceneManager.LoadScene("NextScene");
        });
    }

    public void OnClickStartButton()
    {
        _audio.Play();

        if (_reward.IsLoaded)
            // リワード　開始
            _reward.UserChoseToWatchAd();
    }
}
```

### 動作確認

ちなみにAdMobの動作確認は実施でしかできないと勝手に思っていたんですが、実際にUnity上で動かしてみたところAdMobがUnityエディタ用に広告動作を用意していたので、エディタでエラーが出ないことを確認してから実機確認に入ると作業しやすいかと。

![スクリーンショット 2021-09-12 15.31.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/30d87784-cc99-dbf0-9468-a3a6cd43777c.png)


## ハマった内容

最後の方に出ているログを見ると次の内容でXcode上でエラーになりました。

```bash
HandleRewardedAdFailedToLoad event received with message: GoogleMobileAds.Api.AdFailedToLoadEventArgs
Google.Mobile.Ads.Reward:HandleRewardedAdFailedToLoad(Object, AdFailedToLoadEventArgs)
System.EventHandler`1:Invoke(Object, TEventArgs)
System.EventHandler`1:Invoke(Object, TEventArgs)

HandleRewardedAdFailedToLoad callback.
Google.Mobile.Ads.Reward:HandleRewardedAdFailedToLoad(Object, AdFailedToLoadEventArgs)
System.EventHandler`1:Invoke(Object, TEventArgs)
System.EventHandler`1:Invoke(Object, TEventArgs)

libc++abi: terminating with uncaught exception of type Il2CppExceptionWrapper
terminating with uncaught exception of type Il2CppExceptionWrapper
(lldb) 
```

一番下の`libc++abi`らへんでググっても`HandleRewardedAdFailedToLoad`をセットしている`OnAdFailedToLoad`に関してググっても原因はわからない状況でした。

上記のスクリプトは最終的な完成形のもので、このログが出てエラーになってアプリがクラッシュしていたときはもう少し違って幾つか修正した点がありました。

そのスクリプトを修正してもアプリのクラッシュは解決したものの`OnAdFailedToLoad`が呼ばれて広告の読み込みに失敗する点はどうしても変わらなかったので、再度公式を読み返したところでテストデバイスの未登録に気づきました笑汗

## テストデバイスの登録

公式の[AdMob 管理画面でテストデバイスを追加する](https://developers.google.com/admob/unity/test-ads?hl=ja#add_your_test_device_in_the_admob_ui)から[スタートガイドのテストデバイスを設定する](https://support.google.com/admob/answer/9691433?hl=ja)からテストデバイスの設定方法を確認していきます。

<img width="500" alt="スクリーンショット 2021-09-12 13.33.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/176fcf22-e3e6-892a-190f-d8f6854a38e3.png">
<img width="500" alt="スクリーンショット 2021-09-12 13.33.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/700d9d70-c226-e98f-a607-0342c7ade5c9.png">
<img width="500" alt="スクリーンショット 2021-09-12 13.57.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/92c64abd-d6f0-80dc-b6e0-8ad96a8c11c8.png">

### IDFA

iOSの場合、(2021年9月現在)IDFA(Identification For Advertisers)の取得が難しく次の方法から取得するしかありません。

- Swiftの`ASIdentifierManager`を使ってスクリプトで取得する。
  - [iOS14でのIDFA取得](https://qiita.com/yofuru/items/213b88b85553631204e4)
  - [端末の広告ID(IDFA, ADID)はどうすれば取得できますか？](https://reproio.zendesk.com/hc/ja/articles/900004746846-%E7%AB%AF%E6%9C%AB%E3%81%AE%E5%BA%83%E5%91%8AID-IDFA-ADID-%E3%81%AF%E3%81%A9%E3%81%86%E3%81%99%E3%82%8C%E3%81%B0%E5%8F%96%E5%BE%97%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%81%8B-)
- IDFAを取得して表示してくれるアプリをインストールする。
  - [Adjustテストコンソール](https://help.adjust.com/ja/article/testing-console)
  - [どうすれば自分の端末の広告IDが分かりますか？](https://www.tapjoy.com/ja/faqs/how-do-i-find-my-advertising-id-2/)

セキュリティを考えるならスクリプトで自前でIDFAを取得した方がいいんですが、面倒だったんで仕事でも使ったことがあるAdjustのアプリでIDFAを取得することにしました。
取得した後はもう使わないので、アプリを削除するか設定 > プライバシー > トラッキングからAdjustのトラッキングをdisableにしておくとよいかと。

<img width="500" alt="iOS の画像.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82601/3c8e12c5-06d0-c7e7-938a-9b7cd682c1ac.png">



## まとめ

今回は先にiOSを対応していたので、次にAndroidを対応してまた何かハマったらアプデするか新しい記事でまとめようと思います。