cocos2d-x v4でsdkboxを使ってadmob広告を表示する

# 20220702追記
SDKBoxは更新を終了した。
https://docs.sdkbox.com/en/qa/SDKBox_Shutdown_Announcement/

# プロジェクトの作成方法
前回の記事
https://qiita.com/noprops/items/cc022f326e8bc4f7cfe5

# やること
cocos2d-x v4のプロジェクトにsdkboxでadmobをインポートし、
・バナー
・インタースティシャル(全画面)
・リワード
の広告を実装する。

# 環境
cocos2d-x-4.0
Cocos Console 2.3
SDKBOX v1.4.6.1
Python 2.7.15
macOS Monterey 12.0.1
Xcode Version 13.2.1 (13C100)

# SDKBOXのインストール

http://www.sdkbox.com/plugins/sdkboxads
上記のページへ行ってログインする。

guiもあるが、今回はコマンドラインツールでインストールする。
以下のコマンドをターミナルに貼り付けて実行する。

```
python -c "import urllib; s = urllib.urlopen('https://raw.githubusercontent.com/sdkbox-doc/en/master/install/install.py').read(); exec s"
```

sdkboxはpython2.7で動くので、macのデフォルトのpythonを3に変更している場合はなんとかする必要がある。
成功すればSDKBox installer have been installedと表示される。

また、Homebrewでopensslをインストールしている場合に以下のようなエラーが出る場合がある。

```
ERROR:root:code for hash md5 was not found.
Traceback (most recent call last):
  File "/usr/local/Cellar/python@2/2.7.15_3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py", line 147, in <module>
    globals()[__func_name] = __get_hash(__func_name)
  File "/usr/local/Cellar/python@2/2.7.15_3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py", line 97, in __get_builtin_constructor
    raise ValueError('unsupported hash type ' + name)
```

この場合次のように対応する。
ターミナルで以下を実行。

```
$ ls /usr/local/Cellar/openssl
```

インストールされているopensslのバージョンが表示される。

```
1.0.2h_1	1.0.2j		1.0.2l		1.0.2n		1.0.2o_1
```

存在しているバージョンの中からどれかを選んでswitchする。

```
$ brew switch openssl 1.0.2o_1
```

これでエラーが出なくなる。

上記のコマンドを実行すればすぐにsdkboxが使えるようになるはずだったが、sdkboxを実行しようとするとコマンドが見つからないと言われた。

```
$ sdkbox version
zsh: command not found: sdkbox
```

sdkboxインストーラは.bash_profileに何か書き込んでいるが、Catalinaでmacのデフォルトシェルがbashからzshに変更されたので、これを.zshrcに書き写さなければいけない。
.bash_profileを開いて下記のような記述を見つけてコピーし、.zshrcに貼り付ける。

```
# Add environment variable SDKBOX_HOME for sdkbox installer
export SDKBOX_HOME=/Users/username/.sdkbox
export PATH=${SDKBOX_HOME}/bin:$PATH
```

ターミナルで変更を反映すると、sdkboxが使えるようになった。

```
$ source ~/.zshrc
$ sdkbox version
  _______ ______  _     _ ______   _____  _     _
  |______ |     \ |____/  |_____] |     |  \___/ 
  ______| |_____/ |    \_ |_____] |_____| _/   \_
 Copyright (c) 2016-2020 SDKBOX Inc. v1.4.6.1
 SDKBOX version 1.4.6.1
```

# プロジェクトにインポート

前回作成したプロジェクトにSDKBOXを使ってAdMobをインポートする。
https://qiita.com/noprops/items/cc022f326e8bc4f7cfe5
インポートしたいプロジェクトのルートに移動してからsdkbox importでインポートする。

```
$ cd CocosTest
$ sdkbox import admob
```
成功すればInstallation Successful :)と表示される。

CMakeLists.txtを開いて、xcframeworkを追加している以下の6行をコメントアウトする。

