UnityでAdMobをGDPR対応させる(その7)

AdMob for GDPR Unityプログラミング

前回までで、GDPR同意フォーム(=GDPRメッセージ)を表示できるようになりました。今回は、同意取り消しのために、同意フォームを再表示できるようにしましょう。また、iOSのATT(AppTrackingTransparency)の対応も行いましょう。最後にシミュレータでの動作確認を行いましょう。私は専門家ではないので、記事の内容は必ずしも正確ではないかもしれません。今回がシリーズ最後の記事です。

使用するバージョン

Unity6000.3.8f1
Google Mobile Ads Unity Plugin v11.2.0

同意取り消し

GDPRの規則ではユーザーが同意を取り消すためのプロセスを備えていることが必須となっています(Googleヘルプ)。同意フォームには「Consent」と「Manage options」というボタンがありますが、「Manage options」の機能がどうもそのプロセスに対応しているようです。だから、ユーザーがいつでも同意フォームを呼び出せるようにしておけば、その要件を満たすことができると思われます。

では、ゲーム内に同意フォームを呼び出すボタンを設置しましょう。

GDPR Options Button

次にReconsentManagerというGameObjectを作り、同名のクラスファイルをアタッチしましょう。

ReconsentManager

以下のコードをAdMobManagerクラスに追加してください。これは同意フォームが再表示可能であることを知るためのメソッドです。Ad_Already_StartedはStaticに変更しておいてください。

    public static bool IsReconsentEnabled()
    {
        return ConsentInformation.PrivacyOptionsRequirementStatus == PrivacyOptionsRequirementStatus.Required;//欧州
    }

ReconsentManagerクラスには次のコードを書いてください。Reconsent_Clickメソッドを先ほど作成したButtonにアタッチしてください。また、ReconsentManagerのReconsent_BtnにそのButtonをアタッチしてください。

using System.Collections;
using GoogleMobileAds.Common;
using GoogleMobileAds.Ump.Api;
using UnityEngine;
using UnityEngine.UI;

public class ReconsentManager : MonoBehaviour
{
    [Space(10)]
    [Header("ボタン")]
    [SerializeField] private Button Reconsent_Btn;

    WaitForSeconds WaitSeconds1_5 = new(1.5f);

    IEnumerator Start()
    {
        while (AdMobManager.TryAdMob_Busy) yield return null;
        UpdateReconsentBtns();
    }

    void UpdateReconsentBtns()
    {
        bool canShowForm = AdMobManager.IsReconsentEnabled();
        Reconsent_Btn.gameObject.SetActive(canShowForm);
        Debug.Log(canShowForm ? "Show ReconsentBtns" : "Hide ReconsentBtns");
    }


    static bool Reconsent_Lock = false;//絶対に二重起動させない
    public void Reconsent_Click()
    {
        if (!AdMobManager.IsReconsentEnabled()) return;

        if (Reconsent_Lock) return;
        Reconsent_Lock = true;

        StartCoroutine(Reconsent_Event());
    }


    IEnumerator Reconsent_Event()
    {
        bool isOnline = false;
        yield return InternetChecker.Check(x => isOnline = x);

        if (isOnline)
        {
            bool? result = null;
            ConsentForm.ShowPrivacyOptionsForm(error =>
            {
                MobileAdsEventExecutor.ExecuteInUpdate(() =>
                {
                    //これはリトライしたら固まるメソッド
                    if (error != null)
                    {
                        Debug.Log("ShowOption Error");
                        result = false;
                    }
                    else
                    {
                        //フォームが閉じられる時に出る
                        Debug.Log("ShowOption OK");
                        result = true;
                    }
                });
            });

            while (result == null) yield return null;
        }

        yield return WaitSeconds1_5; // 連射防止

        Reconsent_Lock = false;
    }
}

以上で実装は完了です。Playボタンを押して動作確認してみてください。「Ads options」ボタンを押したら同意フォームが表示されましたか?されませんよね?代わりに以下のような”Consent Form not loaded.”というメッセージが表示されているはずです。つまり、Editorの同意フォームの表示はサポートされているが、再表示はサポートされていません。この点は十分にご注意ください。再表示の動作確認は実機またはシミュレータでしか行えません。

Consent Form not loaded

