[Unity] NCMBを用いてランキング機能を実装する

ニフティクラウド mobile backend(NCMB)を用いてUnity上でランキング機能を実装したのでそのメモ。

具体的には、クラウド上にランキングのデータベースを作成し、そのデータの取得・更新をUnityで行う。

制限はあるが、無料で使うことができるのでNCMBはとても便利。
インディーズのゲームならAPIリクエスト数200万回/月でも十分だろう。
また、ランキングだけなら無料ストレージ5GBを消化することもなさそう。

具体的な導入方法は下記リンクに載っているので、それを参照
[イントロダクション (Unity) : クイックスタート | ニフティクラウド mobile backend](http://mb.cloud.nifty.com/doc/current/introduction/quickstart_unity.html#/Unity/)


現在、音ゲーをUnityで製作しているため、「曲名・曲の難易度・プレイヤー名・スコア」を保存することとする。
レポートっぽい堅い文章となってしまっている点についてはご了承を。

#サンプルコード
まず、忘れてはいけないのは、

```
using NCMB;
using System.Collections.Generic;
```

を始めに記述すること。初心者だとここで詰まりそう（適当）

##スコアのセーブ
以下にスコアのセーブをするコードを示す。

```
public void saveScoreRanking(string musicName, string level, string playerName, int score){
		NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject> ("ScoreRanking"); // NCMB上のScoreRankingクラスを取得
		query.WhereEqualTo ("musicname", musicName); // 曲名でデータを絞る
		query.WhereEqualTo ("level", level); // 曲の難易度でデータを絞る
		query.WhereEqualTo ("playername", playerName); // プレイヤー名でデータを絞る
		query.FindAsync ((List<NCMBObject> objList ,NCMBException e) => {
     		if (e == null) { // データの検索が成功したら、
        		if( objList.Count == 0 ){ // ハイスコアが未登録の場合
           			cloudObj ["musicname"] = musicName; // 曲名、難易度、プレイヤー名、スコアを
					cloudObj ["level"] = level;
					cloudObj ["playername"] = playerName;
					cloudObj ["score"] = score;
            		cloudObj.SaveAsync(); // セーブ
           		}else{ // ハイスコアが登録済みの場合
				   	int cloudScore = System.Convert.ToInt32(objList[0]["score"]); // クラウド上のスコアを取得
				   	if(score > cloudScore){ // 今プレイしたスコアの方が高かったら、
						objList[0]["score"] = score; // それを新しいスコアとしてデータを更新
          				objList[0].SaveAsync(); // セーブ
					}
          		}
        	}
      	});
}
```

大体、公式マニュアル通りのコードである。引数は、曲名、曲のレベル、プレイヤー名、スコアとなっている。
ここでは、 NCMB上のScoreRankingクラスを取得する。Excelで言うと、ScoreRanking.xlsxという感じ。

プログラムの流れとしては、該当データを検索し、
　・まだハイスコアが未登録であったら、データをクラウドにセーブ
　・ハイスコアが登録してあったら、クラウド上のスコアと今プレイして出たスコアを比較し、現在のスコアの方が大きかったら、それをセーブ
となる。

ここで、「ハイスコアが登録してあるかどうか」という判定はどうするのかという問題が生じる。
前提として、プレイヤー名の重複は認められないものとする。（例：すでにhogehofeという名前の人が登録していたら、自分はhogehogeを使えない）
名前の重複がないため、曲名と難易度でデータを絞り、そこに自分の名前がなければハイスコアが未登録であると確認できる。
それは、query.WhereEqualToで確認できる。こうして自分の名前と一致するデータだけを絞っていく。
objList.Countで自分のデータ数が取得できるため、objList.Count = 0ならハイスコア未登録となる。

##ランキングの取得
次に、ランキング取得をするコードを示す。

```
public void getScoreRanking(string musicName, string level){
		NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject> ("ScoreRanking");
		query.WhereEqualTo ("musicname", musicName);
		query.WhereEqualTo ("level", level);
		query.OrderByDescending ("score"); // スコアを降順に並び替える
		query.Limit = 10; // 上位10件のみ取得
		query.FindAsync ((List<NCMBObject> objList ,NCMBException e) => {
			if(e == null){ //検索成功したら
				List<string> nameList = new List<string>(); // 名前のリスト
				List<int> scoreList = new List<int>(); // スコアのリスト
				for(int i = 0; i < objList.Count; i++){
					string s = System.Convert.ToString(objList[i]["playername"]); // 名前を取得
					int n = System.Convert.ToInt32(objList[i]["score"]); // スコアを取得
					nameList.Add(s); // リストに突っ込む
					scoreList.Add(n);
				}
			}
		});
}
```
データのセーブのコードと似たような感じになる。
query.OrderByDescendingでデータを降順に並び替えることができる。また、query.Limitで上位何件取得するかを決められる。
割とゴリ押しコードなので、名前用・スコア用の2つに分けてリストを用いた。（もっとスマートな方法はあるはず）
例として、objList[0]["playername"]で1位の人の名前を取得できる。
objListの添字をfor文で変えていけば、上位10件のランキングを取得することができる。
ここでは記述してないが、リスト内のものをすべてテキスト表示すればランキング機能の完成となる。

##プレイヤー名の存在を確認 / 変更
また、プレイヤーの名前に関するコードを2つ示す。

```
public void checkPlayerName(string playerName){
		NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject> ("ScoreRanking");
		query.WhereEqualTo ("playername", playerName); 
		query.CountAsync((int count , NCMBException e )=>{ // 1つ上のコードで絞られたデータが何個あるかかぞえる 
    		if(e == null){
				if(count == 0){ // 0個なら名前は登録されていない
					Debug.Log("登録可能です");
				}else{ // 0個じゃなかったらすでに名前が登録されている
					Debug.Log("登録できません");
				}
    		}
		});
}
```
ここでは、プレイヤー名がすでに存在しているかどうかを確認する。
query.WhereEqualToでプレイヤー名だけのデータに絞り、query.CountAsyncでその個数を数える。
個数が0個であれば、まだ同じ名前のプレイヤーはいないが、0個でなければ。すでに同じ名前のプレイヤーが存在している。

```
public void renamePlayerName(string previousName, string newName){
		NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject> ("ScoreRanking");
		query.WhereEqualTo ("playername", previousName); // 古い名前でデータを絞る
		query.FindAsync ((List<NCMBObject> objList ,NCMBException e) => {
			if(e == null){ //検索成功したら
				if(objList.Count > 0){ // 1個以上あれば
					for(int i = 0; i<objList.Count; i++){
						objList[i]["playername"] = newName; // 新しい名前にする
						objList[i].SaveAsync(); // セーブ
					}
				}
			}
		});
}
```
ここでは、プレイヤー名の変更を行う。引数：古い名前、新しい名前
まず、古い名前でデータを絞る。
そして、古い名前で登録されているデータをすべて新しい名前に置き換える。
（新しい名前も、checkPlayerNameで登録できるかどうか確認する必要がある。ここでは確認していない）
このコードは、1つのデータの名前を更新するたびにセーブしているため、API数的には非常に効率が悪い。
データベースに関してはあまり知識がないため、一括置き換え的なコードがあるならば、コメント欄にお願いします・・・