前回までで、GDPR同意フォーム(=GDPRメッセージ)を表示できるようになりました。今回は、同意取り消しのために、同意フォームを再表示できるようにしましょう。また、iOSのATT(AppTrackingTransparency)の対応も行いましょう。最後にシミュレータでの動作確認を行いましょう。私は専門家ではないので、記事の内容は必ずしも正確ではないかもしれません。今回がシリーズ最後の記事です。
同意取り消し
GDPRの規則ではユーザーが同意を取り消すためのプロセスを備えていることが必須となっています(Googleヘルプ)。同意フォームには「Consent」と「Manage options」というボタンがありますが、「Manage options」の機能がどうもそのプロセスに対応しているようです。だから、ユーザーがいつでも同意フォームを呼び出せるようにしておけば、その要件を満たすことができると思われます。
では、ゲーム内に同意フォームを呼び出すボタンを設置しましょう。
次にReconsentManagerというGameObjectを作り、同名のクラスファイルをアタッチしましょう。
以下のコードをAdMobManagerクラスに追加してください。これは同意フォームが再表示可能であることを知るためのメソッドです。Ad_Already_StartedはStaticに変更しておいてください。iOSの場合はATTの許可が得られていることを再表示のための必須条件にしています。
public static bool IsReconsentEnabled()
{
var b1 = ConsentInformation.PrivacyOptionsRequirementStatus == PrivacyOptionsRequirementStatus.Required;//欧州
var b2 = ConsentInformation.ConsentStatus == ConsentStatus.Obtained;
var b3 = Ad_Already_Started;
#if UNITY_IOS
var b4 =
(ATTrackingStatusBinding.GetAuthorizationTrackingStatus() == ATTrackingStatusBinding.AuthorizationTrackingStatus.AUTHORIZED) ||
(ATTrackingStatusBinding.GetAuthorizationTrackingStatus() == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED);//オプション次第ではこれになる
Debug.Log($"IsReconsentEnabled:ATTStatus={ATTrackingStatusBinding.GetAuthorizationTrackingStatus()}");
#else
var b4 = true;//iOS以外はいつもTrue
#endif
Debug.Log($"IsReconsentEnabled:OPTION={b1},CONSENT={b2},AD_START={b3},ATT={b4}");
return b1 && b2 && b3 && b4;
}
また、次のコードもAdMobManagerに追加しておいてください。
public static bool IsEnabled {get; private set;} = false;
そして、AdMobManagerのTryAdMob_EventメソッドのCANCEL以下に次のコードを追加してください。
CANCEL:
Debug.Log($"CanRequestAds = {ConsentInformation.CanRequestAds()}");
TryAdMob_Busy = false;
if(!IsEnabled) IsEnabled = true; //←←←←←←←←←←←こいつを追加
ReconsentManagerクラスには次のコードを書いてください。Reconsent_Clickメソッドを先ほど作成したButtonにアタッチしてください。また、ReconsentManagerのReconsent_BtnにそのButtonをアタッチしてください。
using System.Collections;
using GoogleMobileAds.Ump.Api;
using UnityEngine;
using UnityEngine.UI;
public class ReconsentManager : MonoBehaviour
{
[Space(10)]
[Header("ボタン")]
[SerializeField] private Button Reconsent_Btn;
private IEnumerator Start()
{
while (!AdMobManager.IsEnabled) yield return null;
yield return UpdateReconsentBtns();
}
public IEnumerator UpdateReconsentBtns()
{
if (AdMobManager.IsReconsentEnabled())
{
//欧州かつ条件が整った場合Reconsent有効
if (!Reconsent_Btn.gameObject.activeSelf) Reconsent_Btn.gameObject.SetActive(true);
Debug.Log("Show ReconsentBtns");
}
else
{
//それ以外無効 (iOSでは機能しないボタンをアプリ上に表示してはいけないルールがある)
Reconsent_Btn.gameObject.SetActive(false);
Debug.Log("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());
}
static bool? ReconsentStatus = null;
private IEnumerator Reconsent_Event()
{
bool isOnline = false;
yield return InternetChecker.Check(x => isOnline = x);
if (!isOnline)
{
goto CANCEL;
}
//エラーになるのでエディタはフォームを出さない
var b = Application.platform == RuntimePlatform.OSXEditor ||
Application.platform == RuntimePlatform.WindowsEditor ||
Application.platform == RuntimePlatform.LinuxEditor;
if (!b)
{
ReconsentStatus = null;
ConsentForm.ShowPrivacyOptionsForm(OnShowOption);
while (ReconsentStatus == null) yield return new WaitForSeconds(0.1f);
yield return new WaitForSeconds(1.5f);
}
else
{
Debug.LogError("---Editor---");
}
CANCEL:
Reconsent_Lock = false;
}
void OnShowOption(FormError error)
{
//これはリトライしたら固まるメソッド
if (error != null)
{
ReconsentStatus = false;
Debug.Log($"ShowOption Err Code={error.ErrorCode}: {error.Message}");
}
else
{
Debug.Log($"ShowOption OK");//フォームが閉じられる時に出る
ReconsentStatus = true;
}
}
}
以上で実装は完了です。Playボタンを押して動作確認してみてください。どうでしょうか?「Ads options」ボタンを押したら同意フォームが表示されましたか?されませんよね?それは以下のところで、Editorを除外しているからです。
var b = Application.platform == RuntimePlatform.OSXEditor ||
Application.platform == RuntimePlatform.WindowsEditor ||
Application.platform == RuntimePlatform.LinuxEditor;
if (!b)
{
ReconsentStatus = null;
ConsentForm.ShowPrivacyOptionsForm(OnShowOption);
while (ReconsentStatus == null) yield return new WaitForSeconds(0.1f);
yield return new WaitForSeconds(1.5f);
}
else
{
Debug.LogError("---Editor---");
}
以下のように書き換えて再度Playボタンを押してみましょう。
var b = true
if (!b)
{
ReconsentStatus = null;
ConsentForm.ShowPrivacyOptionsForm(OnShowOption);
while (ReconsentStatus == null) yield return new WaitForSeconds(0.1f);
yield return new WaitForSeconds(1.5f);
}
else
{
Debug.LogError("---Editor---");
}
どうでしょうか?以下のようなForm not found!のエラーメッセージが出ましたよね。少し騙したみたいで申し訳ありませんが、皆さんがこの罠にハマらないために体験していただきました。同意フォームの再表示は、どうもEditorではサポートされていないようです。だから、動作確認は実機またはシミュレータで行いましょう。先ほどのbooleanは元に戻しておいてください。
つまり、Unity上でできることはここまでとなります。動作確認は、実機またはXcodeのシミュレータ上で行う必要があります。
実機確認の準備
以前にも申しましたが、実機での動作確認には以下の準備が必要です。
「メッセージの作成」をしておかないと、実機では同意フォーム(=GDPRメッセージ)が表示されずエラーになります。また、日本語の同意フォームを実機で表示すると、私の経験上、高確率で通信が失敗します。なので、英語バージョンを表示するようにしましょう。AdMob管理画面で日本語バージョンを作らず、英語バージョンを作ることでこれを実現できます。そして、ダミー広告だと同意取得後に広告が表示されないことがあります。このように落とし穴が満載なのです。前述した「同意フォームの再表示がEditor上ではできないこと」も落とし穴の一つといえるでしょう。
iOSの場合はAdMob管理画面で、IDFA説明メッセージも作っておきましょう。IDFA説明メッセージはATTメッセージの前に表示され、ATTの同意をユーザーにお願いするためのメッセージです。同意率が上がるかもしれませんので、日本語を含め可能な限り全ての言語で有効にしておきましょう。なお、EUのユーザーにはGDBR同意フォームを表示し、EU以外のユーザーにはIDFA説明メッセージを表示するというのがUMPの仕様のようです。
実機確認
さて、実機確認(シミュレータでの確認)を行いましょう。この例では、私が以前に作成したゲーム「マジックぷう」を使い、iOSアプリとしてビルドしてシミュレータで動作確認する手順を説明します。皆さんは、ご自分のアプリをAdMob管理画面に登録してから動作確認を行なってください。
ビルド
Project Settings > Player > Target SDKをSimulator SDKにして、ビルドします。
いくつかファイルが生成されますが、拡張子がxcworkspaceのものをダブルクリックして開きます。
Xcodeが立ち上がるのを待ちます。Unity-iPhone > UnityFrameworks > Build Phases > Link Binary With Librariesのところで「+」ボタンを押してAppTrackingTransparency.framework(※)を追加します。(※追加しなくてもATTメッセージは自動で出現するようですが、念の為追加します。)
Product > Buildを選択して、シミュレータが立ち上がるのを待ちましょう。動作がおかしい場合は、Product > Clean Build Folderで一度ビルドフォルダを空にしてから再度ビルドを行いましょう。
EUの場合の動作確認(ATT許可の場合)
DebugGeography = DebugGeography.EEAが設定されていて、かつ、正常に起動すれば以下のようにGDPRメッセージが表示されます(※)。(※デバッグ目的で起動のたびにGBDRメッセージを表示する場合は、Startメソッドの中かどこかでConsentInformation.Reset()を実行することが必要です。)
Consent(=同意する)を押してみましょう。すると、以下のようにATTメッセージが表示されます。
許可を押してみましょう。タイトル画面が表示され、バナーが表示され、Ads optionsボタンが表示されました。
Ads optionsボタンを押してみましょう。すると、以下のようにGDPRメッセージを再度表示することができました。
EUの場合の動作確認(ATT不許可の場合)
EUの場合で、ATTメッセージで「アプリにトラッキングしないように要求」を押した場合は、パーソナライズされていないバナーが表示され、Ads optionsボタンは表示されません。
EU以外の場合の動作確認
DebugGeography = DebugGeography.EEAを設定せず、あなたのお住まいの地域が日本ならば、GDPRメッセージが表示される代わりにIDFA説明メッセージが表示されます。なお、IDFA説明メッセージの内容はAdMob管理画面でカスタマイズできます。
続行を押すとATTメッセージが表示されます。
どちらかのボタンを押すとタイトル画面が表示され、バナーが表示され、もちろんAds optionsボタンは表示されません。
完成状態(全コード)
全7回の「UnityでAdMobをGDPR対応させる」シリーズで紹介したものの完成状態は以下のようになります。
画面
記事通りだと、以下のような画面になります。
ヒエラルキー
記事通りだと、ヒエラルキーは以下のようになります。AdMobManager、BannerManager、ReconsentManagerはすべて空のオブジェクトです。
クラスファイル
記事通りだと、クラスファイルは以下のようになります。InternetCheckerのみ静的クラスですので、どこにもアタッチしません。それ以外のクラスは、同名のオブジェクトにアタッチしてください。
AdMobManagerクラスの全コード
記事通りだと、AdMobManagerクラスの全コードは以下のようになります。なお、コード内のConsentInformation.Reset()やConsentDebugSettingsは、あくまでデバッグ用ですので、本番の場合は必ず削除してください。
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
static bool Ad_Already_Started = false;
public static bool IsEnabled { get; private set; } = 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();
yield return null;
yield break;
}
public void TryAdMob()
{
if (TryAdMob_Busy || Ad_Already_Started) return;
TryAdMob_Busy = true;
StartCoroutine(TryAdMob_Event());
}
static 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;
if (!IsEnabled) IsEnabled = true;
yield return null;
yield break;
}
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);
yield return null;
yield break;
}
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);
}
yield return null;
yield break;
}
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);
yield return null;
yield break;
}
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();
}
yield return null;
yield break;
}
public static bool IsReconsentEnabled()
{
var b1 = ConsentInformation.PrivacyOptionsRequirementStatus == PrivacyOptionsRequirementStatus.Required;//欧州
var b2 = ConsentInformation.ConsentStatus == ConsentStatus.Obtained;
var b3 = Ad_Already_Started;
#if UNITY_IOS
var b4 =
(ATTrackingStatusBinding.GetAuthorizationTrackingStatus() == ATTrackingStatusBinding.AuthorizationTrackingStatus.AUTHORIZED) ||
(ATTrackingStatusBinding.GetAuthorizationTrackingStatus() == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED);//オプション次第ではこれになる
Debug.Log($"IsReconsentEnabled:ATTStatus={ATTrackingStatusBinding.GetAuthorizationTrackingStatus()}");
#else
var b4 = true;//iOS以外はいつもTrue
#endif
Debug.Log($"IsReconsentEnabled:OPTION={b1},CONSENT={b2},AD_START={b3},ATT={b4}");
return b1 && b2 && b3 && b4;
}
}
BannerManagerクラスの全コード
記事通りだと、BannerManagerクラスの全コードは以下のようになります。
using GoogleMobileAds.Api;
public class BannerManager
{
public static BannerManager Instance;
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
public static BannerView Ad { get; private set; } = null;
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()
{
LoadAd_Lock = false;
}
void OnFailed(LoadAdError error)
{
DestroyBannerAd();
LoadAd_Lock = false;
}
public void DestroyBannerAd()
{
Ad?.Destroy();
Ad = null;
}
}
ReconsentManagerクラスの全コード
記事通りだと、ReconsentManagerクラスの全コードは以下のようになります。Reconsent_BtnにはCanvasのAds optionsボタンを入れてください。
using System.Collections;
using GoogleMobileAds.Ump.Api;
using UnityEngine;
using UnityEngine.UI;
public class ReconsentManager : MonoBehaviour
{
[Space(10)]
[Header("ボタン")]
[SerializeField] private Button Reconsent_Btn;
private IEnumerator Start()
{
while (!AdMobManager.IsEnabled) yield return null;
yield return UpdateReconsentBtns();
yield return null;
yield break;
}
internal IEnumerator UpdateReconsentBtns()
{
if (AdMobManager.IsReconsentEnabled())
{
//欧州かつ条件が整った場合Reconsent有効
if (!Reconsent_Btn.gameObject.activeSelf) Reconsent_Btn.gameObject.SetActive(true);
Debug.Log("Show ReconsentBtns");
}
else
{
//それ以外無効 (iOSでは機能しないボタンをアプリ上に表示してはいけないルールがある)
Reconsent_Btn.gameObject.SetActive(false);
Debug.Log("Hide ReconsentBtns");
}
yield return null;
yield break;
}
static bool Reconsent_Lock = false;//絶対に二重起動させない
public void Reconsent_Click()
{
if (!AdMobManager.IsReconsentEnabled()) return;
if (Reconsent_Lock) return;
Reconsent_Lock = true;
StartCoroutine(Reconsent_Event());
}
static bool? ReconsentStatus = null;
private IEnumerator Reconsent_Event()
{
bool isOnline = false;
yield return InternetChecker.Check(x => isOnline = x);
if (!isOnline)
{
goto CANCEL;
}
//エラーになるのでエディタはフォームを出さない
var b = Application.platform == RuntimePlatform.OSXEditor ||
Application.platform == RuntimePlatform.WindowsEditor ||
Application.platform == RuntimePlatform.LinuxEditor;
if (!b)
{
ReconsentStatus = null;
ConsentForm.ShowPrivacyOptionsForm(OnShowOption);
while (ReconsentStatus == null) yield return new WaitForSeconds(0.1f);
yield return new WaitForSeconds(1.5f);
}
else
{
Debug.LogError("---Editor---");
}
CANCEL:
Reconsent_Lock = false;
yield return null;
yield break;
}
void OnShowOption(FormError error)
{
//これはリトライしたら固まるメソッド
if (error != null)
{
ReconsentStatus = false;
Debug.Log($"ShowOption Err Code={error.ErrorCode}: {error.Message}");
}
else
{
Debug.Log($"ShowOption OK");//フォームが閉じられる時に出る
ReconsentStatus = true;
}
}
}
InternetCheckerクラスの全コード
記事通りだと、InternetCheckerクラスの全コードは以下のようになります。接続確認用URLは必ずあなたが所有するサイトのURLに置き換えてください。
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対応させたゲームをいくつかリリースしています。もしよろしければ、ダウンロードしていただければと思います。よろしくお願いします。
2024.7.30 | NEW!新作 脱出サムルームズ 6ステージの部屋脱出ゲーム(2024.11.12現在)。ステージ1はチュートリアル。難易度は初級。5と6ステージは少し難しい。 | |
2023.11.27 | NEW!新作 マジックぷう 異世界が舞台のパズルアドベンチャー。悪い魔女にカエルにされた王子を救出するために、可愛い魔女はお城へ向かう。全20面。 このゲームのヒントはこちら | |
2024.3.1 | NEW!新作 牢屋脱獄パズル 「じーや」が牢屋に閉じ込められた。パズルを解いて「じーや」の牢屋脱獄を助けよう。短いステージの牢屋脱獄パズル。全50面。 このゲームのヒントはこちら |
コメント