GameAnalyticsの導入からABテストの実装まで

# はじめに
はじめまして、株式会社グレンジでクライアントエンジニアをしている田島と申します🙇‍♂️
本投稿は [グレンジ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/grenge) の22日目の記事です

# この記事の対象となる読者
* 開発しているゲームにGameAnalyticsを導入したい人
* GameAnalyticsを使ってABテストを実施したい人

# ABテストとは
アプリを新規インストールしたユーザーを2つ以上のグループに割り振りグループごとにアプリの内容を分岐させる手法のことで、期間の違いによる影響を受けることなく複数のバージョンの数字の違いを見ることができます

GameAnalyticsにおいてはリモートコンフィグ機能を使用して一つのkeyに対して複数種類のvalueをGameAnalyticsサーバーから返すことで割り振られたグループをクライアント側で判別することができるようになっています

# 公式ドキュメント
[About GameAnalytics](https://docs.gameanalytics.com/)

# 前提条件
* Unity 2022 3.47f1
* com.gameanalytics.sdk: 7.10.0
* com.google.external-dependency-manager: 1.2.182
* プラットフォーム: Android
* GameAnalyticsを導入するUnityプロジェクトが作成済みであること

# 手順
* [GameAnalyticsプロジェクトの作成(ブラウザ)](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#gameanalytics%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6)
    * [GameAnalyticsアカウントの作成](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#gameanalytics%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)
    * [プロジェクトの作成](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)
* [GameAnalytics SDKのインポート](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#gameanalytics-sdk%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88)
    * [External Dependency Managerのインポート](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#external-dependency-manager%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88)
    * [manifest.jsonの更新](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#manifestjson%E3%81%AE%E6%9B%B4%E6%96%B0)
* [GameAnalytics Settingsの設定](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#gameanalytics-settings%E3%81%AE%E8%A8%AD%E5%AE%9A)
* [GameAnalytics SDKの初期化](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#gameanalytics-sdk%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96)
* [疎通確認](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#%E7%96%8E%E9%80%9A%E7%A2%BA%E8%AA%8D)
* [ABテストの実装](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#ab%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E5%AE%9F%E8%A3%85)
* [ABテストの作成](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#ab%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)
* [ABテストの分岐の確認](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#ab%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E5%88%86%E5%B2%90%E3%81%AE%E7%A2%BA%E8%AA%8D)
* [まとめ](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#%E3%81%BE%E3%81%A8%E3%82%81)
* [サンプルコード](https://qiita.com/coloured_dance/private/ba319b9b0287e45303af#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89)

## GameAnalyticsプロジェクトの作成(ブラウザ)
:::note info
それぞれの項目が済んでいる方は必要に応じてスキップしてください
:::

### GameAnalyticsアカウントの作成
[GameAnalyticsのトップ画面](https://gameanalytics.com/) から右上の `Sign up` を選択し、メールアドレスもしくはGoogleアカウントで登録します

職種や利用する理由について聞かれるので回答してください

![info.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/c2aec558-e4eb-457b-b123-3ff82ffe42d5.png)

パスワードを設定します

![password.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/97e3b9d0-e06c-b494-c1ef-91245a884fc3.png)

組織に関する情報を入力し、組織を作成します

![スクリーンショット 2024-12-19 午後10.02.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/60c03d9b-beb7-0f63-d735-c617888b37d7.png)

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

アカウントの登録まで完了すると↓以下のような組織の管理画面に遷移します
プロジェクトを作成するために組織の下にスタジオを作る必要があるため、真ん中の `+ Create Studio` ボタンを押下しスタジオ作成ダイアログを開きます

![スクリーンショット 2024-12-19 午後10.09.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/ba132627-9db1-0551-d867-37f299efb5c1.png)

スタジオが所属する組織、スタジオの名前を入力しスタジオを作成します

![スクリーンショット 2024-12-19 午後10.12.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/6e179ff5-9200-5659-2d72-3b037391a711.png)

組織の管理画面の真ん中に `+ Create Game` というボタンが表示されるようになるのでこれを押下しプロジェクト作成ダイアログを開きます

![スクリーンショット 2024-12-19 午後10.19.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/0918768d-ab91-a086-522f-b03a1ba08c91.png)

ゲームが所属する組織、スタジオ、タイトル、SDKを導入する環境、プラットフォームを入力しプロジェクトを作成します

ここではプラットフォームとしてAndroidを選択します
iOSもリリースする場合、iOS用のGameAnalyticsプロジェクトを作成する必要があります

![スクリーンショット 2024-12-19 午後10.22.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/9896a893-2cfb-7e73-78d3-50028c33877d.png)

プロジェクトの作成を完了すると、組織の管理画面に作成したプロジェクトが表示されるようになります

:::note warn
プロジェクト作成後、最大1分ほどプロジェクトのページにアクセスできません
:::

## GameAnalytics SDKのインポート
Unityエディタでの作業に移ります

SDKのインポートはUnity Package Manager(以下upm)を使用する方法と `.unitypackage` ファイルを使用する方法がありますが、ここではupmを使用する方法を取ります

[Setup/Unity Package Manager](https://docs.gameanalytics.com/integrations/sdk/unity#unity-package-manager)

### External Dependency Managerのインポート
upmでのインポートではGoogleのExternal Dependency Managerが必要となるため [ダウンロードページ](https://developers.google.com/unity/archive#external_dependency_manager_for_unity) からターボールファイル(.tgz)をダウンロードし `Packages` ディレクトリの下に配置します

### manifest.jsonの更新
`Packages/manifest.json` に 

```json
    "com.gameanalytics.sdk": "7.10.0",
    "com.google.external-dependency-manager": "file:com.google.external-dependency-manager-1.2.182.tgz"
```

を記載します

:::note warn
GameAnalytics SDKの最新バージョンについてはどうやらGameAnalyticsのドキュメント内には記載がないようなので [OpenUPM](https://openupm.com/packages/com.gameanalytics.sdk/?subPage=versions) を参照します
:::

また、`Packages/manifest.json` にScoped Registryを追加します

```json
  "scopedRegistries": [
    {
      "name": "Game Package Registry by Google",
      "url": "https://unityregistry-pa.googleapis.com/",
      "scopes": [
        "com.google"
      ]
    },
    {
      "name": "OpenUPM",
      "url": "https://package.openupm.com/",
      "scopes": [
        "com.gameanalytics"
      ]
    }
  ]
```

Unityエディタに戻るとリロードが走り、 `Packages` 以下に `GameAnalytics` ディレクトリが追加されているかと思います

![スクリーンショット 2024-12-19 午後11.24.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/2dc692f3-d8db-c542-828f-20535d407aac.png)

## GameAnalytics Settingsの設定
GameAnalyticsの設定ファイルを更新していきます

`Window > GameAnalytics > Select Settings` を押すと `Assets/Resources/GameAnalytics/Settings.asset` というファイルが作成されます

:::note warn
すでにある場合は該当のファイルが選択されます
:::

Inspector上にメールアドレスとパスワードを入力するフォームが表示されるかと思うのでGameAnalyticsに登録したアカウントのメールアドレス、パスワードを入力し `Login` を押します

GameAnalyticsで作成したプロジェクトと同じプラットフォームをプルダウンから選択し、 `Add platform` を押します

 ![スクリーンショット 2024-12-19 午後11.13.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/26baa3da-aef7-59c1-83b0-9e06e3c07359.png)

組織、スタジオ、プロジェクト名をプルダウンから選択するとプロジェクトに紐づく `Game Key` および `Secret Key` が自動で入力されます

Buildの欄はデフォルトのままで問題ないですが、アプリのバージョンに対応させるとわかりやすいと思います

:::note warn
入力しただけだと `.asset` ファイルが更新されないので `Cmd(Ctrl) + S` で保存します
:::

![スクリーンショット 2024-12-19 午前10.30.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/b75d248d-f061-bfef-1792-c121a2d383e8.png)


## GameAnalytics SDKの初期化
GameAnalytics SDKを初期化するプログラムはシンプルで

```c#
GameAnalytics.Initialize();
```

を呼び出すだけです


サンプルコードでは簡便のため、Awakeから初期化を呼び出していますがプロジェクトの初期化シーケンスの適切なタイミングで呼び出すことが望ましいです

## 疎通確認
:::note warn
このままABテストの実装に移ってもよいのですがABテスト作成時にGameAnalytics SettingsのBuild番号を指定する項目があり、アクティブユーザーが存在しているBuild番号しか選択肢として表示されないためここで一度実機による疎通確認を行います
:::

アプリをビルドしプレイしたのちGameAnalyticsのプロジェクトのダッシュボードに移動します
左のタブから `RealTime` を選択し遷移したページで `👁️ Live Events` を選択します

![スクリーンショット 2024-12-19 午後11.37.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/5137fbcd-d13f-bead-d290-2bfeca186c72.png)

疎通が完了していれば↓以下のようにイベントが送信されています

![スクリーンショット 2024-12-19 午後11.35.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/9432fd0b-05ba-e95b-88c3-1d21890c6132.png)

## ABテストの実装
内部の処理としては先述の通り、リモートコンフィグから値を取得しその値によってアプリ内部の処理を分岐させる、というものです

GameAnalyticsの初期化が完了するまではテストグループを判定できないため初期化が完了した際のコールバックでフラグを立てます

```c#
    private bool _initializedFlag;

    private void Awake()
    {
        GameAnalytics.onInitialize += OnInitialize;
        ...
    }

    private void OnInitialize(object sender, bool eventArgs)
    {
        _initializedFlag = true;
    }
```

また、リモートコンフィグが取得可能になるまでは同様にテストグループを判定できないためリモートコンフィグのステータスが更新された際のコールバックでフラグを立てます

```c#
    private bool _remoteConfigIsReady;

    private void Awake()
    {
        GameAnalytics.OnRemoteConfigsUpdatedEvent += OnUpdateRemoteConfigs;
        ...
    }

    private void OnUpdateRemoteConfigs()
    {
        _remoteConfigIsReady = GameAnalytics.IsRemoteConfigsReady();
    }
```

`_initializedFlag` および `_remoteConfigIsReady` が `true` になるまで待機し `true` になったらリモートコンフィグから値を取得します

```c#
    private IEnumerator GetTestGroup()
    {
        yield return new WaitUntil(() => _initializedFlag && _remoteConfigIsReady);

        var testGroupRemoteConfigValue = GameAnalytics.GetRemoteConfigsValueAsString(
            "test_group",
            "group_a"
        );

        PlayerPrefs.SetString("test_group", testGroupRemoteConfigValue);
        PlayerPrefs.Save();
    }
```

サンプルコードでは簡便のため `PlayerPrefs` に値を保存していますがご自身のプロジェクトにユーザーデータを保存する機構などがあればそちらで保存することを推奨します
プロジェクトのサーバーが建てられているのであればそちらで保存するのがよいでしょう

取得したリモートコンフィグの値をフラグとしてラップして返すようにすると扱いやすくなるかと思います

例: 
```c#
public class UserData
{
    private string _testGroup;

    // NOTE: グループBだったら難易度を上げたver
    public bool HardModeFlag => _testGroup == "group_b";
}
```

サンプルコードではABテストのグループによって画面に表示するテキストを分岐させるようにしています

```c#
    private IEnumerator GetTestGroup()
    {
        yield return new WaitUntil(() => _initializedFlag && _remoteConfigIsReady);

        var testGroupRemoteConfigValue = GameAnalytics.GetRemoteConfigsValueAsString(
            "test_group",
            "group_a"
        );

        PlayerPrefs.SetString("test_group", testGroupRemoteConfigValue);
        PlayerPrefs.Save();

        var testGroup = PlayerPrefs.GetString("test_group", "group_a");
        _testGroupText.text = testGroup;
    }
```

## ABテストの作成
GameAnalyticsのプロジェクトのダッシュボードに移動します
左のタブから `A/B Testing` を選択し遷移したページで `Create experiment` を選択します

![スクリーンショット 2024-12-20 午前12.35.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/0dd46459-96ef-dfdc-d194-dc33d38b9d2d.png)

↓以下の項目を入力します
* ABテスト名
* ビルド番号
* ユーザー上限
* ABテストに参加する割合
* リモートコンフィグのkey
* それぞれのグループがリモートコンフィグから取得する値
* 開始時間
* 比較する指標

入力後は↓以下のようになります

![スクリーンショット 2024-12-20 午前12.38.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/911688c3-fd7d-d379-f2b1-0564cb37bb20.png)

![スクリーンショット 2024-12-20 午前12.38.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/ee3938b8-4c49-3965-a400-c64716b1473b.png)

:::note warn
ABテストは新規インストールユーザーのみが対象となり、例えばABテストに参加する割合(Enrollment rate)を10にした場合、新規インストールユーザーの10%がABテストに参加することになります
:::

`Start test` を押してABテストを開始します

## ABテストの分岐の確認

アプリをビルドし、再インストールを繰り返してアプリの内容が分岐することを確認します

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/3c0369c9-ca6d-af31-6beb-6a4c2339747c.jpeg" width="270px" > <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412552/c3ba51da-99ca-d384-c48b-2d2aa9078c3d.jpeg" width="270px" >

## まとめ
最後までご覧いただきありがとうございます
SDKの導入は確認するまでにラグがあったりでしんどいことが多いイメージですがGameAnalyticsはほぼリアルタイムで確認できてその点はとてもありがたいです

この記事が1人でも多くの人の助けになれば幸いです
指摘、質問等ございましたら遠慮なくコメントしていただけますと幸いです🙇‍♂️

## サンプルコード

<details><summary>StartScene.cs</summary>

```c#
using System;
using System.Collections;
using GameAnalyticsSDK;
using TMPro;
using UnityEngine;

public class StartScene : MonoBehaviour
{
    private const string DefaultTestGroupValue = "Default";

    private const string TestGroupRemoteConfigKey = "test_group";
    private const string TestGroupRemoteConfigDefaultValue = "group_a";

    private const string TestGroupPreferenceKey = "test_group";

    [SerializeField] private TextMeshProUGUI _testGroupText;

    private bool _initializedFlag;
    private bool _remoteConfigIsReady;

    private void Awake()
    {
        GameAnalytics.onInitialize += OnInitialize;
        GameAnalytics.OnRemoteConfigsUpdatedEvent += OnUpdateRemoteConfigs;

        GameAnalytics.Initialize();
    }

    private void Start()
    {
        var testGroup = PlayerPrefs.GetString(TestGroupPreferenceKey, "");
        if (!string.IsNullOrEmpty(testGroup))
        {
            _testGroupText.text = testGroup;
            return;
        }

        StartCoroutine(GetTestGroupCoroutine());
    }

    private void OnInitialize(object sender, bool eventArgs)
    {
        _initializedFlag = true;
    }

    private void OnUpdateRemoteConfigs()
    {
        _remoteConfigIsReady = GameAnalytics.IsRemoteConfigsReady();
    }

    private IEnumerator GetTestGroupCoroutine()
    {
#if UNITY_EDITOR
        _remoteConfigIsReady = true;
#endif

        yield return new WaitUntil(() => _initializedFlag && _remoteConfigIsReady);

        var testId = GameAnalytics.GetABTestingId();
        if (string.IsNullOrEmpty(testId))
        {
            SaveTestGroup(DefaultTestGroupValue);
            SetTestGroupText(DefaultTestGroupValue);

            yield break;
        }

        var testGroupRemoteConfigValue = GameAnalytics.GetRemoteConfigsValueAsString(
            TestGroupRemoteConfigKey,
            TestGroupRemoteConfigDefaultValue
        );

        SaveTestGroup(testGroupRemoteConfigValue);
        SetTestGroupText(testGroupRemoteConfigValue);
    }

    private void SaveTestGroup(string testGroup)
    {
        PlayerPrefs.SetString(TestGroupPreferenceKey, testGroup);
        PlayerPrefs.Save();
    }
    
    private void SetTestGroupText(string text)
    {
        _testGroupText.text = text;
    }
}
```

</details>