ゲームにおけるFirebase 活用例

[PONOS Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ponos)の24日目の記事です。
🎄メリークリスマス！！クリスマス・イブにFirebaseの記事をお届けします！！！🎄
🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁🎁

## はじめに:eyeglasses:
現在運用中のゲームで[Firebase](https://firebase.google.com/?hl=ja)を導入しました。
この記事では活用事例とノウハウを紹介していきます。
Firebase利用の一例になれば幸いです。

**組み込み方法は別記事をご参照ください**
**公式ドキュメント**
[iOS](https://firebase.google.com/docs/ios/setup?hl=ja)
[Android](https://firebase.google.com/docs/android/setup?hl=ja)
[Unity](https://firebase.google.com/docs/unity/setup?hl=ja)
[C++](https://firebase.google.com/docs/cpp/setup?hl=ja&platform=ios)
[Web](https://firebase.google.com/docs/web/setup?hl=ja)

##この記事の対象者:eyeglasses:
・Firebase初心者、中級者
・Firebaseを検討中の方
・Firebaseの実装イメージを掴みたい方
・サーバーレスを検討中の方
・Googleのサービスが好きな方、興味のある方

##Firebaseの各機能をざっくり紹介##
機能がたくさんあります


| 名称       |       機能 |活用|
|:-----------------|:------------------|:------------------|
| Firebase Authentication|              認証機能 |◎|
| Firebase Realtime Database|NoSQL クラウド データベースでデータの保管と同期を行うことができる |◯|
| Cloud Firestore  |Realtime Database からさらにパワーアップした NoSQL データベース。 |△|
| Cloud Storage for Firebase   |写真や動画などのコンテンツを保管、取得することができる。 |◎|
| Firebase Hosting          |webサイトを構築 |◎|
| Cloud Functions for Firebase |関数ごとにサーバー処理を簡単に用意できる |◯|
| ML Kit for Firebase          |機械学習 |×|
| Firebase Crashlytics  |クラッシュ レポート管理 |◯|
| Firebase Performance Monitoring  |パフォーマンスの問題を診断 |◯|
| Firebase Test Lab  |様々なデバイスでアプリの自動テストおよびカスタマイズされたテストを実行 |◎|
| Firebase Cloud Messaging  |サーバから通知機能 |◎|
| Firebase In-App Messaging  |ターゲットを絞り込んでアプリ内で通知 |×|
| Firebase Predictions  |機械学習を用いた分析ツール |◎|
| Firebase Remote Config|Firebase consoleから、外観の変更、機能の段階的な展開などをカスタマイズ |×|
| Google Analytics for Firebase |総合的な分析ツール |◎|
| Firebase A/B Testing  |ユーザーごとに出し分けしてA/Bテストを行う |◎|
| Firebase Dynamic Links  |複数のプラットフォームで機能するURLを生成 |×|
| Firebase App Indexing  |アプリを Google 検索結果に表示することができる |×|


###活用１：ログイン時のユーザー認証###
Firebase Authenticationをゲーム内での認証機能として使っています。
ログイン時に認証して、Firebase各種機能と連携して認証済みのアカウントしかセキュリティー的に許可しないようにしています。
以下の４種類の認証を使っています。
・Google
・Facebook
・メールアドレス
・匿名認証

匿名認証で作成したデータと各種連携したデータを紐づける処理はこちらで実装する必要がありますが、
公式ドキュメントにもサンプルコードが用意されているので低コストで実装できます。

匿名認証のコード

```C++:Auth.cpp
#include "firebase/app.h"
#include "firebase/auth.h"

void Auth::signInAnonymously(){
    firebase::App* app = App::GetInstance();
    firebase::auth::Auth* auth = Auth::GetAuth(app);
        
    auth->SignInAnonymously().OnCompletion([this](const firebase::Future<firebase::auth::User*>& result){
        if (result.status() == firebase::kFutureStatusComplete &&
	     result.error() == firebase::auth::kAuthErrorNone &&
	     result.result()) {
            //成功処理
        }else{
           //失敗処理
        }
    });
}


```

###活用２：通信時の不正対策###
通信時の不正対策として
Firebase Authentication
と
Cloud Functions
を組み合わせてサーバーサイドでも認証チェックしております。

Cloud Functionsを使用するとクライアント側の通信処理もセキュアでシンプルな作りになります。
詳細はCloud Functionsのドキュメントを一読するのもありです。

**クライアント側の通信処理**
Cloud Functionsで定義したsampleにリクエストを投げます。


```C++:Request.cpp
void Request::callSample(){
    firebase::Variant data = firebase::Variant::EmptyMap();

	firebase::functions::HttpsCallableReference doSomething = functions->GetHttpsCallable("sample");
	doSomething.Call(data).OnCompletion([this]
	(firebase::Future<firebase::functions::HttpsCallableResult> result){
	    if(result.status() == firebase::kFutureStatusComplete &&
		result.error() == firebase::functions::kErrorNone && result.result()){
		//成功処理
		}else{
		//失敗処理
		}
	});
}


```

**サーバー側の認証チェック**
exports.sampleの部分がレスポンスを返します。


```TypeScript:Server.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
 
exports.sample = functions.https.onCall((data, context) => {
    if(!checkAuth (data, context) ){
        throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +'while authenticated.');
    }
    //以降でサーバー処理を記入していく 
    return {
            text:"success",
    };
});


//認証チェック
async function checkAuth (data, context) {
    if( !(context.auth && context.auth.uid && context.auth.token)){
        console.log('checkAuth error');
        return false;
    }
    const user = await admin.auth().getUser(context.auth.uid);
    if (user.customClaims && user.customClaims.admin !== true) {
        console.log('checkAuth error');
        return false;
    }
    return true;
}
```


###活用３：サーバーからのPush通知###
Cloud Messagingを活用して特定条件を満たしたユーザーにPush通知を送るようにしています。
このサンプルソースではRealTimeDBにユーザーのpushTokenが保存されており、pushTokenを取り出して
使用しているパターンです。


```TypeScript:Server.js
exports.pushNotification = functions.https.onCall((data, context) => {
    if(!checkAuth (data, context) ){
        throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
        'while authenticated.');
    }
	

    const userId = data.userId;
    var pass = "user_table/" + userId + "/" + "pushToken";
    return admin.database().ref(pass).once('value').then(function(snapshot){
        var pushToken = snapshot.val();
        const payload = {
            notification: {
              title: 'プッシュ通知タイトル',
              body: `プッシュ通知本文`
            }
          };
          return admin.messaging().sendToDevice(pushToken, payload);
    }).catch(error => {
          console.error(error);
          throw new functions.https.HttpsError(error);
    });
});

```

シンプルに書くとこれだけで通知を送れます。

```
          return admin.messaging().sendToDevice(pushToken, payload);
```


###活用４：バトルログの保存（アップロード）###
バトル時のログや詳細情報はCloud Storageに保存しています。
DBに保存すると量と値段の面で、デメリットがあったので分析の必要がなく、他のユーザーから参照されないデータはCloud StorageにGZip化して保存しています。

```C++:CloudStorage.cpp
#include "firebase/app.h"
#include "firebase/storage.h"

void CloudStorage::storageUpLoad(){
	firebase::App* app = App::GetInstance();
	firebase::storage::Storage* storage = Storage::GetInstance(app);

	//アップロード先のパス
	std::string serverPath = "/";
	//アップロードするファイル保存先
	std::string localPath = "file://" + "ローカルパス";

	storage->GetReference().Child(serverPath).PutFile(localPath.c_str()).OnCompletion([](const firebase::FutureBase& result) {
	            if (result.error() == firebase::storage::kErrorNone){
	              //成功処理
	            }
	            else {
	              //失敗処理
	            }
	});

}


```


###活用５：リソースダウンロード###
これもCloud Storageを活用しています。
リソース数、容量などによって状況は変わると思いますが、問題ない速度でダウンロードできています。
何よりもCloud Storageはコストパフォーマンスがいいです。
現在、ほぼ無料枠で済んでいます。


```C++:CloudStorage.cpp
#include "firebase/app.h"
#include "firebase/storage.h"

void CloudStorage::storageDownload(){
    firebase::App* app = App::GetInstance();
    firebase::storage::Storage* storage = Storage::GetInstance(app);

    //対象リソースのパス
    std::string serverPath = "/";
    //ダウンロード後の保存先
    std::string localPath = "file://" + "ローカルパス";
    storage->GetReference().Child(serverPath).GetFile(localPath.c_str()).OnCompletion([](const firebase::FutureBase& result) {
        if (result.error() == firebase::storage::kErrorNone) {
         //成功処理
        }
        else {
         //失敗処理
        }
    });
}


```


###活用６：ゲーム内お知らせ###
お知らせ機能はFirebase Hostingを活用しています。
環境構築などの導入は非常に簡単です。
5分くらいでできると思います。
[環境構築](https://firebase.google.com/docs/hosting/quickstart?hl=ja)

環境構築が終わったら、HTML,CSS,画像などのWEB素材を用意して

```
firebase deploy
```

で、デプロイして完了です。

固定のお知らせやヘルプページを見せるだけでしたらFirebase Hostingだけで十分なのですが
ユーザーごとに見せるページを変えたい、期間で出し分けをしたい
などの仕様によってはサーバー側でHTMLを生成する必要があるので
うちのゲームではCloud Function、RealTimeDatabaseなども絡めて使っている箇所があります。

注意点としては
過去にデプロイしたWEBページがCDNに溜まっていき、容量が無駄になるので
Firebaseコンソール上からときどき消去しないといけません。
WEBページに問題があったときに、とっさにロールバックできるのでそれとトレードオフという感じですね



###活用７：KPI 分析###
ゲーム運営にKPI 分析は必須ですよね
Google Analytics for Firebaseを使っています。
for Firebaseと書いていますが、バックエンドの仕組みはGoogle AnalyticsでKPI分析するうえでは違いはありません。違いは組み込み方法が若干違うくらいです。

![スクリーンショット 2019-12-19 18.55.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249951/1b3e7729-1502-4e1e-901b-dd56db009b63.png)


こちらのFirebase デモプロジェクトを触ってみるのが理解が早いです。

[1.デモプロジェクトログイン](https://support.google.com/firebase/answer/7157552?hl=ja)
  デモプロジェクトが見れるようになったらAnalyticsページへ
[2.デモプロジェクト　Analyticsページ](https://console.firebase.google.com/u/0/project/fir-demo-project/analytics/app/android:com.labpixies.flood/overview~2F%3Ft=1576644889842&fpn=300830567303&swu=1&sgu=1&sus=upgraded&cs=app.m.dashboard.overview&g=1)
※リンクから飛べない場合は、左側のメニューにてアナリティクス配下のDashboardをクリック


Analyticsは組み込むだけでセッション数、DAU、課金額、ARPPUなど
デフォルトでいくつかの値を集計してくれます。
ゲーム内独自の集計をしたい場合は、イベントを実装すればできます。

**イベント実装例**

```C++:FirebaseAnalytics.cpp
#include "firebase/app.h"
#include "firebase/analytics.h"

void FirebaseAnalytics::init(){
    firebase::App* app = App::GetInstance();
    firebase::analytics::Initialize(*app);
}

//レベルアップ時、イベント登録
void FirebaseAnalytics::levelUp(){
    firebase::analytics::LogEvent(firebase::analytics::kEventLevelUp);
}

```


###活用８：予測データの運営活用###
Firebase PredictionsではGoogleの機械学習の予測結果を簡単に知ることができます。

![スクリーンショット 2019-12-19 19.03.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249951/5fb16cad-f3e9-a9a8-e44a-59f312868be5.png)

[デモプロジェクト](https://console.firebase.google.com/u/0/project/fir-demo-project/growth/app/android:com.labpixies.flood)

以下項目の一週間後の予測結果を知ることができます。
**churn**：**離脱ユーザーの数**

**not_churn**：**継続ユーザーの数**

**spend**：**課金するユーザーの数**

**not_spend**：**課金しないユーザーの数**


Firebase Predictionsの優れている点は
これらの予測結果に対して
・A/Bテストを行なって動向を分析できる
・Remote ConfigによってUIやパラメーターなどを瞬時に変えることができる
・Cloud Messagingを使ってお得な情報などを送ることができる
ことにあると思います。

また、Google Analyticsと連携してコンバージョンに設定されたイベントを予測項目として追加することもできます。
ゲームごとに有効なイベントを設定すれば有効活用できると思います。


###活用９：バナー広告、動画広告###
Google AdMobを使って、バナー広告、動画広告に対応することができます。
Firebaseの機能としてあまり注目されてないですが、実はFirebase SDKではAdMobの機能もサポートされてます。

**バナー広告表示**

```C++:FirebaseAdmob.cpp
#include "firebase/app.h"
#include "firebase/admob.h"
#include "firebase/admob/banner_view.h"


void FirebaseAdmob::createBanner(){
    //firebase::admob::BannerView* banner_view
    banner_view = new firebase::admob::BannerView();
    
    firebase::admob::AdSize ad_size;
    ad_size.ad_size_type = firebase::admob::kAdSizeStandard;
    ad_size.width = 320;
    ad_size.height = 50;
    // my_ad_parent is a reference to an iOS UIView or an Android Activity.
    // This is the parent UIView or Activity of the banner view.
    // kBannerAdUnitはAdMobのサイトで発行されるId
    banner_view->Initialize(getAdParent(), kBannerAdUnit, ad_size);
}

void FirebaseAdmob::showBanner(){
    banner_view->MoveTo(banner_view->kPositionBottomLeft);
    banner_view->Show();
    firebase::admob::AdRequest my_ad_request = {};
    banner_view->LoadAd(my_ad_request);
}

```


###活用１０：多端末テスト###
スマホゲーム開発において多端末検証って大変ですよね
特にAndroidとかAndroidとかAndroidとか・・・
そこで活躍するのが
**Firebase Test Lab**
Google データセンターでホストされているデバイス上でアプリをテストします。
1 回のオペレーションで、さまざまなデバイス、
さまざまなデバイス構成で Android アプリや iOS アプリをテストし、
Firebase consoleで結果（ログ、動画、スクリーンショットなど）
を確認できます。


・Android端末が足りない場合
・テスターが足りない場合
・QA開始前のシステム的なテストをしたい場合
など使いどころはたくさんあります。

![スクリーンショット 2019-12-19 19.04.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249951/1060c7cc-5215-d739-ca66-725fe140fa48.png)

[1.デモプロジェクトログイン](https://support.google.com/firebase/answer/7157552?hl=ja)
  デモプロジェクトが見れるようになったらTest Labページへ
[2.デモプロジェクト　Test Labページ](https://console.firebase.google.com/u/0/project/fir-demo-project/testlab/histories/bh.1c4c603ad7ecb427/matrices/6797954313847734613)
※リンクから飛べない場合は、左側のメニューにて品質、配下のTest Labをクリック

###おわりに###
Firebaseを導入してみての感想としては・・・

非常に簡単、便利で素晴らしいです！
ゲーム仕様や料金面での検討をする必要はありますが
まずは無料プランで使ってみてから他のサービスと比較検討する方法がオススメです！

料金面にて実装で創意工夫した箇所もありますが、結果として今は**超・低コスト**で運営できています。


以下の機能は無料で使い続けることができるので、組み込んでおいて損はないです
**・Google Analytics for Firebase**
**・Firebase Authentication**
**・Firebase Crashlytics**
**・Firebase Performance Monitoring**
**・Firebase Cloud Messaging**
**・Firebase Predictions**
**・Firebase Dynamic Links**
**・Firebase App Indexing**
**・Firebase A/B Testing**


本プロジェクトでは[GCP](https://cloud.google.com/docs/overview/?hl=ja)も併用して活用しています。
次回は[GCP](https://cloud.google.com/docs/overview/?hl=ja)のゲーム活用事例を書きたい思います。


明日は@honeniqさんの記事です
最終日の記事も楽しみですね！