Cocos2d-x v3.17にFirebase SDK C++ を入れて匿名認証を実装する(2019年度版cmake対応) 

# 概要
誰がなんといおうと、ぼくはまだcocos2d-xを使い続ける所存です。
(Unityも勉強してはいるんだけどね)
そんなわけで、FirebaseのC++ SDKを最新のものに入れた際の設定をまとめました。

## ゴール
* Firebase匿名認証を使ってiOS/Androidで認証する
* Androidはcmakeでビルドする

## 前提環境
* Mac OS X (10.14)
* cocos2d-x (v3.17.1)
* Xcode10.3
* Android Studio 3.5
* Gradle 5.4.1
* Android NDK r13b

# Firebaseのプロジェクト作成＆ネイティブSDK導入
ここらへんは、公式に記載された通りにやっていきます。

## アプリの追加
Project Overviewから「プロジェクトの設定」を選択します。
そして、設定画面からアプリの追加を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/ea0a3fd1-85be-9671-be6f-79bfc3e49619.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/da635a3e-44ac-4b35-c19c-ec2873af4a26.png)

あとは、以下のような画面になるんで手順どおりに進めていってアプリを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/13f1c9b2-d92c-4a26-3187-bfdc968ac366.png)

## 匿名認証の設定
Firebaseにて、匿名認証の設定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/0e920ec8-e650-2cca-c661-ff1e1657ade1.png)


# 環境構築
みんな大好きC++の環境構築やっていきましょうね(にっこり)

## iOS版
### 最初は書いてある通りに行う
C++ プロジェクトに Firebase を追加する
https://firebase.google.com/docs/cpp/setup?hl=ja

上記の「ステップ 5」の1「Firebase C++ SDK をダウンロードし、適切な場所で解凍します。」まではそのまま行います。
また、 Firestore C++ SDK のディレクトリは `firebase_cpp_sdk` みたいな名前にし、 `proj.ios-mac` や `proj.android` と同じ階層の場所に配置します。(という前提でここから進めますが、別の場所に配置する場合はパスの指定は適宜置き換えて下さい）

### Podfileの作成
このあたりから日本語版ではちょっと嘘が書かれはじめます（更新されてないだけですが）。
2019/9/2現在、Firebase SDK は `6.3.0` まで上がってますので、記載のやり方だと古いものが入っちゃいます。
また、そのバージョンだと `iOS8` 以上が必須になります。

```bash
$ cp your-app-directory
$ pod init
```

```bash:Podfile
# Uncomment the next line to define a global platform for your project
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'

target 'MyTest-desktop' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!

  # Pods for MyTest-desktop

end

target 'MyTest-mobile' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!

  # Pods for MyTest-mobile
  pod 'Firebase' # これは入れといた方がよさげ
  pod 'Firebase/Analytics' # これも入れといた方がよさげ
  pod 'Firebase/Core' #　これもたぶん必須。その他は使いたいものを入れる
  pod 'Firebase/Auth' 

end
```

その後、インストールします。

```bash
$ pod update
```

すると、cocos2d-xの場合 https://firebase.google.com/docs/admob/cpp/cocos2d-x?hl=ja に記載されてるようなエラーがでます。

> CocoaPods が、いくつかのプロパティで $(inherited) が欠落しているという警告を発します。Xcode で MyGame-mobile ターゲットのビルド設定に移動し、次の各ビルド設定のリストの最後に $(inherited) を追加します。  
> 
> * Other Linker Flags
> * Preprocessor Macros
> * Library Search Paths

というわけで、それらの設定をします。ただ、なぜかMyGame-desktop側でも警告でちゃうんでそっちも合わせて同じ設定をします。

(例：Processor Macros)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/3c97df9b-50ac-529d-2563-d7435cdf3a74.png)

### BuildSetting
#### Framework Search Pathsへの追加
[Framework Search Paths]に `$(PROJECT_DIR)/../firebase_cpp_sdk/frameworks/ios/universal` を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/83c28093-2793-1899-c217-f6caffb6d4ff.png)

その上にある、 `"${PODS_ROOT}/FirebaseAnalytics/Frameworks"` と `"${PODS_ROOT}/GoogleAppMeasurement/Frameworks"` もいつのまにか入ってきてました。
必要っぽいのでとりあえず残しておきます。

#### Header Search Pathsへの追加
[Header Search Paths] に `$(PROJECT_DIR)/../firebase_cpp_sdk/include` を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/71667ed8-a30b-c90a-efc8-bc70de5bf8fe.png)

#### 必要なライブラリを追加
`firebase_cpp_sdk/frameworks/ios/universal` の中にあるそれぞれ必要なライブラリをプロジェクトに追加します。
ライブラリは、使うものによって異なります。

