週刊SleepNel新聞

SleepNel所属のぽうひろが日々の個人開発で気になったことを綴ります。

FirebaseをUnityで使ってランキング機能を作ろう![実装編]

みなさん、こんにちは。ぽうひろです。

f:id:pouhiroshi:20170125234924p:plain

前回はFirebaseをUnityで使うための準備について説明しました。
sleepnel.hatenablog.com

今回は、簡単に実装方法を説明していきます。

前回、firebase側でアプリの設定を行いましたので、以下のconsole画面からデータベースなどを見ることができます。
https://console.firebase.google.com/

コンソールから作ったプロジェクトを選びます。
f:id:pouhiroshi:20170126000520p:plain:w400

左側のメニューにDatabaseがあるので選ぶと、データベースが見れます。
f:id:pouhiroshi:20170126000612p:plain:w400

firebaseのDBはこのようにjsonツリーになっていまして、consoleでGUIでデータを編集することができます。
f:id:pouhiroshi:20170126000929p:plain:w400

さて、まずUnityで使う前の準備が少し残っています。
FirebaseのRealtimeDatabaseには認証機能がありまして、基本はそれを使ってDB使用して良いかを判定します。これは別途SNS認証を使うとか、メール認証アドレスにするのかとか選択肢がありますので、またの機会にやろうと思います。
今回は開発モードとして、読み書き権限をOKにしちゃう設定にします。
firebase consoleのルールのタブにjson形式で設定を書きます。
以下のように rulesの.readと.writeをtrueにして「公開」ボタンを押せばOKです!
f:id:pouhiroshi:20170126092515p:plain:w400

あと、データベースのアドレスはここに書いてあるアドレスになります。
Unity側のコードで必要になります。
f:id:pouhiroshi:20170126092836p:plain:w400