Unity上でできることはここまでとなります。動作確認は、実機またはXcodeのシミュレータ上で行う必要があります。

実機確認の準備

以前にも申しましたが、実機での動作確認には以下の準備が必要です。

事前準備
  1. 実機確認の前にAdMob管理画面で「メッセージの作成」をしておくこと
  2. 「メッセージの作成」では日本語など欧米以外の言語は選択しないこと
  3. 実機確認の際はダミー広告ではなく本番広告をテストモードで使用すること

「メッセージの作成」をしておかないと、実機では同意フォーム(=GDPRメッセージ)が表示されずエラーになります。また、日本語の同意フォームを実機で表示すると、私の経験上、高確率で通信が失敗します。なので、英語バージョンを表示するようにしましょう。AdMob管理画面で日本語バージョンを作らず、英語バージョンを作ることでこれを実現できます。そして、ダミー広告だと同意取得後に広告が表示されないことがあります。このように落とし穴が満載なのです。前述した「同意フォームの再表示がEditor上ではできないこと」も落とし穴の一つといえるでしょう。

iOSの場合はAdMob管理画面で、IDFA説明メッセージも作っておきましょう。IDFA説明メッセージはATTメッセージの前に表示され、ATTの同意をユーザーにお願いするためのメッセージです。同意率が上がるかもしれませんので、日本語を含め可能な限り全ての言語で有効にしておきましょう。なお、EEAのユーザーにはGDPR同意フォームを表示し、EEA以外のユーザーにはIDFA説明メッセージを表示するというのがUMPの仕様のようです。

IDFA message

実機確認

さて、実機確認(シミュレータでの確認)を行いましょう。この例では、私が以前に作成したゲーム「マジックぷう」を使い、iOSアプリとしてビルドしてシミュレータで動作確認する手順を説明します。皆さんは、ご自分のアプリをAdMob管理画面に登録してから動作確認を行なってください。

ビルド

Project Settings > Player > Target SDKをSimulator SDKにして、ビルドします。

Setting

いくつかファイルが生成されますが、拡張子がxcworkspaceのものをダブルクリックして開きます。

xcworkspace file

Xcodeが立ち上がるのを待ちます。Unity-iPhone > UnityFrameworks > Build Phases > Link Binary With Librariesのところで「+」ボタンを押してAppTrackingTransparency.framework(※)を追加します。(※追加しなくてもATTメッセージは自動で出現するようですが、念の為追加します。)

AppTrackingTransparency

Product > Buildを選択して、シミュレータが立ち上がるのを待ちましょう。動作がおかしい場合は、Product > Clean Build Folderで一度ビルドフォルダを空にしてから再度ビルドを行いましょう。

EEAの場合の動作確認(ATT許可の場合)

DebugGeography = DebugGeography.EEAが設定されていて、かつ、正常に起動すれば以下のようにGDPRメッセージが表示されます(※)。(※デバッグ目的で起動のたびにGDPRメッセージを表示する場合は、Startメソッドの中かどこかでConsentInformation.Reset()を実行することが必要です。)

GDPR

Consent(=同意する)を押してみましょう。すると、以下のようにATTメッセージが表示されます。

ATTメッセージ

許可を押してみましょう。タイトル画面が表示され、バナーが表示され、Ads optionsボタンが表示されました。

タイトル画面

Ads optionsボタンを押してみましょう。すると、以下のようにGDPRメッセージを再度表示することができました。

reconsentGDPR

EEAの場合の動作確認(ATT不許可の場合)

EEAの場合で、ATTメッセージで「アプリにトラッキングしないように要求」を押した場合は、パーソナライズされていないバナーが表示され、Ads optionsボタンは表示されません。

タイトル画面2

EEA以外の場合の動作確認

DebugGeography = DebugGeography.EEAを設定せず、あなたのお住まいの地域が日本ならば、GDPRメッセージが表示される代わりにIDFA説明メッセージが表示されます。なお、IDFA説明メッセージの内容はAdMob管理画面でカスタマイズできます。

IDFA説明メッセージ

続行を押すとATTメッセージが表示されます。

IDFA説明メッセージの後のATTメッセージ

どちらかのボタンを押すとタイトル画面が表示され、バナーが表示され、もちろんAds optionsボタンは表示されません。

タイトル画面3

完成状態(全コード)

