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

AdMob for GDPR Unityプログラミング

前回、GDPR同意取得のメソッドのコードについて粗方説明しました。今回は、前回説明できなかった次のメソッドの説明をします。

メソッド一覧
  • すべての広告に使用されるグローバル設定(MakeAdRequest)
  • 広告のロード(AdFinalize)
  • インターネット接続確認(InternetChecker.Check)

ほとんどが私の我流のコードなので、ガラパゴス化しているかもしれません。また、この記事には、極端な表現や矛盾や説明の不足や表現が一致しない箇所があるかもしれません。色々と大目に見ていただけると助かります。

使用するバージョン

Unity2022.3.29f1
Google Mobile Ads Unity Plugin v9.2.0

すべての広告に使用されるグローバル設定

すべての広告に使用されるグローバル設定を、広告のロード前に必ず行います。前回の記事のTryAdMob_Eventの「5.広告表示処理」のMakeAdRequestが、その設定を行うメソッドです。中身は以下の通りです。

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

    var request = new RequestConfiguration()
    {
        TagForChildDirectedTreatment = TagForChildDirectedTreatment.False,
        MaxAdContentRating = MaxAdContentRating.T,
    };

    MobileAds.SetRequestConfiguration(request);
}

コードを上から順に解説します。MobileAds.SetiOSAppPauseOnBackground(true)はiOSでフルスクリーン広告が表示されている間、Unityアプリを一時停止するためのメソッドです。Androidの場合は処理が無視される仕様となっていますので、OSの違いで場合分けする必要はありません。つまり、Androidの場合もそのまま消さなくて大丈夫です。

次にMobileAds.RaiseAdEventsOnUnityMainThread = trueによって、別スレッドで実行されているAdMobのイベントをUnityのメインスレッドに同期させています。

そして、var request = new RequestConfiguration()が、すべての広告に使用されるグローバル設定の設定内容です。RequestConfigurationのプロパティのTagForChildDirectedTreatmentは、アメリカCOPPAの「子ども」に該当するかどうかの設定です。COPPAはアメリカ版のGDPRみたいなものですが、GDPRではありません。なので、記事のテーマである”GDPR対応”とは関係ありませんが、ついでですので設定しておきましょう。13歳未満のアプリの場合はTagForChildDirectedTreatment.Trueを代入します。13歳以上のアプリはTagForChildDirectedTreatment.Falseを代入します。我々のアプリは後者でしょう。そして、もう一つのプロパティのMaxAdContentRatingは、広告のレーティングの設定です。レーティングの種類は以下の4つです。

レーティング
  • MaxAdContentRating.G(一般ユーザー)
  • MaxAdContentRating.PG(保護者の判断を推奨)
  • MaxAdContentRating.T(13歳以上)
  • MaxAdContentRating.MA(成人)

我々のアプリはMaxAdContentRating.TかMaxAdContentRating.MAでしょう。これと同様の設定をAdMob管理画面でも行えます。ただし、MaxAdContentRatingとAdMob管理画面の設定が食い違う場合は、MaxAdContentRatingが優先されることに注意が必要です。

最後のMobileAds.SetRequestConfiguration(request)が、前述の設定内容で設定を行うメソッドです。

広告のロード

次に広告のロード処理です。前回の記事のTryAdMob_Eventの「5.広告表示処理」のAdFinalizeが、そのメソッドです。中身は以下の通りです。

IEnumerator AdFinalize()
{
    bool? result = null;

    MobileAds.Initialize((InitializationStatus initstatus) =>
    {
        if (initstatus == null)
        {
            result = false;
        }
        else
        {
            result = true;
        }
    });

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

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

        if (BannerManager.Ad == null) BannerManager.Instance.LoadAd();

        //他の広告もこの位置でロード

    }
}

コードを解説します。MobileAds.InitializeはAdMobの初期化メソッドです。このメソッドの引数のラムダ式はデリゲートです。初期化メソッド実行後にこのデリゲートが実行されます。処理が正常に終わればinitstatusはnullではありません。よって、initstatusがnullのときはresultにFalseを、nullでなければresultにTrueを代入しています。そして、resultがTrueの場合のみ、BannerManager.Instance.LoadAdでバナー広告のロードを行います。同じタイミングでAd_Already_StartedにTrueを入れて「広告が開始された」フラグを立てます。

