2021年01月22日

[Unity] iOS14のATT(App Tracking Transparency)に対応してAdMob広告を表示した

Unity製のアプリをiOS14のATT(App Tracking Transparency)に対応させてAdMob広告を表示した話。
部分的な情報はあったものの、ATT対応して広告を表示するまでの一連の流れが載っていなかったので、自分用にメモ。

環境:
・macOS Catalina (10.15.7)
・UnityHub2.4.2
・Unity2020.2.1f1 Personal
・Xcode12.3
・iOS14.3
使用SDK:
Google Mobile Ads Unity Plugin v5.4.0


手順:
@ 空のUnityプロジェクトを用意
A AdMobのUnityプラグインをインポート
B ATT対応
B-1. PostBuild処理にてinfo.plistへ追記
B-2. 独自のiOS用プラグインを用意
B-3. 独自プラグインを使った実装
C 実機テスト


@空のUnityプロジェクトを用意
UnityHubを起動。
新規作成ボタンの右にある△ボタンを押して、2020.2.1f1を選択。
テンプレートに「Mobile 3D」という選択肢があったので選択してみた。

20210121_01.png


AAdMobのUnityプラグインをインポート
AdMobページのMobile Ads SDK(Unity)のページへ移動。
ページ中段の「Mobile Ads Unity プラグインをダウンロードする」から、「プラグインをダウンロード」を選択。
2020年1月21日現在、Google Mobile Ads Unity Plugin v5.4.0 が最新版です。

画面中段に「Built and tested with:」という記載から
Google Mobile Ads iOS SDK 7.68.0
を発見。ATT対応は7.64.0以降なので、このSDKは対応していることを確認。

ページ下の「GoogleMobileAds-v5.4.0.unitypackage」のリンクから、パッケージをダウンロード。
ダウンロードしたパッケージファイルをUnityを開いた状態でWクリック。
インポートウィンドウが開くので、そのまますべてをUnityプロジェクトにインポート。

Android用にパッケージマネージャーへのレジストリの追加登録が聞かれるので、そのまま右下ボタン「Add Selected Registries」を選択。

20210121_02_1.png

レジストリ登録が完了すると、こんなメッセージが表示される。

20210121_02_2.png

さらに、「Migrating Packages」というウィンドウが表示されるので、ここでも何も考えず「Apply」を選択。

20210121_02_3.png

色々と自動で設定してくれるのはありがたい。

Unityメニュー > File > Build Settings からプラットフォームをiOSに変更
これで準備完了。

早速サンプルシーンを開いてみる。
インポートした中で、Asset > Scenes に SampleSceneファイルがあるので、Wクリックして開いてみる。

20210121_02_4.png

シーンの中身を確認したところ、ライトとカメラの2つのオブジェクトしか入ってなかった。
サンプルなんて入ってないじゃないか!

20210121_02_5.png

何も表示されなくていいので、とりあえず実機でのアプリ起動まで持っていくことにする。
スタートガイドに従ってプロジェクトの設定をしていく。


後で見直したところ、スタートガイド中段にある「Mobile Ads Unity プラグインをダウンロードする」下にある「ソースを見る」からGitHubに行くと、サンプルソースがありました。
サンプルソースのパス: https://github.com/googleads/googleads-mobile-unity/tree/master/samples



AdMobアプリIDを設定する

Unityメニュー > Assets > Google Mobile Ads > Settings...
を選択すると、インスペクターが表示されるので、
・Google AdMob > Enabled にチェックを入れる
・AdMob App IDのAndroidとiOSにテスト用AppIDを入力する

20210121_02_6.png

ちなみに、Google AdMob > Enabled にチェックを入れておかないとビルド時にエラーが出るので注意。

20210121_02_9.png

スタートガイドのスクショにあるAppIDはそのまま使えたので、テキストでも記載しておく(動作確認したのはiOSのみ)。

iOSca-app-pub-3940256099942544~1458002511
Androidca-app-pub-3940256099942544~3347511713


