IN THIS ARTICLE
Why Leaderboards?
Leaderboards are one of the oldest social features in video games that are used to increase the level of competition amongst players. Global scoreboards generate longer gameplay times and appeal to the majority of player segments. In addition, leaderboards help increase player engagement and retention by motivating them to achieve a higher rank. If a player slips down in the ranks, they will usually try to re-assert their position at the top.
You can build leaderboards that update in realtime by using PubNub. Realtime scorekeeping is important since it gives users instant feedback which increases gamer satisfaction.
Setting Up Unity
To start out, check out my GitHub and clone/download the repo. If you don’t already have Unity installed, install it here. The repo contains an Assets folder which contains all of the Project Files. Open up Unity, and go to File -> Open Project and select the folder. It will take some time to load the project in Unity. When it completes, open up the Assets folder in your Project tab, and double-click on the LeaderBoard Unity Scene.
When you click on the LeaderBoard scene, you should see this:
To try out the demo, click the Play button at the top.
If you get the following error, follow the steps below:
Scene ‘LeaderBoard’ couldn’t be loaded because it has not been added to the build settings or the AssetBundle has not been loaded.
To fix this, go to File -> Build Settings. Click the Add Open Scenes button. Then go back to your Project tab, and double-click on the LeaderBoard, go back to your Build Settings, and click Add Open Scenes once again. Then in your Project Tab, double-click on the LeaderBoard scene again and run the project.
Running the Project
Now that your project is all set up, click the Run button. When you run the project, a PubNub Fire message is sent to the PubNub Function.As you can see in the screenshot below, when there are no scores stored in the KV Store, it will fill out every unfilled entry with “unset”. Below I’m going to add some more entries to the KV store by typing in a username and score.
Let’s add more entries. Lara, James, and Bob just got added to the high score list below.
Now let’s add Stephen to the list. As you can see, since his score is higher than Bob’s but lower than James, he is placed in the 5th spot. All players that are connected to the game will see this score update in realtime. This logic is all done in the PubNub Function using the KV Store and array sorting. Click the try our demo button below to check out how it works.
Open up two windows to see the high scores updating in real time
How Does it Work?
Creating a leaderboard can be a complicated task since you want all users to see the same information as new scores are added to the list. Using PubNub and PubNub Functions makes creating custom leaderboards for your application seamless.
In the main folder, I created a script called leaderBoard which handles all the sending and receiving of data. In the Start() function, we start out by defining some components and variables. The current demo is using my personal pub/sub keys. You will have to go in and replace the pub/sub keys to your own which you can find in the PubNub Admin Dashboard or create them by clicking the button below. Copy your pub/sub keys into the leaderBoard.cs code.
In the code above, we set each players UUID to a random number. However, if you were creating your game you would want to associate this number to a unique username so that way people can’t have the same usernames in your realtime leaderboard.
The code below is the entire leaderBoard.cs document. When the script runs for the first time, it initializes PubNub and sends a Fire message to the PubNub Function. This fire object tells the Function to send back the most recent data which is stored in the database (KV Store). When the client gets a response from the PubNub Function, it runs the pubnub.SubscribeCallback and iterates through the dictionary, replacing any data that has changed since the last update.
The TaskOnClick function takes the information you input in the input fields and publishes that data to the PubNub Function which then updates all clients currently subscribed to my_channel2.
using System.Collections; using System.Collections.Generic; using UnityEngine; using PubNubAPI; using UnityEngine.UI; using SimpleJSON; public class MyClass { public string username; public string score; public string test; } public class leaderBoard : MonoBehaviour { public static PubNub pubnub; public Text Line1; public Text Line2; public Text Line3; public Text Line4; public Text Line5; public Text Score1; public Text Score2; public Text Score3; public Text Score4; public Text Score5; public Button SubmitButton; public InputField FieldUsername; public InputField FieldScore; // Use this for initialization void Start () { Button btn = SubmitButton.GetComponent<Button>(); btn.onClick.AddListener(TaskOnClick); // Use this for initialization PNConfiguration pnConfiguration = new PNConfiguration (); pnConfiguration.PublishKey = "YOUR-KEY-HERE"; pnConfiguration.SubscribeKey = "YOUR-KEY-HERE"; pnConfiguration.LogVerbosity = PNLogVerbosity.BODY; pnConfiguration.UUID = Random.Range (0f, 999999f).ToString (); pubnub = new PubNub(pnConfiguration); Debug.Log (pnConfiguration.UUID); MyClass myFireObject = new MyClass(); myFireObject.test = "new user"; string fireobject = JsonUtility.ToJson(myFireObject); pubnub.Fire() .Channel("my_channel") .Message(fireobject) .Async((result, status) => { if(status.Error){ Debug.Log (status.Error); Debug.Log (status.ErrorData.Info); } else { Debug.Log (string.Format("Fire Timetoken: {0}", result.Timetoken)); } }); pubnub.SusbcribeCallback += (sender, e) => { SusbcribeEventEventArgs mea = e as SusbcribeEventEventArgs; if (mea.Status != null) { } if (mea.MessageResult != null) { Dictionary<string, object> msg = mea.MessageResult.Payload as Dictionary<string, object>; string[] strArr = msg["username"] as string[]; string[] strScores = msg["score"] as string[]; int usernamevar = 1; foreach (string username in strArr) { string usernameobject = "Line" + usernamevar; GameObject.Find(usernameobject).GetComponent<Text>().text = usernamevar.ToString() + ". " + username.ToString(); usernamevar++; Debug.Log(username); } int scorevar = 1; foreach (string score in strScores) { string scoreobject = "Score" + scorevar; GameObject.Find(scoreobject).GetComponent<Text>().text = "Score: " + score.ToString(); scorevar++; Debug.Log(score); } } if (mea.PresenceEventResult != null) { Debug.Log("In Example, SusbcribeCallback in presence" + mea.PresenceEventResult.Channel + mea.PresenceEventResult.Occupancy + mea.PresenceEventResult.Event); } }; pubnub.Subscribe () .Channels (new List<string> () { "my_channel2" }) .WithPresence() .Execute(); } void TaskOnClick() { var usernametext = FieldUsername.text;// this would be set somewhere else in the code var scoretext = FieldScore.text; MyClass myObject = new MyClass(); myObject.username = FieldUsername.text; myObject.score = FieldScore.text; string json = JsonUtility.ToJson(myObject); pubnub.Publish() .Channel("my_channel") .Message(json) .Async((result, status) => { if (!status.Error) { Debug.Log(string.Format("Publish Timetoken: {0}", result.Timetoken)); } else { Debug.Log(status.Error); Debug.Log(status.ErrorData.Info); } }); //Output this to console when the Button is clicked Debug.Log("You have clicked the button!"); } }
In order to make the leaderboards work, we now have to set up a PubNub Function. To make a Function, go to the PubNub Admin Dashboard, and click on your application. On the left-hand side, click the Functions button, and create a new Module. Make sure you are listening to the correct channel name, in this case, I called my channel “my_channel“.
In the function, we first have to import the KV Store and PubNub dependencies. Next we JSON parse the message received from the Unity client, and place the contents of the message into variables called username and score.
Next we use db.get to check if there is any data stored in the KV store. If there isn’t, we use db.set to create a string format of how the data will be structured. Once the data is in the KV store, we iterate through the array using value.score.some(item => {});. We use the prototype since we want to be able to return true at the end of the loop to cancel out of the loop once the entry has been correctly replaced. When the loop has been completed, it sends the new updated values to all clients subscribed to my_channel2 by sending a pubnub.publish message.
export default (request) => { const db = require("kvstore"); const pubnub = require("pubnub"); var json = JSON.parse(request.message); console.log(json); let { username, score } = json; //let { username, score } = request.message; var scorearray1 = []; var scorearray2 = []; var usernamearray1 = []; var usernamearray2 = []; //db.removeItem("data"); //reset the block db.get("data").then((value) => { if(value){ console.log("value", value); let i = 0; value.score.some(item => { if(parseInt(item) < parseInt(score)){ //Parse into int since variables are currently strings //Score scorearray2 = value.score.slice(0, i); scorearray1 = value.score.slice(i, value.score.length); console.log("values", scorearray2, scorearray1); scorearray2.push(score); var newList = scorearray2.concat(scorearray1); newList.splice(-1,1); //Username usernamearray2 = value.username.slice(0, i); usernamearray1 = value.username.slice(i, value.score.length); console.log("values", usernamearray2, usernamearray1); usernamearray2.push(username); var newList2 = usernamearray2.concat(usernamearray1); newList2.splice(-1,1); value.score = newList; value.username = newList2; db.set("data", value); return true; //break out of the loop using Array.prototype.some by returning true } i++; }); pubnub.publish({ "channel": "my_channel2", "message": value }).then((publishResponse) => { console.log("publish response", publishResponse); }); } else { db.set("data", { "username":["unset","unset","unset","unset","unset"], "score":["0","0","0","0","0"]}); } }); return request.ok(); };
That’s it, folks! It’s super easy to create a custom leaderboard using PubNub and Unity.
Debugging and Building
When you go to build your project, you may run into an error that says:
error CS0234: The type or namespace name 'TestTools' does not exist in the namespace 'UnityEngine'. Are you missing a reference? CS0246: The type or namespace name 'NUnit' could not be found. Are you missing an assembly reference?
To fix this problem, go to: Window -> Test Runner -> Select Play Mode
You will be asked to enable playmode. Then reset the editor.
Now when you go to build your project, those errors should be removed.
Wrapping Up
Setting up PubNub with Unity is straightforward and seamless. To download the Unity SDK, visit our Unity documentation. You can download my demo project on my GitHub here.