サーバレスでUnity開発できるFirebaseを導入する - Analytics & 強制アップデート編

以前開発したときはParseをUnityのサーバサイドとして使ってましたがサービス終了してしまったので、Firebaseを導入します。FirebaseはAnalyticsや匿名のユーザ認証など多くの機能がついています。個人的にはクライアントのイベントログをFirebaseに送って、それをリアルタイムにBigQueryにシンクできるところが気に入ってます。

# 必須環境

[Adding Firebase to your Unity Project](https://firebase.google.com/docs/unity/setup)を参考にします。記事を書いている時点では以下でした。

* iOS
    * Unity 5.3以上
    * Xcode 7.0以上
    * CocoaPods 1.0.0 or later
* Android
    * Unity 5.3以上
    * Android 2.3 (Gingerbread)以上

# 導入環境

* Unity: 5.5.1f1
* Xcode: 8.2.1
* Firebase:
* 導入端末
    * iOS: 10.2.1

なお、CocoaPodsが必要なので以下でいれておきます

```
sudo gem install -n /usr/local/bin cocoapods
```

# 導入方法

## Firebaseプロジェクトの作成

[Adding Firebase to your Unity Project](https://firebase.google.com/docs/unity/setup)に手順がいろいろ書いてあるので、設定します。iOSとAndroidのアプリもFirebase Consoleで追加しておきます。

## Firebase Unity SDK

[Adding Firebase to your Unity Project](https://firebase.google.com/docs/unity/setup)のDownload the sdkをクリックしてSDKを入手します。

![image](https://qiita-image-store.s3.amazonaws.com/0/30079/ee8e8052-c319-3d93-6498-3db6d356c42d.png)

解凍すると以下のようなパッケージがあります。

| Feature                            | Unity Package                     |
|:----------------------------------:|:---------------------------------:|
| Firebase Analytics                 | FirebaseAnalytics.unitypackage    |
| Firebase Authentication            | FirebaseAuth.unitypackage         |
| Firebase Realtime Database         | FirebaseDatabase.unitypackage     |
| Firebase Invites and Dynamic Links | FirebaseInvites.unitypackage      |
| Firebase Messaging                 | FirebaseMessaging.unitypackage    |
| Firebase Realtime Database         | FirebaseDatabase.unitypackage     |
| Firebase Remote Config             | FirebaseRemoteConfig.unitypackage |
| Firebase Storage                   | FirebaseStorage.unitypackage      |

## [Remote Config](https://firebase.google.com/docs/remote-config/) ([Unity用ドキュメント](https://firebase.google.com/docs/remote-config/use-config-unity))

今回は強制アップデートの仕組みをRemote Configで実現しようと思います。
強制アップデートとは、クライアントのバージョンがFirebase側で指定しているバージョンより低かったらストアに飛ばすようにすることです。

最初に、Firebase Console側のRemote Configの`minimum_version`を以下のように設定します。

![image](https://qiita-image-store.s3.amazonaws.com/0/30079/ebc2c60b-7c82-d396-c22e-91d0c40a806c.png)

Unityでのアプリバージョンは以下のよう設定します。

![Cursor_と_Unity_5_5_1f1_Personal__64bit__-_Main_unity_-_round-card-animals_-_iPhone__iPod_Touch_and_iPad__Personal___OpenGL_4_1_.png](https://qiita-image-store.s3.amazonaws.com/0/30079/ea5666ed-5845-9bcc-11c4-63b4a650f7a9.png)


次に、目的のプロジェクトを開いてから、先程解凍したファイルの`FirebaseRemoteConfig.unitypackage
`を開きます。各種インポートしたら、FirebaseのConsoleでiOSアプリを追加した際にダウンロードされた`GoogleService-Info.plist`と`google-services.json`をUnityプロジェクトの任意の場所に配置します。今回は以下のようにしました。

![image](https://qiita-image-store.s3.amazonaws.com/0/30079/503f6918-904a-651c-4ed7-147ff964bd5b.png)

C#のコードでfetchは以下のようにします。

```csharp
using System.Threading.Tasks;

	// 強制アップデート用
	void CheckVersion ()
	{
		if (Debug.isDebugBuild) {
			// FirebaseはRemoteConfigの値はキャッシュする. Debug modeだとキャッシュしなくなる
			Debug.Log ("debug mode version check");
			var settings = Firebase.RemoteConfig.FirebaseRemoteConfig.Settings;
			settings.IsDeveloperMode = true;
			Firebase.RemoteConfig.FirebaseRemoteConfig.Settings = settings;
		}
		Task fetchTask = Firebase.RemoteConfig.FirebaseRemoteConfig.FetchAsync (new TimeSpan (0));
		fetchTask.ContinueWith (VersionFetchComplete);
	}

	void VersionFetchComplete (Task fetchTask)
	{
		if (fetchTask.IsCanceled) {
			Debug.Log ("Fetch canceled.");
		} else if (fetchTask.IsFaulted) {
			Debug.Log ("Fetch encountered an error.");
		} else if (fetchTask.IsCompleted) {
			Debug.Log ("Fetch completed successfully!");
		}

		switch (Firebase.RemoteConfig.FirebaseRemoteConfig.Info.LastFetchStatus) {
		case Firebase.RemoteConfig.LastFetchStatus.Success:
			// 正しくフェッチできたときだけバージョンチェックをする. 
			Debug.Log ("Remote data loaded and ready.");
			Firebase.RemoteConfig.FirebaseRemoteConfig.ActivateFetched ();
			var minimumVersion = Firebase.RemoteConfig.FirebaseRemoteConfig.GetValue ("minimum_version").StringValue;
			#if UNITY_EDITOR
			// 注意: EditorではIsDeveloperModeがtrueに設定できないため動作確認できないので実機で確認すること
			minimumVersion = "1.0.0";  // Debug用
			#endif
			if (NeedUpdate (Application.version, minimumVersion)) {
				Debug.Log ("need to update");
			} else {
				Debug.Log ("no need to update");
			}
			break;
		case Firebase.RemoteConfig.LastFetchStatus.Failure:
			switch (Firebase.RemoteConfig.FirebaseRemoteConfig.Info.LastFetchFailureReason) {
			case Firebase.RemoteConfig.FetchFailureReason.Error:
				Debug.Log ("Fetch failed for unknown reason");
				break;
			case Firebase.RemoteConfig.FetchFailureReason.Throttled:
				Debug.Log ("Fetch throttled until " +
				Firebase.RemoteConfig.FirebaseRemoteConfig.Info.ThrottledEndTime);
				break;
			}
			break;
		case Firebase.RemoteConfig.LastFetchStatus.Pending:
			Debug.Log ("Latest Fetch call still pending.");
			break;
		}
	}

	bool NeedUpdate (string currentVersion, string minimumVersion)
	{
		int[] currentVersions = Array.ConvertAll (currentVersion.Split ('.'), int.Parse);
		int[] minimumVersions = Array.ConvertAll (minimumVersion.Split ('.'), int.Parse);
		for (var i = 0; i < minimumVersions.Length; i++) {
			if (currentVersions [i] < minimumVersions [i]) {
				return true;
			}
		}
		return false;
	}
```

注意点としてRemote Configはデフォルトで12時間、値をローカルにキャッシュします。これに気づいてなくて、Firebase ConsoleからRemote Configの値をかえたのに、実機では値がかわらなくてハマってました（[参考](https://firebase.google.com/docs/remote-config/ios#caching_and_throttling)）。このキャッシュの期限は[FetchAsync](https://firebase.google.com/docs/reference/unity/class/firebase/remote-config/firebase-remote-config#fetchasync_3)時に設定できます。

Developer Modeにするとキャッシュしないで常に最新の値がとれます。Developer Modeにするには`Firebase.RemoteConfig.FirebaseRemoteConfig.Settings.IsDeveloperMode = true;`で設定できます。ただし、Unity Editor内ではFirebaseのRemoteConfigは正しく動かないのと、IsDeveloperModeがTrueにならないので注意する必要があります。さきほどのコードではDebug Buildの時のみtrueにしています。

ちなみに`Debug.isDebugBuild`は以下のようにDeveloper Modeにチェックを入れるとtrueになります。

![Cursor_と_Build_Settings_と_Unity_5_5_1f1_Personal__64bit__-_Main_unity_-_round-card-animals_-_iPhone__iPod_Touch_and_iPad__Personal___OpenGL_4_1_.png](https://qiita-image-store.s3.amazonaws.com/0/30079/4d8dfe24-37bf-be26-aabe-f528c6d332bf.png)

今回は、デバッグログを出しただけでしたが、Nativeのダイアログを開きたい場合は[Mobile Native PopUps](https://forum.unity3d.com/threads/released-free-mobile-native-popups.250927/)のアセットを使えば簡単にNativeのポップアップが出せます。自分がやったときは`ISN_NativeCore.h`と`ISN_NativeCore.mm`がデフォルトでただしくXcodeで読み込まれなかったのでその際は手動で、Plugins/iOSの下に移動させ、.txtファイルになっていたのでtxtの拡張子を取り除いたところ正常に動作しました。


# [Analytics](https://firebase.google.com/docs/analytics/) ([Unity用ドキュメント](https://firebase.google.com/docs/analytics/unity/start)]

Unityで該当のプロジェクトを開きながら、`FirebaseAnalytics.unitypackage`を開きインポートします。
以下のようなコードでイベントを送れます。

```csharp
// イベントのみ送りたいとき
FirebaseAnalytics.LogEvent ("l_purchase_finish");

// パラメータをつけて送りたいとき
FirebaseAnalytics.LogEvent (
  "l_start_stage",
  new [] {
    new Parameter ("level", level),
    new Parameter ("stage", stage),
  }
);
```
## iOSでイベントをデバッグ

デバッグ時はリアルタイムでAnalyticsイベントデータを監視したいのでそのようにするには、[DebugView](https://support.google.com/firebase/answer/7201382?hl=ja&utm_id=ad)を参考にしてください。
以下のように`-FIRDebugEnabled`を設定するとリアルタイムにイベントを見ることができます。（もしかしたら`-FIRAnalyticsDebugEnabled`もつけないとだめかも)

![image](https://qiita-image-store.s3.amazonaws.com/0/30079/64304db2-2bc9-7506-6ad8-03553de886df.png)

## Androidでイベントをデバッグ

[DebugView](https://support.google.com/firebase/answer/7201382?hl=ja&utm_id=ad)に書いてある通り以下のようにADBコマンドを実行すると、Firebase ConsoleのDebugViewでリアルタイムに確認することができます。

```
# DebugViewで確認したいとき
adb shell setprop debug.firebase.analytics.app <package_name>

# DebugViewでの確認を終了したいとき
adb shell setprop debug.firebase.analytics.app .none.
```


なお、[ここ](https://firebase.google.com/docs/analytics/android/events#view_events_in_the_android_studio_debug_log)を参考し、以下のようにするとADBでログ送信された際に見ることができます。

```
adb shell setprop log.tag.FA VERBOSE
adb shell setprop log.tag.FA-SVC VERBOSE
adb logcat -v time -s FA FA-SVC
```

# 参考

## エラー対応

* Androidで動かしたときに`Google Play services out of date. Requires...`と出たらAndroid端末のGoogle Play開発者サービスのアプリをアップデートする. => http://qiita.com/heat2160/items/cb08c6afe3bdea75d9ab