スクリプトとオブジェクトを新しく用意する

一番簡単そうなバナー広告を表示する。そのためにC#ファイルを作る。
ファイル名は「MySample」にした。

20210121_02_7.png

Hierarchyウィンドウに移り、空のオブジェクトを作成する。
そのオブジェクトに先ほど作成したMySampleのC#ファイルを適用する。

20210121_02_8.png

Unityの実行画面でバナー広告を表示する
MySample.cs
using GoogleMobileAds.Api;
public class MySample : MonoBehaviour
{
private BannerView bannerView;
void Start()
{
// AdMobを初期化
MobileAds.Initialize(initStatus => {
// AdMobからのコールバックはメインスレッドで呼び出される保証がないため、次のUpdate()で呼ばれるようにMobileAdsEventExecutorを使用
MobileAdsEventExecutor.ExecuteInUpdate(() => {
// バナーをリクエスト
this.RequestBanner();
});
});
}
private void RequestBanner()
{
#if UNITY_ANDROID
string adUnitId = "ca-app-pub-3940256099942544/6300978111";
#elif UNITY_IPHONE
string adUnitId = "ca-app-pub-3940256099942544/2934735716";
#else
string adUnitId = "unexpected_platform";
#endif
// 画面上に320x50のバナーを用意
this.bannerView = new BannerView(adUnitId, AdSize.Banner, AdPosition.Top);
// 広告をリクエスト
AdRequest request = new AdRequest.Builder().Build();
// バナーを読み込む
this.bannerView.LoadAd(request);
}
}

広告イベントは割愛して、これでバナー広告が表示された。
※Unityの実行画面で確認できた

20210121_02_10.png

詳しくはバナー広告のページを確認する。
プログラムで使用しているテスト用の広告ユニットIDは、ここに記載されてあったものを使用した。

実機でバナー広告を表示する

Unityの実行画面ででバナー広告を表示できたので、次は実機で表示してみる。
Unityメニュー > File > Build Settings... でBuild Settingsウィンドウを開く。
・Add Open Scenes で、今開いている「SampleScene」を「Scenes in Build」に追加。
・Platform = iOS を確認。
・Player Settingの設定はそのまま
上記3点を確認したら、Buildボタンを押してビルドする。

20210121_02_11.png

ビルド結果の保存先を聞かれるので、プロジェクトのディレクトリ内に「iOS」とか適当なディレクトリを作って選択。

ビルド中になぜか「TMP Importer」が起動した。
何も考えず、「Import TMP Essentials」を選択して更新しておく。

20210121_02_12.png

ビルドが成功すると、先ほど作ったiOSディレクトリの中にXcodeプロジェクトが保存される。
その中から、Unity-iPhone.xcworkspaceをWクリックして開く。
※拡張子が「.xcodeproj」の方ではないので注意

Xcodeの設定をして実機で確認する

Xcode側での作業は、Appleの開発者アカウントを自分のものに変更しただけだった。
左メニューのプロジェクト(Unity-iPhone)を選択 > TARGETS > Signing & Capabilities > 自分のApple開発者アカウントを設定

これでXcodeでビルド&実行すると、無事、実機でもバナー広告が表示された。
※バナー上部の隙間は、セーフエリアだと思われる

20210121_02_13.png


BATT対応
このままだとATT(App Tracking Transparency)には未対応なので、ユーザーの好みじゃない広告が表示される可能性が高くなるし、アップルの審査にもひっかかってしまう。

参考ページによると、iOS14からはATT(App Tracking Transparency)とSKAdNetworkを設定する必要があるそうです。

ATTに対応してAdMob広告を表示するためには、いくつかの手順がある。

(1) iOS用のAdMob SDKのバージョンが7.64.0以上を使用
(2) GoogleのSKAdNetwork IDを設定
(3) Privacy - Tacking Usage Description(NSUserTrackingUsageDescription)を設定
(4) AppTrackingTransparency.frameworkを参照するように設定(iOSプラグインにて必要)