詳細は https://firebase.google.com/docs/cpp/setup (英語版) に記載されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/b86539b1-1379-37c1-7069-56b0f5cbd36e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/68a020d2-b4c6-30f4-e4bf-ba2d8f92ec88.png)

#### Linked Frameworks and Librariesにフレームワークを追加
Linked Frameworks and Libraries に以下を入れます。

* MediaPlayer.framework
* GameController.framework

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/cd399815-3080-f4ed-6320-2aaffdfd4eb7.png)

このあたりは、使うフレームワークによっても変わってきそう。

## Android版
現在、Android NDKのbuildはcmakeが推奨されており、Firebase C++ SDKも公式ドキュメントではcmakeの利用が推奨されてます。ということで、それで設定します。

公式(C++)
https://firebase.google.com/docs/cpp/setup?platform=android#nav-buttons-1

### google-services.jsonの配置
まずはFirebaseプロジェクトを作成し、google-services.jsonを以下の場所に配置します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/ab89cfdd-89c8-eea4-ae04-089ddb3f36a1.png)

### project/build.gradleの設定
続いて、プロジェクト側(proj.android直下)のbuild.gradleに以下を追加します。

```gradle:project/build.gradle

buildscript {
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath 'com.google.gms:google-services:4.3.1' // ここを追加
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
```
### gradle.propetiesの設定
次はgradle.propertiesです。ついでにここで64bit対応もしちゃいます。

```properties
// 省略
PROP_APP_ABI=armeabi-v7a:arm64-v8a // 変更
// 以下を追加
PROP_BUILD_TYPE=cmake
systemProp.firebase_cpp_sdk.dir=【フルパス】/firebase_cpp_sdk
```
`systemProp.firebase_cpp_sdk.dir` には、最初に配置した `firebase_cpp_sdk` へのフルパスを指定します。

### settings.gradleの設定
続いて、以下を追加します。これによりFirebaseのC++側がビルドされます(と思うよ)

```gradle:settings.gradle
def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir')
gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir"
includeBuild "$firebase_cpp_sdk_dir"
```

### app/build.gradleの設定

次に、アプリの方のbuild.gradle(app直下)にも以下を追加します。
こちらはいくつか追加する箇所があります。

```gradle:app/build.gradle

android {
    // 省略
    defaultConfig {
        // 省略
        externalNativeBuild {
            if (PROP_BUILD_TYPE == 'ndk-build') {
                // 省略
            }
            else if (PROP_BUILD_TYPE == 'cmake') {
                cmake {
                    targets 'MyGame'
                    // "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" を追加
                    arguments "-DCMAKE_FIND_ROOT_PATH=", "-DANDROID_STL=c++_static", "-DANDROID_TOOLCHAIN=clang", "-DANDROID_ARM_NEON=TRUE", "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir"
                    cppFlags "-frtti -fexceptions -fsigned-char"
                }
            }
        }

        ndk {
            abiFilters = []
            abiFilters.addAll(PROP_APP_ABI.split(':').collect{it as String})
        }
    }
    // 省略
}

// 省略

dependencies {
    // 入れたいSDKを追加する
    implementation 'com.google.firebase:firebase-analytics:17.2.0'
    implementation 'com.google.firebase:firebase-auth:19.0.0'
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation project(':libcocos2dx')
}
// 以下を追加
apply plugin: 'com.google.gms.google-services'  // Google Play services Gradle plugin
apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle"

// 入れたいC++のライブラリを指定する
firebaseCpp.dependencies {
    analytics
    auth
}
```

### CMakeLists.txt の修正
最後に、CMakeLists.txtを修正します。

```cmake
# 中間あたり
target_include_directories(${APP_NAME}
        PRIVATE Classes
        PRIVATE ${COCOS2DX_ROOT_PATH}/cocos/audio/include/
        PRIVATE firebase_cpp_sdk/include/ # 追加(ヘッダーをインクルード)
)
# 省略
# 最後に追加
if(ANDROID)
    # Add Firebase libraries to the target using the function from the SDK.
    add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL)

    # The Firebase C++ library `firebase_app` is required,
    # and it must always be listed last.

    # Add the Firebase SDKs for the products you want to use in your app
    # For example, to use Analytics, Firebase Authentication, and Firebase Realtime Database
    set(firebase_libs firebase_analytics firebase_auth firebase_app)
    target_link_libraries(${APP_NAME} "${firebase_libs}")
endif()
```
上記では、 `set(firebase_libs firebase_analytics firebase_auth firebase_app)` を指定しています。注意書きにあるように、 `firebase_app` は必須で、かつ最後に指定しないといけないそうです。

