9 min read
.
on Mar 1, 2021
In this tutorial, learn how to easily build real-time leaderboards and achievement streaming for multiplayer games using PubNub and Unity.

Leaderboards are an essential component for multiplayer games because leaderboards invoke more competition. Competition builds with comparison and gamers want to see how they stack up against other players. Leaderboards improve engagement because gamers are motivated to improve their scores and will keep playing your game until they do. Players motivated with real-time global scoreboards will have longer play times: It’s game on!

Want to skip to the action? Try the Real-Time Unity Leaderboard you’ll build here.

In a previous post we covered how real-time player communication is essential for an engaging gaming experience. Consider how you should use PubNub powered leaderboards to improve your player engagement alongside other social features built with PubNub, such as chat. When players chat with their friends and other gamers they are more likely to keep returning to your game. Combining chat with leaderboards adds more player interaction and further motivates players to stick around to spend more time playing your game with their friends. 

Unity plus PubNub: The easy way to build real-time leaderboards

A potential downside to adding new leaderboards and social gaming features is building the infrastructure behind it. However, there’s an easier option that’s perfect for game developers. With PubNub’s Unity SDK you only need to write front-end code, then PubNub manages and scales the infrastructure for you. You’re able to use PubNub to power your entire game, including all player actions, multiplayer chat, and leaderboards. If you use PubNub to power all of your real-time actions then your entire game can be built without a backend. 

In this tutorial, we’ll cover leaderboards that update in real time when a new score is submitted using PubNub. Real-time scorekeeping is important since it gives users instant feedback which increases gamer satisfaction. Scores can be transmitted in 100ms or less anywhere in the world with PubNub. With the PubNub Unity SDK, you’re able to create real-time games and experiences with ease. 

Talk to our experts

Unity leaderboard environment setup

Before you begin, you’ll need to download the Unity Hub. The Unity Hub is used to manage Unity versions and Unity projects you’re developing. 

Once you’ve downloaded Unity Hub, add any Unity Version equal to or older than 2018 LTS. The 2018 LTS version was used to create the real-time leaderboard demo app, but upgrade it if you would prefer. 

Gaming leaderboard project setup

Download or clone the Real-Time Unity High Score Leaderboards repo. Open Unity Hub, go to the projects tap, and click ‘Add’. 

If you’re using a Unity version newer than 2018 LTS then you’ll be asked to confirm you want to upgrade the project to a newer version of Unity. Click ‘Confirm’ to continue.

When you import the project you may get error messages in the Unity Console. We’ll take care of that in the next step.

‘TestTools does not exist’ errors in Unity

After importing the PubNub SDK or the tutorial project, you might see an error message in the Unity console window that looks like this:

error CS0234: The type or namespace name 'TestTools' does not exist…

Fix the error by going to Window > General > Test Runner. Click on the drop-down menu in the top right corner and enable playmode tests for all assemblies. Close all open Unity windows completely and reopen your project.

Unity leaderboard error message

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:

Unity LeaderBoard scene

To try out the demo, click the 'Play' button at the top of the Unity Editor.

If you get the following error:

Scene ‘LeaderBoard’ couldn’t be loaded because it has not been added to the build settings or the AssetBundle has not been loaded.

Follow the steps below:

To fix this, go to File > Build Settings. Click the ‘Add Open Scene’s’ button. Then go back to your Project tab, and double-click on the ‘LeaderBoard’ scene. 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 Unity 3D leaderboard

When you run the project, a PubNub Fire message is sent to the PubNub Function. We’ll cover how this works in detail below in this post. When there are no scores stored in the KV Store, it will fill out every unfilled entry with “unset”. In the screenshot below we’ve already added some entries into our leaderboard KV Store by typing in a username and score.

Unity KV Store

Let’s add another entry, Ted - 501, to the leaderboard. To do this type ‘Ted’ in the username input field and ‘501’ in the score input field. Then click ‘Publish Score’.

Unity Ted - 501

Since Ted’s score is lower than Jj’s score and higher than Billy’s score, it’s placed at spot 3 and Bob’s score is removed from the leaderboard. All players that are connected to the game will see this score updated in real time. The sort order is all done in a PubNub Function using the KV Store and array sorting. Open the demo in multiple tabs to see this in action. Next, we’ll cover how to set up the leaderboard for your own project and configure PubNub.

Unity 3D Leaderboard walkthrough and setup

Before you begin you’ll need free API keys from PubNub to continue. To get these keys:

  1. Sign up for a PubNub account

  2. Go to your PubNub Dashboard.

  3. Click Create New App.

  4. Give your app a name, and select Other Messaging Use Case as the app type.

  5. Click Create.

  6. Click your new app to open its settings, then click its keyset.

  7. Enable the Storage and Playback feature for your keyset. This is required to remember your scores as well as to sort them in real-time.

  8. Enable the PubNub Functions feature for your keyset. This is required to sort scores in real time.

  9. Save the changes.

  10. Copy the Publish and Subscribe keys for the next steps.

