You are finishing up your single-player game, already wrote the end screen… but something feels wrong. Something is missing. Your player has no idea how well he played, he sees his score but has no idea how other people finished.

Don’t worry! Having a Top 10 screen is quick and easy using Firebase. It’s a service that provides you with a JSON database - so you get no real processing online, and no way to have a “server” so to speak - but it has a free tier and data validation rules that should be enough for a simple game.

Start by creating an app in Firebase. When you are done, go to https://yourApp.firebaseio.com to enter the Forge. This articles assumes your database will have the following structure:

appName
  |- scores
    |- random ID
      |- cat
      |- name
      |- score
      |- time

The ID is auto-generated when data is posted to firebase and acts as a key - you could use a unique player ID for this. cat is a category, such as if the player died or finished the game, etc. time is an auto-generated value.

As security rules, use the following to make sure your data is validated to some extent and that no one can write on everything or otherwise mess with already existing content:

{
  "rules": {
    ".read": false,
    ".write": false,
    "scores": {
      ".read": true,
      ".indexOn": ["score"],
      "$score": {
        ".write": "!data.exists()",
        ".validate": "newData.hasChildren(['name', 'score', 'cat', 'time'])",
        "name": {
          ".validate": "newData.isString() &&
                        newData.val().length <= 30"
        },
        "score": {
          ".validate": "newData.isNumber()"
        },
        "cat": {
          ".validate": "newData.isString() &&
                        (newData.val() == 'death' 
                          || newData.val() == 'failure'
                          || newData.val() == 'success')"
        },
        "time": {
          ".validate": "newData.isNumber() &&
                        newData.val() <= now"
        }
      }
    }
  }
}

The score validation is also where you could do some sort of validation - something along the lines of && newData.val() <= yourMaximumScore.

And that’s it, you are done with the server side! For the client/game side, we will not use Firebase’s default client: it won’t work out of the box, specially if you are using the web player, so we’ll use UnityHTTP and the REST API. Just download the master revision and extract it somewhere in your project.

We’ll use two scenes. The first one shoud be your endgame scene - attach a script to an object, a button for example, with the following code that will upload the player’s score:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System;

public class EndGame: MonoBehaviour {

  public void BtnSendScore(string category) 
  {
    string playerName = //get your player name here
    if (playerName == null || string.IsNullOrEmpty (playerName.Trim())) 
    {
      playerName = "Some random player";
    }
    
    playerName = playerName.Trim();
    if(playerName.Length > 30) 
    {
      playerName = playerName.Substring(0, 30); 
    }
    int playerScore = //find your player score here

    SendScore (playerName, playerScore, category);
  }

  protected void SendScore(string name, int score, string category) 
  {
    Hashtable data = new Hashtable();
    data.Add ("name", name );
    data.Add ("score", score );
    data.Add ("cat", category);
    
    Hashtable time = new Hashtable ();
    time.Add (".sv", "timestamp");
    data.Add ("time", time);
    
    HTTP.Request theRequest = new HTTP.Request( "post", "https://yourApp.firebaseio.com/scores.json", data );
    theRequest.Send (( request ) => {
      Hashtable jsonObj = (Hashtable)JSON.JsonDecode(request.response.Text);
      if(jsonObj == null) 
      {
        Debug.LogError("server returned null or malformed response ):");
      }
      TopScores.SetPlayer ((string)jsonObj["name"], name, score, category);
      Application.LoadLevel(8); //fill with the correct ID for the next scene
    });
  }
}

Now create your Top10 scene. Start with a canvas and create an easy to use structure such as this:

Project structure (Unity)

On the screenshot, success, death and failure are category images that will be activated based on the returned content.

Attach a script to your Canvas with the following content:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using System;

public class TopScores : MonoBehaviour {

  //this is set by EndGame and used to compare if the player is in the Top10 returned by the server, or otherwise add it as the 11th result
  public static Score player;

  public static void SetPlayer(string id, string name, int score, string category) 
  {
    player = new Score ();
    player.id = id;
    player.name = name;
    player.score = score;
    player.category = category;
    player.time = new DateTime ();
  }

  //Have a button point to this to exit
  public void Quit () 
  {
    Application.Quit ();
  }

  //you'll need to set this in unity as the parent 'Scores' object shown above
  public GameObject scorePanel;
  protected List<Score> scores = new List<Score>();
  protected void Start()
  {
    DownloadScores ();
  }

  protected void DownloadScores()
  {

    HTTP.Request someRequest = new HTTP.Request( "get", "https://yourApp.firebaseio.com/scores.json?orderBy="score"&limitToLast=10" );
    someRequest.Send( ( request ) => {
      Hashtable decoded = (Hashtable)JSON.JsonDecode(request.response.Text);
      if(decoded == null) 
      {
        Debug.LogError("server returned null or     malformed response ):");
        return;
      }

      foreach (DictionaryEntry json in decoded)
      {
        Hashtable jsonObj = (Hashtable)json.Value;
        Score s = new Score();
        s.id = (string)json.Key;
        s.name = (string)jsonObj["name"];
        s.score = (int)jsonObj["score"];
        s.category = (string)jsonObj["cat"];
        //gotta convert it!
        DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
        s.time = dtDateTime.AddMilliseconds( (long)jsonObj["time"] ).ToLocalTime();

        scores.Add(s);
      }
      AddScores();
    });
  }

  protected void AddScores()
  {
    //no dupes
    if (!scores.Contains (player)) 
    {
      scores.Add (player);
    }
    //firebase usually returns sorted content, but that's not guaranteed by the JSON format
    scores.Sort ((Score x, Score y) => { return x.score.CompareTo(y.score);});
            
    //we'll be filling top to bottom
    scores.Reverse();

    int i = 0;
    foreach(Score s in scores)
    {
      if(i>10)
      {
        break;
      }
      Transform panel = scorePanel.transform.Find("Panel " + i);
      panel.Find("score").GetComponent<Text>().text = s.score + "";
      panel.Find(s.category).gameObject.SetActive(true);
      panel.Find("name").GetComponent<Text>().text = s.name;
      if(!s.Equals(player)) 
      {
        //the player might not come from the server, so 'time' will be null
        panel.Find("time").GetComponent<Text>().text = s.time.ToString("yyyy-MM-dd");
      }
      i++;
    }
  }
  
  public class Score
  {
    public string id;
    public string name;
    public int score;
    public string category;
    public DateTime time;

    public override bool Equals(System.Object obj)
    {
      if (obj == null)
      {
        return false;
      }
      Score s = obj as Score;
      if ((System.Object)s == null)
      {
        return false;
      }
      return id == s.id;
    }
  }
}

And… done! Now you have a Top10 screen such as the one on the header of this post, from the game SCRUM’ed!