(1)については、AdMobのUnity用プラグイン(ver5.4.0)の説明にて「Google Mobile Ads iOS SDK 7.68.0」とあるのでクリア。

(2)についても、(1)と同じ説明ページにて「Add support for iOS14 with Google's SKAdNetwork identifiers automatically included in Info.plist.」とあるので、info.plistにSKAdNetwork IDが自動的に設定されるのでクリア。

(3)と(4)については、Xcode側に毎回手入力するのは効率が悪いので、Unity側でPostBuild処理にて行うようにする。


B-1. PostBuild処理にてinfo.plistへ追記
先ほど述べた
(3) Privacy - Tacking Usage Description(NSUserTrackingUsageDescription)を設定
(4) AppTrackingTransparency.frameworkを参照するように設定(iOSプラグインにて必要)

について、対応する。

(3)についてここを、(4)についてはここを参考にした。

PostBuild処理については、C#ファイルをAssets > Editorディレクトリ配下に置く必要がある。ファイル名は参考ページのまま「PostBuildProcessForIosAtt」にした。

20210121_03_1.png

PostBuildProcessForIosAtt.cs
#if UNITY_IOS
using System.IO;
using UnityEditor.iOS.Xcode;
using UnityEditor;
using UnityEditor.Callbacks;

namespace iOS.Editor
{
public class PostBuildProcessForIosAtt
{
private const string ATT_FRAMEWORK = "AppTrackingTransparency.framework";

[PostProcessBuild]
public static void OnPostProcessBuild(BuildTarget buildTarget, string buildPath)
{
if (buildTarget != BuildTarget.iOS)
{
return;
}
// pbxに AppTrackingTransparency.framework を追加する
var pbxPath = PBXProject.GetPBXProjectPath(buildPath);
var pbx = new PBXProject();
pbx.ReadFromFile(pbxPath);
string target = GetUnityMainTargetGuidWithCompatible(pbx);
pbx.AddFrameworkToProject(target, ATT_FRAMEWORK, true);
pbx.WriteToFile(pbxPath);

// Info.plist に Privacy - Tacking Usage Description(NSUserTrackingUsageDescription)を追加する
var path = buildPath + "/Info.plist";
var plist = new PlistDocument();
plist.ReadFromFile(path);
var root = plist.root;
root.SetString( "NSUserTrackingUsageDescription", "好みに合わせた広告を表示するために使用されます。");
plist.WriteToFile(path);
}

private static string GetUnityMainTargetGuidWithCompatible(PBXProject pbx)
{
#if UNITY_2019_3_OR_NEWER
return pbx.GetUnityFrameworkTargetGuid();
#else
return pbx.TargetGuidByName(PBXProject.GetUnityTargetName());
#endif
}
}
}
#endif

UnityでビルドしてできたXcodeプロジェクトの中身を確認してみると、info.plistにちゃんと反映されていることが確認できた。

20210121_03_1_2.png

AppTrackingTransparency.frameworkについては、見つけられませんでした。。。


B-2. 独自のiOS用プラグインを用意
UnityからObjective-Cを使う必要があるので、プラグインを作成する。
「MyObjC.mm」というテキストファイルを外部エディタ等を使って用意し、Assets > Plugins > iOS に保存する。
※最近だとこの階層にプラグインファイルを置かなくても読み込まれるそうだけど、慣習にならって…。

20210121_03_2.png

プラグイン(MyObjC.mm)の中身は、参考ページのままで、コメントを加えた程度。

MyObjC.mm
#import <string.h>
// #import <apptrackingtransparency attrackingmanager="" h=""> <- Xcodeでビルドエラーになった
#import <AppTrackingTransparency/ATTrackingManager.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* ATT承認状態を取得(同期)
**/
int Sge_Att_getTrackingAuthorizationStatus()
{
if (@available(iOS 14, *)) {
// enum値はそのままではC#に渡せないためにネイティブ側でintに変換
// ATTrackingManagerAuthorizationStatusNotDetermined = 0
// ATTrackingManagerAuthorizationStatusRestricted = 1
// ATTrackingManagerAuthorizationStatusDenied = 2
// ATTrackingManagerAuthorizationStatusAuthorized = 3
return (int)ATTrackingManager.trackingAuthorizationStatus;
} else {
return -1;
}
}