つぶやき

なお、ややこしい話ですが、バナー広告はロードすると勝手に自動で表示されます。インタースティシャルなど他の広告はロードしても勝手には表示はされません。バナー広告だけ扱いが違いますので注意してください。

インターネットの接続確認

インターネット接続を確認するクラスについてはkanのメモ帳様の記事を参考に作成しました(kanのメモ帳様の記事)。前回の記事のTryAdMob_Eventの「1.インターネット接続確認」のInternetCheckerがそのクラスです。静的クラスにしています。中身は次のとおりです。

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/",             //あなた自身のURLに変更してください
    "https://b.hatena.ne.jp/mochitchies/",  //あなた自身のURLに変更してください
    "https://note.com/mochitchies",         //あなた自身のURLに変更してください
  };

    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);
    }
}

コードを上から順に解説します。UrlsはstringのListで、インターネット接続確認を行うためのURLをstringとして持っています。

その下のCheckが、接続の確認処理を行うメソッドです。Checkの第一引数はboolを引数に持つデリゲートで、第二引数がタイムアウトを設定するためのintです。Checkはコルーチンなので戻り値が取れませんので、デリゲートを通じて最終結果をメソッド外に渡すということをやっています。第二引数のint timeOut = 2は、第二引数が指定されなかった場合に2が初期値として与えられるという意味です。

Checkメソッドの冒頭で、Application.internetReachabilityでネットの接続設定状態を確認しています。ネットの接続設定がOFFならば、callbackにfalseを渡して実行してCheckメソッドを抜けます。

for文の中の説明をします。for文の中で、Urlsの小さいインデックスの要素から順に接続確認していきます。そして、接続確認が取れた時点で、それ以降の確認は無駄なので、すぐにfor文を抜けるようにしています。HTTPのリクエストが以下のコードです。timeoutプロパティには特に指定がなければ、初期値の2(秒)が代入されるようにしています。

using var request = new UnityWebRequest(Urls[i]) { timeout = timeOut };

そして、リクエストの送信が以下のコードです。request.SendWebRequestはコルーチンですので、この処理が完了するまで待機する形となります。

yield return request.SendWebRequest();

request.SendWebRequestの処理完了後に行う分岐処理が以下のコードです。request.resultがUnityWebRequest.Result.Successでない場合は通信失敗です。なので、失敗の場合は、次のURLのチェックに移行します。全てのURLに対して通信が失敗した場合は、result = falseが確定し、「ネット接続なし」が確定することになります。request.resultがUnityWebRequest.Result.Successの場合はresult = trueが確定し、「ネット接続あり」が確定することになります。

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;
}

コードの全体

AdMobManagerクラスのコードの全体は以下の通りです。

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

public class AdMobManager : MonoBehaviour
{
    public static AdMobManager Instance;

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

    bool Ad_Already_Started = false;

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


    IEnumerator Start()
    {
        ConsentInformation.Reset();//これはテスト用なので本番では削除する

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

        TryAdMob();
    }


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

    bool TryAdMob_Busy = false;
    IEnumerator TryAdMob_Event()
    {
        Debug.Log($"TryAdMob Start");
        // 1.インターネット接続確認++++++++++++++++++++++++++++++++++++++++
        bool isOnline = false;
        yield return InternetChecker.Check(x => isOnline = x);
        if (!isOnline)
        {
            Debug.Log($"TryAdMob Internet NG");
            goto CANCEL;
        }

        // 2.念の為広告表示可能か確認+++++++++++++++++++++++++++++++++++++++
        if (ConsentInformation.CanRequestAds())
        {
            Debug.Log($"TryAdMob Ad Start");
            yield return MakeAdRequest();                   //広告を開始する
            yield return AdFinalize();
            goto CANCEL;
        }

        // 3.同意状態確認リクエスト++++++++++++++++++++++++++++++++++++++++
        bool? updateOK = null;
        yield return TryUpdate(x => updateOK = x);           //これはダミー広告だと初回失敗する
        if (updateOK == false)
        {
            Debug.Log($"TryAdMob Update NG");
            goto CANCEL;
        }

        // 4.同意フォーム表示リクエスト++++++++++++++++++++++++++++++++++++++++
        bool? canShowAd = null;
        yield return TryShowForm(x => canShowAd = x);       //デバッグの同意フォームは英語じゃないとエラーが返る

        // 5.広告表示処理++++++++++++++++++++++++++++++++++++++++
        if (canShowAd == true)
        {
            while (!ConsentInformation.CanRequestAds()) yield return new WaitForSeconds(0.1f);  //広告可能になるまで待つ
            yield return MakeAdRequest();                                                       //広告を開始する
            yield return AdFinalize();
        }

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

    CANCEL:

        Debug.Log($"CanRequestAds = {ConsentInformation.CanRequestAds()}");

        TryAdMob_Busy = false;
    }


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