全7回の「UnityでAdMobをGDPR対応させる」シリーズで紹介したものの完成状態は以下のようになります。

画面

記事通りだと、以下のような画面になります。

完成画面

ヒエラルキー

記事通りだと、ヒエラルキーは以下のようになります。AdMobManager、BannerManager、ReconsentManagerはすべて空のオブジェクトです。

Unityヒエラルキー

クラスファイル

記事通りだと、クラスファイルは以下のようになります。InternetCheckerのみ静的クラスですので、どこにもアタッチしません。それ以外のクラスは、同名のオブジェクトにアタッチしてください。

クラスファイル一覧

AdMobManagerクラスの全コード

記事通りだと、AdMobManagerクラスの全コードは以下のようになります。なお、コード内のConsentInformation.Reset()やConsentDebugSettingsは、あくまでデバッグ用ですので、本番の場合は必ず削除してください。

ポイント

TryAdMobメソッドを実行すると、「同意取得」から「広告の表示」までの必要な全工程が行われます。シーン切り替えのボタンや、設定画面の表示ボタンや、その他ボタンのメソッドに、TryAdMobメソッドを含ませておけば、万一広告が表示されなかった場合に再トライしてくれます。

using System;
using System.Collections;
using UnityEngine;
using GoogleMobileAds.Api;
using GoogleMobileAds.Ump.Api;
using GoogleMobileAds.Common;
using System.Collections.Generic;

public class AdMobManager : MonoBehaviour
{
    public static AdMobManager Instance;

    public const string DRO_BANNER = "ca-app-pub-3940256099942544/6300978111";// デモ広告ユニットID