/**
* ATT承認を要求(非同期)
**/
typedef void (*Callback)(int status);
void Sge_Att_requestTrackingAuthorization(Callback callback)
{
if (@available(iOS 14, *)) {
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
if (callback != nil) {
if (status == ATTrackingManagerAuthorizationStatusNotDetermined) {
NSLog(@"ATT status = NotDetermined(未設定) -> 端末設定でOK、アプリで未選択");
} else if (status == ATTrackingManagerAuthorizationStatusRestricted) {
NSLog(@"ATT status = Restricted(制限あり)");
} else if (status == ATTrackingManagerAuthorizationStatusDenied) {
NSLog(@"ATT status = Denied(不許可) -> 端末設定orアプリで不許可");
} else if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
NSLog(@"ATT status = Authorized(許可)");
} else {
NSLog(@"ATT status = Other(その他)");
}
callback((int)status);
}
}];
} else {
NSLog(@"ATT status = iOS14 未満(非対応)");
callback(-1);
}
}
#ifdef __cplusplus
}
#endif

C#側からObjective-Cにアクセスするためのクラスを用意。こちらの中身も、参考ページのままで、コメントを加えた程度。
ファイルの保存場所は、Assets > Scripts。

20210121_03_2_2.png

ObjC.cs
using System;                           // for Action
using System.Runtime.InteropServices; // for DllImport
using System.Threading; // for SynchronizationContext
using UnityEngine; // for Application

public class ObjC
{
#if UNITY_IOS
private const string DLL_NAME = "__Internal";

/**
* ATT承認状態を取得(同期)
**/
[DllImport(DLL_NAME)]
private static extern int Sge_Att_getTrackingAuthorizationStatus();
public static int GetTrackingAuthorizationStatus()
{
if (Application.isEditor) {
return -1;
}
return Sge_Att_getTrackingAuthorizationStatus();
}


/**
* ATT承認を要求(非同期)
**/
[DllImport(DLL_NAME)]
private static extern void Sge_Att_requestTrackingAuthorization(OnCompleteCallback callback);

private delegate void OnCompleteCallback(int status);
private static SynchronizationContext _context;
private static Action<int> _onComplete;

public static void RequestTrackingAuthorization(Action<int> onComplete)
{
if (Application.isEditor) {
// 呼出元のActionの引数を0にして実行する
onComplete?.Invoke(0);
return;
}
_context = SynchronizationContext.Current;
_onComplete = onComplete;
Sge_Att_requestTrackingAuthorization(OnRequestComplete);
}

[AOT.MonoPInvokeCallback(typeof(OnCompleteCallback))]
private static void OnRequestComplete(int status)
{
if (_onComplete != null) {
_context.Post(_ => {
// if (_onComplete!=null) { _onComplete(status); } を省略した書き方
_onComplete?.Invoke(status);
_onComplete = null;
}, null);
}
}
#endif
}

ここまできて、やっとATT対応の準備が整った。


B-3. 独自プラグインを使った実装
Aで作成したMySample.csに、ATT対応を追記する。
流れとしては、
(1) ATTの設定状態を見る
(2) ATTの設定状態によって、ATT承認要求するか、ATT承認なしのまま広告表示するかに別れる

MySample.cs
using System;
using UnityEngine;
using GoogleMobileAds.Api;
using GoogleMobileAds.Common; // for MobileAdsEventExecutor

