In-game online leaderboard options for the layman (Android, iOS.)

Hello everyone,

I am looking for a way to have an online leaderboard that perhaps doesn’t rely on any external service, like Google Play. I appreciate all the services G.P.S has to offer, but I only want a simple top 100 online leaderboad with some level of security that can appear in my app the way I want it to.

I’m targeting Android and eventualy iOS and I understand if something like the device’s keyboard needs to pop up when the user name is entered for registration. Beyond that, however, I want to use my own assets for menu windows, buttons and fonts.

I’m new to a lot of this (I don’t know how to use PHP(yet)) and it feels like I’m kinda reaching “beyond my depth.” I already spent weeks getting an asset from the store to work with G.P.S. only to realize it was not what I was looking for…

How can I handle leaderboard data online with a local interface and some level of security? My search results yield either tutorials on how to set up a local leaderboard, or they are too technical for me to follow. Furthermore, as far as I can tell, the selection in the asset store seems limited and kind of outdated.

If you know of any service or resource that can help me accomplish this in an easy-to-follow manner please let me know; Any help,direction or advice will be enormously appreciated! Thank you for your time.

Best regards,
Don

http://wiki.unity3d.com/index.php?title=Server_Side_Highscores

This is a serverside high scores script and a how-to for setting up the sql database and stuff.

You need web hosting of some kind that support PHP and SQL databases…

Good luck!

1 Like

There are many service providers for this, I recall seeing @Ryiah recommend something in a post a few weeks back.

I have toyed around with Dreamlo and gamejolt (only guest mode avail for non gamejolt members). This was easier than expected, it’s relatively straight forward, I guess for any of the platforms. I guess really secure is nothing, but for dreamlo I encrypted it and used a hash. Gamejolt and I would assume any of those more professional services come with public/private key encryption, so people would need to decompile to get the private key (which seems not too hard, but likely no one cares anyway :wink: )

1 Like

@MD_Reptile Thank you for clarifying those things for me;
Cheers!

@sngdan I’ll look into Dreamlo and this encryption using hash business. Not trying to build Fort Knox here, just looking for something like a fence hehe:) Thanks for the pointers!

Here some code to get you started…

  • if i recall correctly it is based on the sample code that came with dreamlo
  • i have removed variables and methods that were releated to local hi-score, input, display to focus on the dreamlo bits
  • i used a double encryption at the time “0123456ABCDEFG” the first 6 chars of a string would be interpreted as the score and they would run through an extra encryption to obfuscate a little as my general encryption would be easier to break without it

stripped down code

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

public class HighScoreManager_DreamLoVersion : MonoBehaviour
{
    private const string pssst = "THIS IS A TEST"; /// encryption key
    private string deviceID;
    private const string keycode = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_- "; /// important that numbers are first for special encryption as I dont wrap around, i.e. assume decryption never below 0
   
    public const string dreamloWebserviceURL = "http://dreamlo.com/lb/";
    public string privateCode = ""; // you get those from dreamlo
    public string publicCode = ""; // you get those from dreamlo
    private string highScores = "";
    private List<Score> globalHighScoreCache = new List<Score>();
    private float timeOfLastUpdate;
    private float updateInterval;
    private bool isFetchingData;
    private int globalScorePosition;

    public struct Score
    {
        public string playerName; // used to hold encrypted score + unique deviceID
        public int score; // used for unencrypted score
        public int seconds; // used for unencrypted max level reached or distance?
        public string shortText; // used for encrypted player name
        public string dateString; // read only?
    }
   
    // local variables --- removed

    void Start ()
    {
        //ClearLocalHighScores();
        StartCoroutine(Init());
    }