    static bool Ad_Already_Started = false;

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }


    IEnumerator Start()
    {
        MobileAdsEventExecutor.Initialize();    // コールバック関数からメインスレッドを利用できるようにする

        ConsentInformation.Reset();             //テスト用

        while (BannerManager.Instance == null) yield return null;

        TryAdMob();

        yield return null;
        yield break;
    }


    public static void TryAdMob()
    {
        if (TryAdMob_Busy || Ad_Already_Started) return;
        TryAdMob_Busy = true;
        Instance.StartCoroutine(TryAdMob_Event());
    }

    public static bool TryAdMob_Busy = false;
    static IEnumerator TryAdMob_Event()
    {
        Debug.Log($"TryAdMob Start");

        // 0.すでに広告が開始されている場合
        if (Ad_Already_Started)
        {
            Debug.Log($"TyAdMob Ad already started");
            LoadAdsIfNull();
            TryAdMob_Busy = false;
            yield break;
        }

        // 1.理由はわからないが最初から広告が許可されている場合
        if (ConsentInformation.CanRequestAds())
        {
            Debug.Log($"TryAdMob Ad Start");
            yield return AdFinalize();                          // 広告を開始する
            TryAdMob_Busy = false;
            yield break;
        }

        // 2.インターネット未接続の場合は処理をキャンセル
        bool isOnline = false;
        yield return InternetChecker.Check(x => isOnline = x);
        if (!isOnline)
        {
            Debug.Log($"TryAdMob Internet NG");
            TryAdMob_Busy = false;
            yield break;
        }

        // 3.同意状態を確認し、エラーの場合は処理をキャンセル
        bool? updateResult = null;
        yield return TryUpdate(x => updateResult = x);          // これはダミー広告だと初回失敗する
        if (updateResult == false)
        {
            Debug.Log($"TryAdMob Update NG");
            TryAdMob_Busy = false;
            yield break;
        }

        // 4.同意フォームを表示する
        bool? canShowAd = null;
        yield return TryShowForm(x => canShowAd = x);                       // デバッグの同意フォームは英語じゃないとエラーが返る
        if (canShowAd == true)
        {
            while (!ConsentInformation.CanRequestAds()) yield return null;  // 広告可能になるまで待つ
            yield return AdFinalize();                                      // 広告を開始する
        }

        Debug.Log($"TryAdMob FinalResult={canShowAd}");
        TryAdMob_Busy = false;
    }


    static IEnumerator TryUpdate(Action<bool?> callback)
    {
        bool? result = null;

        ConsentInformation.Update(MakeConsentParameters(), error =>
        {
            MobileAdsEventExecutor.ExecuteInUpdate(() =>
            {
                result = error == null; // nullじゃなければ合格

                if (result == false)
                {
                    Debug.Log($"TryUpdate Err: {error.Message}");
                }
            });
        });

        while (result == null) yield return null;          //処理が完了するまで待つ

        callback(result);
    }


    static IEnumerator TryShowForm(Action<bool> callback)
    {
        var status = ConsentInformation.ConsentStatus;

        Debug.Log($"TryShowForm CONSENTSTATUS={status}");

        if (status == ConsentStatus.Required)
        {
            while (!ConsentInformation.IsConsentFormAvailable()) yield return null; // 同意フォームが可能になるまで待つ

            bool? result = null;

            ConsentForm.LoadAndShowConsentFormIfRequired(error =>
            {
                MobileAdsEventExecutor.ExecuteInUpdate(() =>
                {
                    result = error == null;// nullじゃなければ合格

                    if (result == false)
                    {
                        Debug.Log($"TryShowForm Err: {error.Message}");
                    }
                });
            });

            while (result == null) yield return null;

            callback(result == true);
        }
        else if (status == ConsentStatus.Obtained || status == ConsentStatus.NotRequired)
        {
            callback(true);
        }
        else
        {
            //念の為
            callback(false);
        }
    }


    static ConsentRequestParameters MakeConsentParameters()
    {
        var parameters = new ConsentRequestParameters
        {
            TagForUnderAgeOfConsent = false,
            ConsentDebugSettings = new ConsentDebugSettings
            {
                DebugGeography = DebugGeography.EEA,
                TestDeviceHashedIds = UMP_TestDevices
            }
        };

        return parameters;
    }

    static readonly List<string> UMP_TestDevices = new()
    {
        //"テストデバイスID",
    };


    static IEnumerator AdFinalize()
    {
        // すべての広告に使用されるグローバル設定=======================
        
        var request = new RequestConfiguration()
        {
            TagForChildDirectedTreatment = TagForChildDirectedTreatment.False,
            MaxAdContentRating = MaxAdContentRating.T,
        };

        MobileAds.SetRequestConfiguration(request);
        MobileAds.SetiOSAppPauseOnBackground(true);//iOSでフルスクリーン広告が表示されている間、Unityアプリを一時停止


        // 広告の初期化===========================================

        bool? result = null;

        MobileAds.Initialize(initstatus =>
        {
            result = initstatus != null; // nullでなければ合格
        });

        while (result == null) yield return null;//結果を待つ


        // 広告のロード===========================================

        if (result == true)
        {
            Ad_Already_Started = true;  //広告表示に関係するのでこの位置
            LoadAdsIfNull();
        }
    }


    static void LoadAdsIfNull()
    {
        if (BannerManager.Ad == null) BannerManager.Instance.LoadAd();
    }


    public static bool IsReconsentEnabled()
    {
        return ConsentInformation.PrivacyOptionsRequirementStatus == PrivacyOptionsRequirementStatus.Required;//欧州
    }
}

BannerManagerクラスの全コード

記事通りだと、BannerManagerクラスの全コードは以下のようになります。

using GoogleMobileAds.Api;

public class BannerManager
{
    public static BannerManager Instance;

    public static BannerView Ad { get; private set; } = null;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }


    bool LoadAd_Lock  = false;
    public void LoadAd()
    {
        if (LoadAd_Lock) return;
        LoadAd_Lock = true;

        DestroyBannerAd();

        Ad = new BannerView(AdMobManager.DRO_BANNER, AdSize.Banner, AdPosition.Bottom);
        Ad.OnBannerAdLoaded += OnLoaded;
        Ad.OnBannerAdLoadFailed += OnFailed;
        Ad.LoadAd(new AdRequest());
    }


    void OnLoaded()
    {
        MobileAdsEventExecutor.ExecuteInUpdate(() =>
        {
            LoadAd_Lock = false;
        }); 
    }

    void OnFailed(LoadAdError error)
    {
        MobileAdsEventExecutor.ExecuteInUpdate(() =>
        {
            DestroyBannerAd();
            LoadAd_Lock = false;
        }); 
    }

    public void DestroyBannerAd()
    {
        Ad?.Destroy();
        Ad = null;
    }
}

ReconsentManagerクラスの全コード

