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.
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 )
@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!
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
}