public class MySample : MonoBehaviour
{
private BannerView bannerView;
void Start()
{
int status = ObjC.GetTrackingAuthorizationStatus();
Debug.Log("ATT状態 = " + status);
// ATT状態は4択
// ATTrackingManagerAuthorizationStatusNotDetermined = 0
// ATTrackingManagerAuthorizationStatusRestricted = 1
// ATTrackingManagerAuthorizationStatusDenied = 2
// ATTrackingManagerAuthorizationStatusAuthorized = 3
if (status == 0) {
// ATT設定可能 & 未承認なのでATT承認要求アラートを表示
ObjC.RequestTrackingAuthorization(CallbackFunction);
} else {
if (status == 1 || status == 2) {
// ATT設定不可なので、ATT承認が必要になる旨をユーザーに伝える
}
// Google Mobile Ads SDK を初期化
MobileAds.Initialize(initStatus => {
// AdMobからのコールバックはメインスレッドで呼び出される保証がないため、次のUpdate()で呼ばれるようにMobileAdsEventExecutorを使用
MobileAdsEventExecutor.ExecuteInUpdate(() => {
// バナーをリクエスト
this.RequestBanner();
});
});
}
}

void CallbackFunction(int status)
{
Debug.Log("ATT最新状況 --> " + status);
// ATTの状況を待ってから Google Mobile Ads SDK を初期化
MobileAds.Initialize(initStatus => {
// AdMobからのコールバックはメインスレッドで呼び出される保証がないため、次のUpdate()で呼ばれるようにMobileAdsEventExecutorを使用
MobileAdsEventExecutor.ExecuteInUpdate(() => {
// バナーをリクエスト
this.RequestBanner();
});
});
}

private void RequestBanner()
{
#if UNITY_ANDROID
string adUnitId = "ca-app-pub-3940256099942544/6300978111";
#elif UNITY_IPHONE
string adUnitId = "ca-app-pub-3940256099942544/2934735716";
#else
string adUnitId = "unexpected_platform";
#endif
// 画面上に320x50のバナーを用意
this.bannerView = new BannerView(adUnitId, AdSize.Banner, AdPosition.Top);
// 広告をリクエスト
AdRequest request = new AdRequest.Builder().Build();
// バナーを読み込む
this.bannerView.LoadAd(request);
}
}



C実機テスト
実機にて実行してみる。
初めて実行すると通知の許可を求められるので、ここは「許可」を選ぶ。

20210121_03_3_1.png

次に、Privacy - Tacking Usage Descriptionで設定した文言が入ったATT承認要求アラート が表示される。

20210121_03_3_2.png

「Appにトラッキングしないように要求」を選択した場合のログ。
ATT状態 = 0
ATT status = Denied(不許可) -> 端末設定orアプリで不許可
ATT最新状況 --> 2

「許可」を選択した場合のログ。
ATT状態 = 0
ATT status = Authorized(許可)
ATT最新状況 --> 3

ATT承認要求アラートが表示されない場合、
端末設定のプライバシー > トラッキング
にて、トラッキング要求が不許可になっているので、こちらで「許可」をしておく必要がある。

20210121_03_3_3.png

最後に、端末設定のトラッキング要求が不許可になっている場合のログ。
端末の画面ではATT承認要求アラート が表示されず、バナーが表示された画面に移る。
ATT状態 = 2
ATT status = Denied(不許可) -> 端末設定orアプリで不許可
ATT最新状況 --> 2

以上で、ATT対応して広告を表示するまでの一連の流れでした。
振り返ってみると長かったー。

あとはアップルの審査に通るかどうか。
結果は後日にてっ!
posted by be-style at 01:19| Comment(2) | Unity
この記事へのコメント
有益な情報、誠にありがとうございます
ObjC.csですが、Invoke(0), Invoke(status)として呼び出されているため型はActionではなくAction<int>ではないでしょうか?
Posted by at 2021年05月10日 07:59
ご指摘ありがとうございます。
気づくのが遅くなり申し訳ございません。

原文ではちゃんと記述していたのですが、HTMLタグの「<int>」と認識されていたため表示されていませんでした。

修正しましたので、ちゃんと<int>になりました。
Posted by be-style at 2021年12月12日 10:01
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

※ブログオーナーが承認したコメントのみ表示されます。