記事通りだと、ReconsentManagerクラスの全コードは以下のようになります。Reconsent_BtnにはCanvasのAds optionsボタンを入れてください。

using System;
using GoogleMobileAds.Api;
using GoogleMobileAds.Common;
using UnityEngine;

public class ReconsentManager : MonoBehaviour
{
    [Space(10)]
    [Header("ボタン")]
    [SerializeField] private Button Reconsent_Btn;

    WaitForSeconds WaitSeconds1_5 = new(1.5f);

    IEnumerator Start()
    {
        while (AdMobManager.TryAdMob_Busy) yield return null;
        UpdateReconsentBtns();
    }

    void UpdateReconsentBtns()
    {
        bool canShowForm = AdMobManager.IsReconsentEnabled();
        Reconsent_Btn.gameObject.SetActive(canShowForm);
        Debug.Log(canShowForm ? "Show ReconsentBtns" : "Hide ReconsentBtns");
    }


    static bool Reconsent_Lock = false;//絶対に二重起動させない
    public void Reconsent_Click()
    {
        if (!AdMobManager.IsReconsentEnabled()) return;

        if (Reconsent_Lock) return;
        Reconsent_Lock = true;

        StartCoroutine(Reconsent_Event());
    }


    IEnumerator Reconsent_Event()
    {
        bool isOnline = false;
        yield return InternetChecker.Check(x => isOnline = x);

        if (isOnline)
        {
            bool? result = null;
            ConsentForm.ShowPrivacyOptionsForm(error =>
            {
                MobileAdsEventExecutor.ExecuteInUpdate(() =>
                {
                    //これはリトライしたら固まるメソッド
                    if (error != null)
                    {
                        Debug.Log("ShowOption Error");
                        result = false;
                    }
                    else
                    {
                        //フォームが閉じられる時に出る
                        Debug.Log("ShowOption OK");
                        result = true;
                    }
                });
            });

            while (result == null) yield return null;
        }

        yield return WaitSeconds1_5; // 連射防止

        Reconsent_Lock = false;
    }
}

InternetCheckerクラスの全コード

記事通りだと、InternetCheckerクラスの全コードは以下のようになります。接続確認用URLは必ずあなたが所有するサイトのURLに置き換えてください。

サーバーの負荷をもっと軽くしたい場合は前回の記事のInternetChecker2クラスを参照してください。前回の記事 → その6(GDPR同意取得メソッドのコードの補足)

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

/// <summary>
/// インターネットに接続されているか確認するクラス
/// </summary>
public static class InternetChecker
{

    //接続確認用URL
    private static readonly List<string> Urls = new()
   {
    "https://mochitchies.com/",
    "https://b.hatena.ne.jp/mochitchies/",
    "https://note.com/mochitchies",
  };

    public static IEnumerator Check(Action<bool> callback, int timeOut = 2)
    {
        if (Application.internetReachability == NetworkReachability.NotReachable)
        {
            Debug.Log("Internet not reachable");
            callback(false);
            yield break;
        }

        bool result = false;

        for (int i = 0; i < Urls.Count; i++)
        {
            using var request = new UnityWebRequest(Urls[i]) { timeout = timeOut };

            yield return request.SendWebRequest();

            if (request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log($"Internet ng :{Urls[i]} : result {request.result} : error {request.error}");
            }
            else
            {
                Debug.Log($"Internet OK : {Urls[i]}");
                result = true;
                break;
            }
        }

        callback(result);
    }

}

終わりに

以上で「UnityでAdMobをGDPR対応させる」シリーズは終了となります。お疲れ様でした。少しでも皆様のお役に立てたのなら幸いです。長時間、ありがとうございました。

ゲームの紹介

GDPR対応させたゲームをいくつかリリースしています。もしよろしければ、ダウンロードしていただければと思います。よろしくお願いします。

ゲームのページ

このシリーズの記事

☕ ブログを応援する

いつも記事をお読みいただきありがとうございます!
内容が役立った、応援したいと思っていただけた方は、サーバー代や活動費としてサポート(投げ銭・150円〜)をいただけると励みになります。

PayPalでサポートを送る (150円〜)

※いただいたサポート(投げ銭)の原則としての返金には対応いたしかねます。あらかじめご了承のうえ、温かいご支援をお願いいたします。

コメント

タイトルとURLをコピーしました