How to Add a Score System in Unity

6 min readMar 21, 2023

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 by real-time global scoreboards will have longer play times and bring players back to the game to try and obtain better scores.

Online Unity Games Need Online Features

Unity games that are connected online need social features such as in-game chat for players to communicate with players and teammates, friendly competitions with live leaderboard updates and scores, and notifications to update players on missed messages, in-game events, and news about the game. While developers can set up this online infrastructure themselves, online features take time, money, and resources to create, secure, maintain, and update constantly. The resources used in this endeavor could be used instead to add more features to the game itself.

PubNub and Unity Game streaming abilities

This is where PubNub, a developer API platform that enables applications to receive real-time updates at a massive, global scale, can handle this online functionality infrastructure. PubNub serves as the foundation for over 2000 customers in diverse industries, including gaming. Game developers can depend on PubNub’s scalability and reliability to power their games and tools for in-game chat, live leaderboard updates, and alerts and notifications to bring players back to the game.

In this Unity tutorial, you'll learn how to create leaderboards that update in real time when a new score is submitted using PubNub. You'll be able to enter usernames and scores as the score values and watch them update from other players in real time. With the PubNub Unity SDK, you’re able to create real-time games and experiences with ease. Although this tutorial guides you through the process step-by-step, you can watch a video walkthrough of this tutorial on YouTube, as well as view the finished project in the GitHub repository.

Note: This post was written using an older version of Unity (2018 LTS version) and PubNub's Unity SDK.

Creating a Unity Leaderboard Environment

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

Once you’ve downloaded Unity Hub, add any Unity Editor Version equal to or older than 2018 LTS, as the 2018 LTS version was used to create the real-time leaderboard demo application.

Unity Gaming leaderboard system 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. This will be handled in the next step.

Fixing the ‘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 receive 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 Scenes’ 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.

PubNub Keys & Functions Setup for Unity Leaderboard systems

Before you can see the leaderboard demo running properly, 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 and give your app a name.

  4. Click Create.

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

  6. Enable Message Persistence. This is required to remember your scores as well as to sort them in real time.

  7. Save the changes.

  8. Copy the Publish and Subscribe keys to a text editor for the next steps.

Next, you need to set up a Function to sort scores and publish the list to the ‘leaderboard_scores’ channel. To make a Function, navigate back to your application in the PubNub Admin Dashboard.

  1. On the left-hand side, click the Functions button.

  2. Create a new Module.

  3. Create a new Function by giving the function a name and ensure the Event selected is After Publish or Fire. Make sure you are listening to the correct channel name, in this case, the score submission channel is “submit_score”.

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

Function for Unity Leaderboard

You will use db.get to check if there is any data stored in the KV store. If there isn’t, db.set is used to create a string format of how the data will be structured. Once the data is in the KV store, iterate through the point values array using value.score.some(item => {});. Use the prototype for the score variables, since you 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 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();
};

Build & Run

Now that you have your API keys and set up the Function in the Admin Portal, open scripts/leaderboard.cs in the project. 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 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 Function, it runs the pubnub.SubscribeCallback and iterates through the dictionary, replacing any data that has changed since the last update.

Each player's UUID is set to a random number. However, when creating your game, you need 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 that is entered in the input fields as the parameters and publishes that data to the channel ‘submit_score’ that triggers the Function, which then updates all subscribers of the ‘leaderboard_scores’ channel.

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!");
    }

}

When you run the project, a PubNub Fire message is sent to the Function that was created earlier. When there are no scores stored in the KV Store, it will instantiate every unfilled entry as “unset”. In the screenshot below, some entries have been added to the leaderboard KV Store by typing in a username and score.

Unity KV Store

Add another player score 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 current 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 update in real time. The sort order is all done in the Function implemented earlier using the KV Store and array sorting.

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

What's Next after adding a score system in unity

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. Remember, you can view the video walkthrough of this tutorial on YouTube, as well as view the finished project in the GitHub repository.

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

  • Explore PubNub's Unity Developer Path, a guided journey in how you can use PubNub's Unity SDK to build real-time games with ease.

  • Play our Unity demo PubNub Prix, a racing game where you can chat with other players in and view leaderboard updates in real time.

  • Learn how to add PubNub to your Unity game in our How-to guide.

  • Learn how to develop your own scoring system and leaderboard for games in our gaming how-to guides.

  • Follow our step-by-step tutorial to set up and build the Unity game PubNub Prix.

  • Dive into our documentation for the PubNub Unity SDK, to learn how to customize PubNub's features that uniquely fit your application.

Have suggestions or questions about the content of this post? Reach out to devrel@pubnub.com.

More from PubNub

How to Create a Dating App: 7 Steps to Fit Any Design
Insights6 minMar 15, 2023

How to Create a Dating App: 7 Steps to Fit Any Design

There are common underlying technologies for a dating app, and in this post, we’ll talk about the major technologies and designs...

Michael Carroll

Michael Carroll

How to Create a Real-time Public Transportation Schedule App
Build6 minMar 14, 2023

How to Create a Real-time Public Transportation Schedule App

How to use geohashing, JavaScript, Google Maps API, and BART API to build a real-time public transit schedule app.

Michael Carroll

Michael Carroll

How to Create Real-Time Vehicle Location Tracking App
Build2 minMar 9, 2023

How to Create Real-Time Vehicle Location Tracking App

How to track and stream real-time vehicle location on a live-updating map using EON, JavaScript, and the Mapbox API.

Michael Carroll

Michael Carroll

Talk to an expert