# コーディング
今回は匿名認証なので、以下の通り実装します。

## iOS側
どこにも必要とは書いてないんですが、これ入れないとnullが返ってくる…。

```objc:AppDelegate.mm

#import "AppController.h"
#import "cocos2d.h"
#import "AppDelegate.h"
#import "RootViewController.h"
#import <Firebase/Firebase.h> // 追加

// (省略)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [FIRApp configure]; // 追加

    // 以下省略
}
```
上記をいれないと、ログで `The default Firebase app has not yet been configured. Add [FIRApp configure]; (FirebaseApp.configure() in Swift) to your application initialization. Read more: https://goo.gl/ctyzm8.` と怒られるので必要なのでしょう。

## Android側
Android側は特に何もJavaに追加はしてません。

## C++側
AppDelegateに以下のように記載します。

```c++:AppDelegate.cpp
#include "AppDelegate.h"
#include "HelloWorldScene.h"
#include "firebase/app.h"
#include "firebase/auth.h"
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
#include "platform/android/jni/JniHelper.h"
#endif

// (省略)
bool AppDelegate::applicationDidFinishLaunching() {

   // (省略)
#if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
    auto app = firebase::App::Create(firebase::AppOptions(), JniHelper::getEnv(), JniHelper::getActivity());
#else
    auto app = firebase::App::Create(firebase::AppOptions());
#endif
    auto auth = firebase::auth::Auth::GetAuth(app);
    auto result = auth->SignInAnonymously();
    result.OnCompletion([](const firebase::Future<firebase::auth::User*>& user) {
        auto u = user.result();
    }); 
}
```

## 最後実行すると

こんな感じで、Firebaseに匿名ユーザーが登録されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/67910df1-3a26-598a-04cf-57a31d694b3e.png)


# トラブルシューティング
ぼくが設定してる時に出くわしたエラーとその対処法です。

## 環境構築編
### iOS
#### "_OBJC_CLASS_$XXXXX", referenced from: とでる場合

iOSでの設定中に以下のエラーが出ました。

```bash
Undefined symbols for architecture x86_64:### 
  "_OBJC_CLASS_$_MPMoviePlayerController", referenced from:
      objc-class-ref in libcocos2d iOS.a(UIVideoPlayer-ios.o)
  "_MPMoviePlayerPlaybackDidFinishNotification", referenced from:
      -[UIVideoViewWrapperIos dealloc] in libcocos2d iOS.a(UIVideoPlayer-ios.o)
      -[UIVideoViewWrapperIos setURL::] in libcocos2d iOS.a(UIVideoPlayer-ios.o)
  "_MPMoviePlayerPlaybackStateDidChangeNotification", referenced from:
      -[UIVideoViewWrapperIos dealloc] in libcocos2d iOS.a(UIVideoPlayer-ios.o)
      -[UIVideoViewWrapperIos setURL::] in libcocos2d iOS.a(UIVideoPlayer-ios.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

この場合、ライブラリが足りていないというエラーらしく、Linked Frameworks and Librariesに **MediaPlayer.framework**を入れることで解決しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/6e60eaea-a39f-9eba-653d-057594ce3db1.png)

足りてないライブラリの一覧はこちらに詳しく乗ってます。

Xcodeでライブラリリンク時に発生するエラーに対応するframework
https://joyplot.com/documents/2016/09/18/xcode-link-error-framework/

## コーディング編
### firebase::AppがNULLが返却される
ターミナル上には以下の警告がでてる場合

```
[Firebase/Core][I-COR000003] The default Firebase app has not yet been configured. Add `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) to your application initialization. Read more: https://goo.gl/ctyzm8.
[Firebase/Analytics][I-ACS023007] Analytics v.60101000 started
[Firebase/Analytics][I-ACS023008] To enable debug logging set the following application argument: -FIRAnalyticsDebugEnabled (see http://goo.gl/RfcP7r)
[Firebase/InstanceID][I-IID001000] Firebase not set up correctly, nil or empty senderID.
ERROR: Unable to configure Firebase app (Could not configure Firebase InstanceID. GCM_SENDER_ID must not be nil or empty.)
```

iOS側のコーディングが足りてない場合に発生します。 AppDelegate.mm にも処理を追加する必要があります。

# 参考
Add Firebase to your C++ project
https://firebase.google.com/docs/cpp/setup

Cocos2d-x 3.17.1からビルドがcmakeに変更された件に対応する、ほかトラブル対応
https://takachan.hatenablog.com/entry/2019/02/03/014844

Xcodeでライブラリリンク時に発生するエラーに対応するframework
https://joyplot.com/documents/2016/09/18/xcode-link-error-framework/