【unity】全ユーザー参加のランキング機能をGoogleスプレッドシート1枚で作った

#はじめに
　unityでマインスイーパーを作っていて、全ユーザーが参加できるランキング機能が欲しくなった。　
→　データベース、PlayFabでもできるらしい。なんならAsset Storeにそれっぽいのある。でもせっかくならできるだけ自力で作りたい。
→　Googleスプレッドシートをランキングに見立てれば作れんじゃね？
　
ということで作りました。
#結果
　**100人しか参加できないランキングができた。**
　悲しいですね。。　GoogleスプレッドシートAPIには同時編集可能人数100人までという制限があったんです。。　このことは実装終盤に知りました。結局上記の制限が課された100人参加可能なランキング機能は作りきりましたが、最終的にはみなさんおなじみ？のPlayFabを使って実装しました。まあでも個人や少人数利用の無料DB開発だったと思えばまあいい勉強になったと思います。以下、

①環境構築　
②unityにスプレッドシートから情報をもってくる方法　
③unityからスプレッドシートに情報を書き込む方法　
④ランキングを作る上でのアルゴリズム　

を備忘録みたいな感じで書いていきます。よろしくお願いします。
#動作環境
Unity2020 1.5f1
Visual Studio 2017
Win10


#①環境構築
###1. スプレッドシートAPIの取得、スプレッドシートの作成及び設定変更
1. [Google Cloud Platform](https://console.cloud.google.com/home/)にログイン、新規プロジェクトを作成しスプレッドシートAPIを取得
2. [Google スプレッドシート](https://www.google.com/intl/ja_jp/sheets/about/)にログイン、新規スプレッドシートを1枚作成
2. 作成したスプレッドシート上部の「ファイル」→「ウェブに公開」→「ドキュメント全体」を「シート1」に変更→「ウェブページ」を「カンマ区切り形式(.csv)」に変更→「公開」→出てきたURLをコピー（このURLを**URL1**とする）
2. 作成したスプレッドシート右上の「共有」→名前を適当に変更して「保存」→左下の「変更」→「閲覧者」を「編集者」に変更→「完了」
2. 作成したスプレッドシート上部の「表示形式」→「数字」→「書式なしテキスト」をクリック

###2. Unityの設定の変更
unity editor上部の「Edit」→「Project Settings」→「Player」→「Other Settings」→「Api Compatibility Level」を「.NET 4.x」に変更

###3. dllをunityにインポート
unityで適当にC#スクリプトを作成しVisual Studioで開く→「ツール」→「Nugetパッケージマネージャー」→「パッケージマネージャーコンソール」をクリック → パッケージマネージャーコンソールが開くので

`PM> Install-Package Google.Apis.Sheets.v4`

を実行
→ Assets直下に「Plugins」フォルダを作成→以下7つのdllファイル
・Google.Apis.Auth.dll
・Google.Apis.Auth.PlatformServices.dll
・Google.Apis.Core.dll
・Google.Apis.dll
・Google.Apis.PlatformServices.dll
・Google.Apis.Sheets.v4.dll
・Newtonsoft.Json.dll
を先ほど作成した「Plugins」フォルダにコピー

#②unityにスプレッドシートから情報をもってくる方法　

今回作成するスクリプトは2つです。
1. RankingManager.cs：スプレッドシートから情報を持ってくる関数と、持ってきた情報が保存されているスクリプト
2. test.cs：上記で用意した関数を呼び出すスクリプト

```c#:RankingManager.cs
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

public static string[][] Datas; //Datas[i][j]:=スプレッドシートのi行j列目の内容を格納

public static IEnumerator GetFromWeb()
    {
        UnityWebRequest request = UnityWebRequest.Get(/*①環境構築で得たURL1を入力*/);
        yield return request.SendWebRequest();

        var protocol_error = request.result == UnityWebRequest.Result.ProtocolError ? true : false;
        var connection_error = request.result == UnityWebRequest.Result.ConnectionError ? true : false;
        if (protocol_error || connection_error)
        {
            Debug.Log(request.error);
        }
        else
        {
            Datas = ConvertCSVtoJaggedArray(request.downloadHandler.text);
        }

    }

string[][] ConvertCSVtoJaggedArray(string t)
    {
        var reader = new StringReader(t);
        //reader.ReadLine();
        var rows = new List<string[]>();
        while (reader.Peek() >= 0)
        {
            var line = reader.ReadLine();
            var elements = line.Split(',');
            for (var i = 0; i < elements.Length; i++)
            {
                elements[i] = elements[i].TrimStart('"').TrimEnd('"');
            }
            rows.Add(elements);
        }
        return rows.ToArray();
    }
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
```
上記のコードを作成した状態で下記のようなtest.csを書いてあげることでRankingManager.csのDatas配列にスプレッドシートの情報が格納されます。

```c#:test.cs

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

void Start()
{
    StartCoroutine(StartProcessing());
}

IEnumerator StartProcessing()
{

    var async1 = StartCoroutine(RankingManager.GetFromWeb());
    yield return async1;
    
    //例 Debug.Log(RankingManager.Datas[0][2]);
}

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

```
#③unityからスプレッドシートに情報を書き込む方法　

```c#:RankingManager.cs

UserCredential credential;

public static async Task AutorizeAsync()
    {
        var clientId = "--取得したクライアントID--";
        var clientSecret = "--取得したクライアントシークレット--";
        var user = "user";

        var secrets = new ClientSecrets
        {
            ClientId = clientId,
            ClientSecret = clientSecret
        };

        var scopes = new[]
        {
            SheetsService.Scope.Spreadsheets
        };

        credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(secrets, scopes, user, CancellationToken.None);
        Debug.Log("AutorizeAsync finish");
    }


public static IEnumerator UpdateGSS(string pos, string message)
    {

        var service = new SheetsService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credential,
            ApplicationName = "app" // ←適宜変更
        });
        var wv = new List<IList<object>>()
        {
            new List<object>{message}
        };
        var body = new ValueRange() { Values = wv };
        var req = service.Spreadsheets.Values.Update(body, spreadSheetId, $"シート1!{pos}");
        req.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
        var result = req.Execute();

    }
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
```
上記のコードを作成した状態で下記のようなtest.csを書いてあげることでスプレッドシートの任意の座標に好きな文字列を書き込むことができます。

```c#:test.cs
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

void Start()
{
    StartCoroutine(StartProcessing());
}

IEnumerator StartProcessing()
{
    var async1 = RankingManager.AutorizeAsync();
    yield return async1;

    var async2 = StartCoroutine(RankingManager.UpdateGSS("A3", "hello world!"));
    yield return async2;
}


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

```
#④ランキングを作る上でのアルゴリズム　

![スクリーンショット (496).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421961/2f7e8fbc-a6dc-e6ea-0e8c-ce5058530d1a.png)

 例としてユーザー名とクリアタイムを記録するランキングを作る体で説明します。
ランキングの更新は、「ユーザーがランキングページに移動したとき」に行われます。
ゲームクリア時にはPlayerPrefsにスコアを登録します（初回の場合はユーザーネームも登録します）。
 ランキングページに移動したときまずE列を参照して自分のユーザーネームがあるか探します。
1. なければPlayerPrefsからユーザーネーム及びスコアをスプレッドシートに書き込みます。
2. あればそこに書いてあるスコアとPlayerPrefs上のスコアのうちより良い方のスコアでスコア欄を更新します。
次にE列F列の全情報を持ってきてソートします。上位10位のデータをA、B、C列に書き込んで終了です。


#おわりに
 確かにGoogleスプレッドシート1枚でランキング機能が実装できました。しかしながら100人という制限があるため実際のゲームではおとなしくAsset StoreやPlayFabを使うのをおすすめします。Gmail APIを使えば人数無制限できないこともないかもしれませんがそこはみなさんにおまかせします。
ありがとうございました。