```CMakeLists.txt
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} GoogleAppMeasurement.xcframework")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} GoogleMobileAds.xcframework")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} GoogleUtilities.xcframework")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} PromisesObjC.xcframework")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} nanopb.xcframework")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} UserMessagingPlatform.xcframework")
```

その後ios-buildフォルダに移動してcmake ..を実行する。

```
cd ios-build
cmake ..
```

ios-buildフォルダにあるXcodeプロジェクトを開く。
Xcodeの左ペインで右クリックしてNew GroupからFrameworksというグループを作る。
proj.ios_macフォルダ内にある
GoogleAppMeasurement.xcframework/ios-arm64_armv7/GoogleAppMeasurement.framework
GoogleMobileAds.xcframework/ios-arm64_armv7/GoogleMobileAds.framework
GoogleUtilities.xcframework/ios-arm64_armv7/GoogleUtilities.framework
PromisesObjC.xcframework/ios-arm64_armv7/PromisesObjC.framework
nanopb.xcframework/ios-arm64_armv7/nanopb.framework
UserMessagingPlatform.xcframework/ios-arm64_armv7/UserMessagingPlatform.framework
の6つをXcodeのFrameworksにドラッグしてインポートする。

![スクリーンショット 2020-09-29 2.16.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37640/b3a66c60-e2d1-3176-efc2-1d4bdda1ea1b.png)

インポートするときのダイアログでCopy items if neededにチェックを入れなかった場合は、自分で追加したフレームワークをLink Binary With Librariesに追加する必要がある。

TARGETS->アプリ名->Build Phasesを表示し、左上の+ボタンを押してLink Binary With Librariesを追加する。
そこに左ペインのFrameworksグループから、先ほどインポートした6つのフレームワークをドラッグする。

![スクリーンショット 2020-09-29 2.28.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37640/f8ffde70-9440-9ab1-6c74-b62aa7655ff6.png)

TARGETS->アプリ名->Build Settingsを開き、Installation Directoryに/Applicationsという文字列を追加する。

File->Project Settingを開き、Build SystemをNew Build System (Default)に変更する。

![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37640/a707fa18-6f7f-17d6-affd-432076025e4a.png)

# sdkbox_config.jsonの編集

Resourcesフォルダにあるsdkbox_config.jsonを編集する。
今回はAdMobのみ使うので、UnityAd, SDKBoxAdsなどはすべて消して次のようにする。

```json:sdkbox_config.json
{
    "android": {
        "AdMob": {
            "testdevice": "FE20924C46522E2E204587EB339897C6,kGADSimulatorID", 
            "test": false, 
            "ads": {
                "interstitial": {
                    "type": "interstitial", 
                    "id": "ca-app-pub-3940256099942544/1033173712"
                }, 
                "rewarded": {
                    "type": "rewarded_video", 
                    "id": "ca-app-pub-1329374026572143/8251968111"
                }, 
                "banner": {
                    "width": 300, 
                    "type": "banner", 
                    "id": "ca-app-pub-3940256099942544/6300978111", 
                    "alignment": "bottom", 
                    "height": 50
                }
            }, 
            "tag_for_child_directed_treatment": 1
        }
    }, 
    "ios": {
        "AdMob": {
            "test": false, 
            "safearea": true, 
            "ads": {
                "interstitial": {
                    "type": "interstitial", 
                    "id": "ca-app-pub-3940256099942544/4411468910"
                }, 
                "rewarded": {
                    "type": "rewarded_video", 
                    "id": "ca-app-pub-1329374026572143/7991602916"
                },
                "banner": {
                    "width": 300, 
                    "type": "banner", 
                    "id": "ca-app-pub-3940256099942544/2934735716", 
                    "alignment": "bottom", 
                    "height": 50
                }
            }
        }
    }
}
```

Android、iOSともにbanner,interstitial,rewardedの3つの広告を作成する。
appidと広告idは、はじめから書かれている値をそのまま使うと、テスト広告が表示される。
リリース時にはAdMobの管理画面で広告を作って、IDを置き換え、"test"の値をfalseにする。