        //今の同意状態をサーバーへ確認(いずれにせよこれはAdMobには必須なようだ)
        ConsentInformation.Update(MakeConsentParameters(), (FormError error) =>
        {
            if (error != null)
            {
                updateOK = false;
                Debug.Log($"TryUpdate Err: {error.Message}");
            }
            else
            {
                updateOK = true;
            }
        });

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

        callback(updateOK);
    }


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

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

        bool? canShowAd = null;

        if (status == ConsentStatus.Obtained || status == ConsentStatus.NotRequired)
        {
            canShowAd = true;
            callback(canShowAd);
        }
        else if (status == ConsentStatus.Required)
        {
            while (!ConsentInformation.IsConsentFormAvailable()) yield return new WaitForSeconds(0.1f);//同意フォームが可能になるまで待つ

            Debug.Log("TryShowForm Start");

            ConsentForm.LoadAndShowConsentFormIfRequired((FormError error) =>
            {
                if (error != null)
                {
                    canShowAd = false;
                    Debug.Log($"TryShowForm Err: {error.Message}");
                }
                else
                {
                    canShowAd = true;
                }
            });

            while (canShowAd == null) yield return new WaitForSeconds(0.1f);               //同意フォーム表示処理が完了するまで待つ

            callback(canShowAd);
        }
        else
        {
            //念の為
            canShowAd = false;
            callback(canShowAd);
        }
    }



    ConsentRequestParameters _Parameters = null;
    ConsentRequestParameters MakeConsentParameters()
    {
        if (_Parameters != null) return _Parameters;

        _Parameters = new ConsentRequestParameters
        {
            TagForUnderAgeOfConsent = false,

       //これはテスト用なので本番では削除する
            ConsentDebugSettings = new ConsentDebugSettings
            {
                DebugGeography = DebugGeography.EEA,
            }
        };

        return _Parameters;
    }



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

        var request = new RequestConfiguration()
        {
            TagForChildDirectedTreatment = TagForChildDirectedTreatment.False,
            MaxAdContentRating = MaxAdContentRating.T,
        };

        MobileAds.SetRequestConfiguration(request);
    }


    IEnumerator AdFinalize()
    {
        bool? result = null;
        MobileAds.Initialize((InitializationStatus initstatus) =>
        {
            if (initstatus == null)
            {
                result = false;
            }
            else
            {
                result = true;
            }
        });

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

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

            if (BannerManager.Ad == null) BannerManager.Instance.LoadAd();
        }
    }
}

動作確認して見よう

Hierarchyには以下のように、AdMobManagerとBannerManagerのGameObjectが配置されていますか?

クラスファイルは以下のようになっていますか?AdMobManagerクラスはAdMobManagerのGameObjectに、BannerManagerクラスはBannerManagerのGameObjectにコンポーネントとしてアタッチされていますか?

ファイル構成

では、Unityのプレイボタンを押して動作確認して見ましょう。Unity上で動作確認するだけならば、Google Mobile Ads SettingsのGoogle Mobile Ads App IDは空でも大丈夫です。以下のようなGDPR同意フォームが表示されたら成功です😆💯。

GDPR form

終わりに

今回までの記事で、GDPR同意取得が取り敢えず行えるようになりました。いや、長かったですね。お疲れ様です。次回ですが、GDPRの規則では同意後の取消が必須ですので、その対応方法について説明します。また、iOSのATT(=App Tracking Transparency)への対応についても説明します。

このシリーズの記事

コメント