    IEnumerator Init()
    {
        // deviceID = SystemInfo.deviceUniqueIdentifier; // not working in WebGL
       
        if (PlayerPrefs.HasKey("GUID") && PlayerPrefs.GetString("GUID").Length > 20f)
        {
            deviceID = PlayerPrefs.GetString("GUID");
        } else
        {
            deviceID = Guid.NewGuid().ToString();
            PlayerPrefs.SetString("GUID", deviceID);
            PlayerPrefs.Save();
            Debug.Log("new: " + deviceID);
        }
       
        // Init Local Highscore - removed

        // Init Global Highscore
        SetupLayout();
        timeOfLastUpdate = Time.realtimeSinceStartup;
        updateInterval = 20f;
        isFetchingData = false;
        globalScorePosition = -1;

        yield return StartCoroutine (GetScores());
       
        DisplayGlobalHighScores();
    }

    public void SetupLayout()
    {
    }
   
    void Update ()
    {
        if (!isFetchingData && (timeOfLastUpdate + updateInterval < Time.realtimeSinceStartup))
        {
            LoadScores();
        }
    }

    public void ManageGUIInput ()
    {
    }
   
    IEnumerator PlayAnimation ()
    {
    }
   
    #region Local HighScore
    public void AddLocalHighScore (int score)
    {
    }

    public void DisplayLocalHighScore ()
    {
    }

    public void ClearLocalHighScores ()
    {
    }

    #endregion
 

    #region Global HighScore

    public void DisplayGlobalHighScores()
    {
    }

    public void AddGlobalHighScore (string playerName, int totalScore, int totalSeconds, string shortText)
    {
        globalScorePosition = -1;
        Score newScore = new Score();
        newScore.playerName = playerName;
        newScore.score = totalScore;
        newScore.seconds = totalSeconds;
        newScore.shortText = shortText;
        newScore.dateString = "";

        if (globalHighScoreCache != null )
        {
     // removed --- I kept a copy of the score in cache....
        }
        StartCoroutine(AddScoreWithPipe(this.Enrcypt(RemoveSpecialCharacters(playerName), pssst), totalScore, totalSeconds, shortText));
    }

    IEnumerator GetScores()
    {
        isFetchingData = true;
        highScores = "";
        WWW www = new WWW(dreamloWebserviceURL +  publicCode  + "/pipe" + "/"+LeaderboardLength);
        yield return www;
        highScores = www.text;
        timeOfLastUpdate = Time.realtimeSinceStartup;
       
        List<Score> temp = ToListHighToLow();
        for (int i = 0; i < temp.Count; i++)
        {
            Score s = new Score();
            s.playerName = this.Decrypt(temp[i].playerName, pssst);
            s.score = temp [i].score;
            s.seconds = temp [i].seconds;
            s.shortText = temp [i].shortText;
            s.dateString = temp [i].dateString;
            temp[i] = s;
        }

        globalHighScoreCache = temp;
        isFetchingData = false;
    }
   
    IEnumerator AddScoreWithPipe(string playerName, int totalScore, int totalSeconds, string shortText)
    {
        isFetchingData = true; //added
        playerName = Clean(playerName);
        shortText = Clean(shortText);
       
        WWW www = new WWW(dreamloWebserviceURL + privateCode + "/add-pipe/" + WWW.EscapeURL(playerName) + "/" + totalScore.ToString() + "/" + totalSeconds.ToString()+ "/" + shortText);
        yield return www;
        highScores = www.text;
        timeOfLastUpdate = Time.realtimeSinceStartup; //added

        List<Score> temp = ToListHighToLow();
        for (int i = 0; i < temp.Count; i++)
        {
            Score s = new Score();
            s.playerName = this.Decrypt(temp[i].playerName, pssst);
            s.score = temp [i].score;
            s.seconds = temp [i].seconds;
            s.shortText = temp [i].shortText;
            s.dateString = temp [i].dateString;
            temp[i] = s;
        }

        globalHighScoreCache = temp;

        //globalHighScoreCache = ToListHighToLow(); //added
        isFetchingData = false; //added
    }

    #endregion
   
    #region encryption
   