Now that you have API keys, open the file scripts/leaderboard.cs. Replace the publish and subscribe key with your keys.

The code below is the entire leaderboard.cs script. 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 PubNub 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.

We set each player's UUID to a random number. However, when creating your game you would want to associate this value to a unique user ID so that players can’t have the same usernames in your real-time leaderboard.

The TaskOnClick function takes the information you input in the input fields and publishes that data to the channel ‘submit_score’ that triggers the PubNub Function which then updates all clients currently subscribed to ‘leaderboard_scores’.

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 refresh;
}

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;
    //public Object[] tiles = {}
    // 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-PUBLISH-KEY-HERE";
        pnConfiguration.SubscribeKey = "YOUR-SUBSCRIBE-KEY-HERE";

        pnConfiguration.LogVerbosity = PNLogVerbosity.BODY;
        pnConfiguration.UUID = Random.Range (0f, 999999f).ToString ();

        pubnub = new PubNub(pnConfiguration);
        Debug.Log (pnConfiguration.UUID);
        
        MyClass fireRefreshObject = new MyClass();
        fireRefreshObject.refresh = "new user refresh"; 
        string firerefreshobject = JsonUtility.ToJson(fireRefreshObject);
        pubnub.Fire() // This will trigger the leaderboard to refresh so it will display for a new user. 
            .Channel("submit_score")
            .Message(firerefreshobject)
            .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.SubscribeCallback += (sender, e) => {
        SubscribeEventEventArgs mea = e as SubscribeEventEventArgs;
            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, SubscribeCallback in presence" + mea.PresenceEventResult.Channel + mea.PresenceEventResult.Occupancy + mea.PresenceEventResult.Event);
            }
        };
        pubnub.Subscribe ()
            .Channels (new List<string> () {
                "leaderboard_scores"
            })
            .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("submit_score")
            .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!");
    }

}

Now we have to set up a PubNub Function to sort scores and publish the list to the ‘leaderboard_scores’ channel. 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, the score submission channel is “submit_score”.

In the function, we have to import the KV Store and PubNub dependencies. Next, we parse the message JSON received from the Unity leaderboard client and place the contents of the message into variables called username and score.

PubNub Function for Unity Leaderboard

Now 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, iterate through the array using value.score.some(item => {});. 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 ‘leaderboard_scores’ by sending a pubnub.publish message. This is the complete PubNub Function script:

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 scorearrayprevious = [];
    var scorearraynew = [];
    var usernamearraynew = [];
    var usernamearrayprevious = [];

   // db.removeItem("data"); //reset the block
    db.get("data").then((value) => {
        if(value){
            console.log("value", value);
            let i = 0;
            value.score.some(item => {
                console.log("hello", item, score);
                if(parseInt(item) < parseInt(score)){ //Parse into int since variables are currently strings
                    //Score
                    scorearraynew = value.score.slice(0, i);
                    scorearrayprevious = value.score.slice(i, value.score.length);
                    console.log("values", scorearraynew, scorearrayprevious);
                    scorearraynew.push(score);
                    var newScoreList = scorearraynew.concat(scorearrayprevious);
                    newScoreList.splice(-1,1);
                    
                    //Username
                    usernamearrayprevious = value.username.slice(0, i);
                    usernamearraynew = value.username.slice(i, value.score.length);
                    console.log("values", usernamearrayprevious, usernamearraynew);
                    usernamearrayprevious.push(username);
                    var newUsername = usernamearrayprevious.concat(usernamearraynew);
                    newUsername.splice(-1,1);
                    
                    value.score = newScoreList;
                    value.username = newUsername;

                    db.set("data", value);
                    
                    return true; //break out of the loop using Array.prototype.some by returning true
               }
                i++;
            });
            pubnub.publish({
                "channel": "leaderboard_scores",
                "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();
};

Now you have a working, real-time Unity leaderboard for your game! 

This Blog is part of our Developer Path for Unity - Discover, Explore, Build, and expand your unity knowledge all in one place.

More resources for expanding your Unity game

Take advantage of PubNub services to easily build real-time interactions with enterprise-grade security, scalability, and reliability. PubNub eliminates the time it takes to set up and manage your infrastructure so you can focus on game development. 

Interested in doing more with your game and PubNub? Check out these resources:

Have suggestions or questions about the content of this post? Talk to one of our experts.

Free up to 1MM msgs/ month
Customize fully-featured, in-app experiences—chat, notifications, geolocation, IoT devices, collaboration, and more.
Try for free
Free up to 1MM msgs/ month

More From PubNub