#Info.plistの編集

proj.ios_mac/iosフォルダにあるInfo.plistを開いて編集する。
AdMobのアプリIDと、NSAppTransportSecurityの項目を追加する。
AdMobのアプリIDはAdMobの管理画面で予め作成しておく。
以下の内容を\<dict>~\</dict>の中に追加する。

```plist
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-xxxxxxxxxx~xxxxxxxxxx</string>
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSAllowsArbitraryLoadsForMedia</key>
    <true/>
    <key>NSAllowsArbitraryLoadsInWebContent</key>
    <true/>
</dict>
```

# AdManagerクラスの作成

Classesグループ内にAdManager.h,AdManager.cppという名前のファイルを作成する。
ファイルはプロジェクト内のClassesフォルダ内に作る。

![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37640/9466de75-70eb-a4ad-4fac-7e013ccaf65c.png)

内容は以下の通り。

```C++:AdManager.h
#ifndef CocosTestAdManager_hpp
#define CocosTestAdManager_hpp

#include "cocos2d.h"
#include "PluginAdMob/PluginAdMob.h"
#include "PluginSDKBoxAds/PluginSDKBoxAds.h"

class AdManager : public cocos2d::Ref, public sdkbox::AdMobListener
{
private:
	bool _loadBanner;
	bool _didShowInterstitial;
	std::chrono::system_clock::time_point _showInterstitialTime;
	std::function<void (const std::string &name, const std::string &currency, double amount)> _rewardCallback;
	
	virtual void adViewDidReceiveAd(const std::string &name) override;
	virtual void adViewDidFailToReceiveAdWithError(const std::string &name, const std::string &msg) override;
	virtual void adViewWillPresentScreen(const std::string &name) override;
	virtual void adViewDidDismissScreen(const std::string &name) override;
	virtual void adViewWillDismissScreen(const std::string &name) override;
	virtual void adViewWillLeaveApplication(const std::string &name) override;
	virtual void reward(const std::string &name, const std::string &currency, double amount) override;
public:
    AdManager();
    virtual ~AdManager();
	static AdManager* getInstance();
	static void destroyInstance();
	bool init();
	
	void setRewardCallback(const std::function<void (const std::string &name, const std::string &currency, double amount)>& callback)
	{
		_rewardCallback = callback;
	}
	
	void showBanner();
	void showInterstitial();
	void showRewarded();
};

#endif /* defined(CocosTestAdManager_hpp) */
```