ただしこの状態だと注意が必要です。
この状態だと、DB URLが他の人に知られてしまうと、好きなだけ読み書きできてしまうので開発中だけの設定と考えておいてくださいね!(僕ももうwrite権限offしてありますよ(*´∀`))

はい、今度こそ準備完了です。
それでは実装コードの方を説明します。

まずはデータ検索(参照)の方から説明します。
ドキュメントはこちらが参考になります。
Retrieving Data  |  Firebase

データ検索(参照)のやり方

using Firebase;
using Firebase.Database;
using Firebase.Unity.Editor;
public class Ranking : MonoBehaviour {
  //別メソッドのDB操作のためにprivateフィールドにとっておく
  private DatabaseReference timeRankDb;

  // Use this for initialization
  void Start () {
    //データベースURLを設定
    FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("consoleに出てたDBのURL");
    // テーブル名的なノード名を指定して、DB参照をprivateフィールドにとっておく
    timeRankDb = FirebaseDatabase.DefaultInstance.GetReference("timeRanks");
    }
}

ざっくりポイントは、
using Firebase;
using Firebase.Database;
using Firebase.Unity.Editor;
と、データベースURLを指定してノード名(テーブル名的な)指定でDB参照を取る ところですかね。
UnityだとStartメソッドで1度実行しておくと良いと思います。

FirebaseのDBはjsonツリー状の構成となっています。
今回、ボスNoごとのクリアタイムランキングを実装したいと思いますので、構成は以下のように考えています。

root
   ∟ timeRanks
    ∟ ボスNo(1〜15個くらい)
      ∟ 記録ID (ボスIDごとに10個まで(10位まで表示したい))
         ∟  ユーザ名(name)
         ∟  タイム(time)

実際はこんな感じになってます。何となくわかるでしょうか?
f:id:pouhiroshi:20170126094434p:plain:w400

さて、まずはデータの取得をやってみたいと思います。
先ほどStart()で取得しておいたDB参照を利用してデータ参照します。

//bossNoのノードからtimeで昇順ソートして最大10件を取る(非同期)
timeRankDb.Child(boss.bossNo.ToString()).OrderByChild("time").LimitToFirst(10).GetValueAsync().ContinueWith(task =>{
  if(task.IsFaulted){ //取得失敗
    //Handle the Error
  }else if(task.IsCompleted){ //取得成功
    DataSnapshot snapshot = task.Result; //結果取得
    IEnumerator<DataSnapshot> en = snapshot.Children.GetEnumerator(); //結果リストをenumeratorで処理
    int rank = 0;
    while(en.MoveNext()){ //1件ずつ処理
      DataSnapshot data = en.Current; //データ取る
      string name = (string)data.Child("name").GetValue(true); //名前取る
      string time = (string)data.Child("time").GetValue(true); //時間を取る
      //順位のuGUIに値を設定
      GameObject rankItem = rankList.transform.GetChild(rank).gameObject;
      TimeRank timeRank = rankItem.GetComponent<TimeRank>();
      timeRank.SetText(rank+1, name, getTimeStr(time)); //順位1位から
      rank++;
    }
}
});

コメントにも色々書いていますが、ポイントとしては

  • データ検索はDatabaseReferenceを使って.Child("ノード名")、ソートしたかったら.OrderByChild("ソートキーに使う項目名")、取得件数絞りたければ.LimitToFirst(件数) をつなげていき、最後に.GetValueAsync().ContinueWith(task =>{ です。
  • 検索結果は非同期でtaskに返ってきます(コールバック)ので、task.IsCompletedの時、データの処理を行います。task.IsFaultedの時は通信エラーなどが想定されますので、エラーメッセージを出すみたいなハンドリングをしてもいいですね(*´∀`)
  • 検索結果はDataSnapshot snapshot = task.Result; で取れます。複数データの場合はsnapshot.Childrenに複数データが入りますので、GetEnumeratorで繰り返し処理するといいでしょう。
  • 1件のデータはDataSnapshotのChild("項目名").GetValueで取れます。object型になっているので(string)でキャストすると良いでしょう。(データ投入の際、floatとかで登録したかったのですが、どうもエラーが出る模様・・・続報あったら追記します)

結果、このような感じになりました(*´∀`)
f:id:pouhiroshi:20170126121408g:plain:w400

ちゃんと時間が早い順に取れてますね!

さて、次はデータ登録の実装を説明したいと思います。
ドキュメントはこちらが参考になります。
Save Data  |  Firebase

データ登録(insert&update)のやり方

//まずbossNoのノードにレコードを登録(Push)して、生成されたキーを取得(これを1件のノード名に使う)
string key = timeRankDb.Child(bossId.ToString()).Push().Key;
//登録する1件データをDictionaryで定義(nameとtime)
Dictionary<string, object> itemMap = new Dictionary<string, object>();
itemMap.Add("name", name);
itemMap.Add("time", time);
//1件データをさらにDictionaryに入れる。キーはノード(bossNo/さっき生成したキー)
Dictionary<string, object> map = new Dictionary<string, object>();
map.Add(bossId.ToString()+ "/" +key , itemMap);
//データ更新!
timeRankDb.UpdateChildrenAsync(map);

ここでのポイントは

  • データの登録はDbReference.Child("ノード名").Push()でやる。生成されたデータを表す一意キーを使うために.Keyでキーをとっておくと良い。
  • データの更新はDbReference.UpdateChildrenAsync(ノードがキーで値が更新したいデータのDictionary); で行う。

実際はこんな感じで登録されます!イメージ湧くでしょうか。
f:id:pouhiroshi:20170126122733p:plain:w400
この暗号みたいなのが生成されたキーで、データのパスは 1/-KbIyIe621y8MZ9ySB5S ということになります。

駆け足で説明しましたが、いかがでしたでしょうか。

私もまだあまり使いこなしているわけではないし、まだSDKもベータ版なので今後やり方など変わるかもしれませんが、何となく簡単に利用できることがお分かりいただけたんじゃないかと思います。

また何か情報があったら、書きたいと思います(*´∀`)

それではまた!!