    // important that score is always exactly first 6 chars
    public string RemoveSpecialCharacters(string s)
    {
        StringBuilder sb = new StringBuilder();
        char c;
       
        for (int i = 0; i < s.Length; i++)
        {
            c = s[i];
            if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '_' || c == ' ')
            {
                sb.Append(c);
            }
           
        }

        return sb.ToString().ToUpper();
    }
   
    private string Enrcypt (string s, string key)
    {       
        StringBuilder sb = new StringBuilder();
 
        if (s.Length < 13)
        {
            Debug.LogError ("wrong string length");
            return "";
        }
       
        // special encryption for first 6 digits 
        StringBuilder temp = new StringBuilder(s);
        for (int i = 0; i < 6; i++)
        {
///removed
        }
        s = temp.ToString();
       
        // general encryption
        for(int i = 0; i < s.Length; i ++)
        {
            int x = ToKeyCode(s[i]);
            int y = ToKeyCode(key[i % key.Length]);
            int z = (x + y) % keycode.Length;
            char encypted = keycode[z];
            sb.Append(encypted);
        }
        return sb.ToString();
    }

    private string Decrypt (string s, string key)
    {
        StringBuilder sb = new StringBuilder();

        // general decryption
        for(int i = 0; i < s.Length; i ++)
        {           
            int x = ToKeyCode(s[i]);
            int y = ToKeyCode(key[i % key.Length]);
            int z = ((x-y) % keycode.Length < 0) ? ((x-y) % keycode.Length) + keycode.Length : (x-y) % keycode.Length;
            char decrypted = keycode[z];
            sb.Append(decrypted);
        }

        // reverse special encryption of score
        for (int i = 0; i < 6; i++)
        {
  ///removed
        }
        return sb.ToString();
    }
   
    private int ToKeyCode (char c)
    {
        return keycode.IndexOf(c);
    }

    #endregion

   
    #region dreamlo  ---- THIS UNCHANGED from the DREAMLO example
   
    string Clean(string s)
    {
        s = s.Replace("/", "");
        s = s.Replace("|", "");
        return s;
       
    }
   
    public void LoadScores()
    {
        StartCoroutine(GetScores());
    }
   
    public List<Score> ToListHighToLow()
    {
        Score[] scoreList = this.ToScoreArray();
       
        if (scoreList == null) return new List<Score>();
       
        List<Score> genericList = new List<Score>(scoreList);
       
        genericList.Sort((x, y) => y.score.CompareTo(x.score));
       
        return genericList;
    }
   
    public Score[] ToScoreArray()
    {
        string[] rows = ToStringArray();
        if (rows == null) return null;
       
        int rowcount = rows.Length;
       
        if (rowcount <= 0) return null;
       
        Score[] scoreList = new Score[rowcount];
       
        for (int i = 0; i < rowcount; i++)
        {
            string[] values = rows[i].Split(new char[] {'|'}, System.StringSplitOptions.None);
           
            Score current = new Score();
            current.playerName = values[0];
            current.score = 0;
            current.seconds = 0;
            current.shortText = "";
            current.dateString = "";
            if (values.Length > 1) current.score = CheckInt(values[1]);
            if (values.Length > 2) current.seconds = CheckInt(values[2]);
            if (values.Length > 3) current.shortText = values[3];
            if (values.Length > 4) current.dateString = values[4];
            scoreList[i] = current;
        }
       
        return scoreList;
    }
   
    public string[] ToStringArray()
    {
        if (this.highScores == null) return null;
        if (this.highScores == "") return null;
       
        string[] rows = this.highScores.Split(new char[] {'\n'}, System.StringSplitOptions.RemoveEmptyEntries);
        return rows;
    }
   
    int CheckInt(string s)
    {
        int x = 0;
       
        int.TryParse(s, out x);
        return x;
    }
   
    #endregion

}
1 Like

@sngdan very cool! This oughta clear some things up; Thank you!

Does anybody know why dreamlo is not working on google play?