```C++:AdManager.cpp
#include "AdManager.h"

using namespace std;
USING_NS_CC;

namespace {
AdManager* _sharedInstance = nullptr;

const char* kBanner = "banner";
const char* kInterstitial = "interstitial";
const char* kRewarded = "rewarded";

const float showInterstitialTimeThreshold = 60.0f;
}

AdManager::AdManager()
:_loadBanner(false),
_didShowInterstitial(false),
//_showInterstitialTime(std::chrono::system_clock::time_point::min()),
_rewardCallback(nullptr)
{
    
}

AdManager::~AdManager()
{
    
}

AdManager* AdManager::getInstance()
{
	if (! _sharedInstance) {
		_sharedInstance = new (nothrow) AdManager();
		_sharedInstance->init();
	}
	return _sharedInstance;
}
void AdManager::destroyInstance()
{
	CC_SAFE_DELETE(_sharedInstance);
}

bool AdManager::init()
{
	CCLOG(__PRETTY_FUNCTION__);
	sdkbox::PluginAdMob::init();
	sdkbox::PluginSdkboxAds::init();
	
	sdkbox::PluginAdMob::setListener(this);
	sdkbox::PluginAdMob::cache(kBanner);
	sdkbox::PluginAdMob::cache(kInterstitial);
	sdkbox::PluginAdMob::cache(kRewarded);
	return true;
}

void AdManager::showBanner()
{
	if (sdkbox::PluginAdMob::isAvailable(kBanner))
	{
		CCLOG("show banner");
		sdkbox::PluginAdMob::show(kBanner);
	}
	else
	{
		CCLOG("banner is not available");
	}
}

void AdManager::showInterstitial()
{
	if (sdkbox::PluginAdMob::isAvailable(kInterstitial))
	{
		std::chrono::duration<float> duration = std::chrono::system_clock::now() - _showInterstitialTime;
		//CCLOG("%s duration = %f", __PRETTY_FUNCTION__, duration.count());
		if (!_didShowInterstitial || duration.count() > showInterstitialTimeThreshold)
		{
			CCLOG("show interstitial");
			_didShowInterstitial = true;
			_showInterstitialTime = std::chrono::system_clock::now();
			sdkbox::PluginAdMob::show(kInterstitial);
		}
	}
	else
	{
		CCLOG("interstitial is not available");
	}
}
void AdManager::showRewarded()
{
	if (sdkbox::PluginAdMob::isAvailable(kRewarded))
	{
		CCLOG("show rewarded ad");
		sdkbox::PluginAdMob::show(kRewarded);
	}
	else
	{
		CCLOG("Rewarded ad is not available");
	}
}

void AdManager::adViewDidReceiveAd(const std::string &name)
{
	CCLOG("%s name = %s",__PRETTY_FUNCTION__,name.c_str());
	if (name == kBanner)
	{
		if (!_loadBanner)
		{
			_loadBanner = true;
			showBanner();
		}
	}
}
void AdManager::adViewDidFailToReceiveAdWithError(const std::string &name, const std::string &msg)
{
	CCLOG("%s name = %s msg = %s",__PRETTY_FUNCTION__,name.c_str(),msg.c_str());
}
void AdManager::adViewWillPresentScreen(const std::string &name)
{
	CCLOG("%s name = %s", __PRETTY_FUNCTION__, name.c_str());
}
void AdManager::adViewDidDismissScreen(const std::string &name)
{
	CCLOG("%s name = %s", __PRETTY_FUNCTION__, name.c_str());
}
void AdManager::adViewWillDismissScreen(const std::string &name)
{
	CCLOG("%s name = %s", __PRETTY_FUNCTION__, name.c_str());
}
void AdManager::adViewWillLeaveApplication(const std::string &name)
{
	CCLOG("%s name = %s", __PRETTY_FUNCTION__, name.c_str());
}
void AdManager::reward(const std::string &name, const std::string &currency, double amount)
{
	CCLOG("%s name = %s", __PRETTY_FUNCTION__, name.c_str());
	if (_rewardCallback)
	{
		_rewardCallback(name, currency, amount);
	}
}
```

AdManagerはシングルトンクラスで、広告の表示はすべてこれを通しておこなう。
広告はすべて

```c++
sdkbox::PluginAdMob::cache()
```
で読み込んでから

```c++
sdkbox::PluginAdMob::show()
```
で表示する。
広告の表示は必要な時に以下のメンバ関数を呼んで表示する。

```c++
void showBanner();
void showInterstitial();
void showRewarded();
```

またsetRewardCallback関数でコールバックを渡すと、リワード動画を見終わった時にその内容が実行される。

バナーは、今回は広告を読み込み次第、はじめから自動で表示するようにした。

```c++
void AdManager::adViewDidReceiveAd(const std::string &name)
{
    CCLOG("%s name = %s",__PRETTY_FUNCTION__,name.c_str());
    if (name == kBanner)
    {
        if (!_loadBanner)
        {
            _loadBanner = true;
            showBanner();
        }
    }
}
```

CMakeLists.txtにAdManagerを追加しておく。

```CMakeLists.txt
# add cross-platforms source files and header files 
list(APPEND GAME_SOURCE
     Classes/AppDelegate.cpp
     Classes/HelloWorldScene.cpp
     Classes/AdManager.cpp
     )
list(APPEND GAME_HEADER
     Classes/AppDelegate.h
     Classes/HelloWorldScene.h
     Classes/AdManager.h
     )
```

# AppDelegate.cppの編集

```c++:AppDelegate.cpp
#include "AppDelegate.h"
#include "HelloWorldScene.h"
#include "AdManager.h"

/*
#ifdef SDKBOX_ENABLED
#include "PluginAdMob/PluginAdMob.h"
#endif
#ifdef SDKBOX_ENABLED
#include "PluginSdkboxAds/PluginSdkboxAds.h"
#endif
*/

/** 中略 **/

bool AppDelegate::applicationDidFinishLaunching() {
    /*
//#ifdef SDKBOX_ENABLED
    sdkbox::PluginAdMob::init();
//#endif
//#ifdef SDKBOX_ENABLED
    sdkbox::PluginSdkboxAds::init();
//#endif
    */
    
/** 中略 **/

    register_all_packages();
	
    AdManager::getInstance();

    // create a scene. it's an autorelease object
    auto scene = HelloWorld::createScene();

    // run
    director->runWithScene(scene);

    return true;
}

/** 後略 **/

```

はじめに広告を読み込む必要があるのでHelloWorldSceneを作る前にAdManager::getInstance()を一度呼んでいる。

# HelloWorldScene.cppの編集

```c++:HelloWorldScene.cpp

#include "HelloWorldScene.h"
#include "AdManager.h"

USING_NS_CC;

/** 中略 **/

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Scene::init() )
    {
        return false;
    }

    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

	std::string fontPath = "fonts/Marker Felt.ttf";
	float fontSize = 24;
	auto button1 = MenuItemLabel::create(Label::createWithTTF("Interstitial",
															  fontPath,
															  fontSize),
										 [](Ref* sender){
		AdManager::getInstance()->showInterstitial();
	});
	auto button2 = MenuItemLabel::create(Label::createWithTTF("Rewarded",
															  fontPath,
															  fontSize),
										 [](Ref* sender){
		AdManager::getInstance()->showRewarded();
	});
	
	AdManager::getInstance()->setRewardCallback([](const std::string &name, const std::string &currency, double amount){
		CCLOG("get reward name = %s currency = %s amount = %f",name.c_str(),currency.c_str(),amount);
	});
	
	Vec2 center = origin + visibleSize/2;
	button1->setPosition(center + Vec2(0, 50));
	button2->setPosition(center + Vec2(0, -50));
	auto menu = Menu::create(button1, button2, NULL);
	//menu->alignItemsVerticallyWithPadding(5);
	menu->setPosition(Vec2::ZERO);
	this->addChild(menu, 1);

    // add "HelloWorld" splash screen"
    auto sprite = Sprite::create("HelloWorld.png");
    if (sprite == nullptr)
    {
        problemLoading("'HelloWorld.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite, 0);
    }
    return true;
}

/** 後略 **/

```

インタースティシャル広告とリワード広告を表示するボタンを作成した。

# iPhoneで実行

Xcodeの左上でプロジェクト名と同じ名前のターゲットを選んで実行する。

![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37640/b73de402-f13b-35be-6fe7-7e53e58ef830.png)

うまく行けばiPhoneでテスト広告が表示される。

![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37640/71809164-b3ae-ef83-0d56-a7dc755228a2.png)

#Android

Android Studioでプロジェクトを開く。
以下のようなエラーが表示される。

```
This project uses AndroidX dependencies, but the 'android.useAndroidX' property is not enabled. Set this property to true in the gradle.properties file and retry.
```

gradle.propertiesに以下を追記してSyncする。

```
android.useAndroidX=true
android.enableJetifier=true
```

バーチャルデバイスか実機で実行するとうまく行けば広告が表示される。

# 参考
https://stackoverflow.com/questions/59269208/errorrootcode-for-hash-md5-was-not-found-when-using-any-hg-mercurial-command
https://stackoverflow.com/questions/14630828/could-not-inspect-application-package-xcode
https://www.simpleswiftguide.com/how-to-add-xcframework-to-xcode-project/
https://discuss.cocos2d-x.org/t/unable-to-build-xcode-project-after-importing-sdkbox/51258