Gaming

How to Add a Scoreboard and Leaderboard to Your Unity Game

How to Add a Scoreboard and Leaderboard to Your Unity Game

Scoreboards and leaderboards are key aspects in promoting player engagement. Scoreboards provide vital information while players are engaged in an intense match and are necessary to determine the best chance of winning, while leaderboards are used as a way to encourage competition amongst players to view high scores after matches. Both encourage replayability and are an easy way to help player stickiness.

While scoreboards and leaderboards are vital in game development to promote healthy competition and provide vital feedback during a game, incorporating these features in your Unity project is easier said than done. To start from scratch, it takes a lot of resources to build, maintain, and scale when your players increase, as you’ll need to communicate scoreboard updates and store the updated leaderboard scores when they occur. Luckily, PubNub has made it easier than ever to inject real-time functionalities into Unity games with our real-time, low-latency API platform. We take care of the infrastructure layer of your apps so that you can focus on your application. Whether you're developing for Windows, Mac, iOS, Android, Virtual Reality systems such as Oculus and Meta Quest, or going cross-platform, our Unity SDK has you covered.

If you would like to see an example of how to implement a scoreboard and leaderboard in a Unity game to use as a reference when following along with this guide, be sure to check out our Unity Showcase Game.

Continue reading to learn step by step how to add a scoreboard and leaderboard to your Unity game that updates in real time, starting from understanding how to configure the PubNub GameObject, learning about PubNub Functions, and how to parse the PubNub messages to suit your game’s needs.

Getting Started with PubNub

Before you begin to understand how to set up a scoreboard and leaderboard for your Unity Game, you’ll need to understand PubNub and how to configure your application to take advantage of the platform’s features.

Overview

PubNub is based on the Pub/Sub (Publish/Subscribe) model. A user will publish a message, which is essentially a payload that contains all relevant information, to the PubNub network. Users who want to receive or listen to the message and other generated events will subscribe to the PubNub network and parse the message. Event listeners are used to catch messages and events generated in the PubNub Network and trigger based on an action taken place.

To ensure the message gets to the right recipients, channels are used as the mechanism through which the data is transmitted from one device to another. Channels are required each time a device wants to publish and subscribe to the PubNub network. While a user can only publish one message at a time, a user can subscribe to many different channels at a time.

Install and Configure the PubNub Unity SDK

To begin, you'll need to install any PubNub dependencies and configure the PubNub Unity SDK to connect your application to the PubNub network. This guide already assumes you have the Unity Game Engine, Unity Assets, and code editors such as Visual Studio installed. Please refer to the Unity SDK documentation for full details, but as an overview, you will need to:

  1. Add the Unity package through the Package Manager.
  2. Create a free PubNub account and obtain your PubNub Keys. You’ll need to enable the features on your keyset that your application needs. For this guide, you’ll need to enable Stream Controller with the default settings. The other important feature, Functions, will be enabled automatically once you initialize the Function, which will be discussed later in this guide.
  3. Create a new Unity Project or open your existing game and provide Unity with the publish and subscribe keys you obtained in the previous step to configure the PubNub GameObject in the Unity Editor. You should also provide a UserId, as every PubNub object requires a unique identifier to establish a connection to PubNub. Once you have done so, initialize the PubNub object:
1 2 3 4 using PubnubApi; using PubnubApi.Unity; … PubNub pubnub = new Pubnub(pnConfiguration);
  1. Add an event listener for your game to react to messages, where your back end will essentially receive notifications about when you receive new messages. There are different event listeners to be able to implement custom logic to respond to each type of message or event, but for this guide, you’ll simply need the Message Event Listener:
1 2 3 4 5 6 7 8 var listener = new SubscribeCallbackListener(); pubnub.AddListener(listener); listener.onMessage += OnPnMessage; … private void OnPnMessage(Pubnub pn, PNMessageResult<object> result) { Debug.Log($"Message received: {result.Message}"); //Handle the message based on channel name, User ID, etc. }

Vital Information During Matches: Scoreboards

Scoreboards or scoring systems are used in games as a way to provide feedback to a player during a game. Scoreboards are essential to provide valuable, engaging, and rewarding feedback that encourages an enjoyable experience and replayability to improve scores. Check out our scoring systems guide on the different types of scoreboards you can implement into your Unity project.

To update the scoreboard during a match is essentially the same as sending chat messages but at a much higher rate: you construct a payload consisting of the necessary information and publish this message for all subscribers to receive, parse, and update their scoreboards whenever new information occurs. If you are using PubNub to power other multiplayer features such as multiplayer synchronization, it’s recommended to bundle this information together, as the number of messages you need to send will be enormous and can help reduce the number of transactions you need to make. However, this guide will simply focus on the PubNub features necessary to send and receive messages.

To begin implementing your scoreboard, set up a channel pattern so only those who are in the match receive the updates. Depending on your architecture for multiplayer synchronization, you should have some sort of Room ID or Match ID that uniquely identifies a current match with players. Use this ID when constructing your channel:

1 public string scoreboardUpdates = $“scoreboard.” + {RoomID};

The channel uses string interpolation to combine the scoreboard channel and the current match’s identifier. Subscribe to this channel to be able to listen for channel updates:

1 2 3 4 5 6 pubnub.Subscribe<string>() .Channels(new string[] { // subscribe to channels scoreboardUpdates }) .Execute();

Once you have updates to send, such as when you get a kill or your damage score increases, publish the update by constructing a message (in case you also want to send more information than just a few scoreboard updates):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 Dictionary<string, object> leaderboardUpdates= new Dictionary<string, object>(); leaderboardUpdates.Add("userId", user-a-uuid); leaderboardUpdates.Add("kills", 0); leaderboardUpdates.Add("deaths", 0); leaderboardUpdates.Add("assists", 0); leaderboardUpdates.Add("total_damage", 0); … private async void PublishMessage(string text, string channel) { await PNManager.pubnubInstance.pubnub.Publish() .Channel(scoreboardUpdates) .Message(text) .ExecuteAsync(); }

The other players would receive the new updates via the Message Event Listener we constructed earlier. You would then parse the message and update their UI accordingly. This would continue until the game ends, in which case you would make one final call to update the global leaderboards.

High-Score Rankings: Leaderboards

Leaderboards are critical for games to increase competition among players and are displayed after matches are done or on the home screen in a separate menu. Players want to be able to see how well they perform compared to other players, guilds, and clans, and see how they can improve their playstyle to achieve higher scores. If you would like to learn more about the different types of leaderboards you can implement in your Unity game, feel free to check out our leaderboards guide.

Before we begin to implement an online leaderboard, we need to talk about how we can not only update the leaderboard once matches have finished but also store the leaderboard information without needing to spin up your server.

Functions allows developers to create and execute business logic on the edge to route, filter, transform, augment, and aggregate real-time messages as they route through the PubNub network. You can host this business logic without needing to spin up your server, where you can integrate with one of our trusted third-party integrations (which are already set up to work with Functions and include instructions on getting started) or write your code to perform custom logic before or after sending it back to players. You can even store relevant information using the KV store module, which is a persistent key value store that acts as a database for your Functions. You can learn more about Functions in detail by diving into our documentation.

For leaderboards, you want to update the scores after players have finished a match and then notify the players of the updated leaderboard.

To be able to use Functions, you’ll need to perform some more setup in the Admin Portal:

  1. Navigate to the Admin Portal.
  2. Click on the Functions tab on the left-hand side of the portal.
  3. Select the App and keyset you created in the previous section.
  4. Click on Create New Module.
  5. Give the module a name.
  6. Enter a description of what the Module is doing.
  7. Select the keyset you created earlier to add the Module. Click Create.
  8. Give the Function a name.
  9. Select the After Publish or Fire event type.
  10. Enter the channel name you wish the Function to update after a message is published (this can be adjusted later as well). You’ll be using a channel name such as score.* consisting of the Wildcard Subscribe character * the feature. You can learn more about how Wildcard Subscribe works for chat messages in the following guide.
  11. Click the Create button for each Function.

You will be brought to the Function overview page, where you can change settings, test, and even monitor the Function when it is interacting with your game for each Function. In the middle of the screen, there should be automatically generated JavaScript code. This is a sample "Hello World" function to showcase an example of how a function would work and where you can either host your code or execute the business logic of third-party integration.

For leaderboards, you’ll write some custom logic and make use of the KV Store Module to save the leaderboard information. In the code section, you’ll need to consider what your leaderboard information will contain. Will you have multiple leaderboards detailing different information, a large leaderboard that contains multiple statistics that enables you to create different leaderboards in your game?

Once you’ve determined what information you would like to store, you’ll need to set up the message payload that will be published in your game to the PubNub Network and then caught by the Function.

For example, in our Unity Showcase Game, we send the name of each player in the match and their score, which is a combination of their kills/deaths/assists when they finish a match. The message is formatted in JSON and published to the channel score.leaderboard that the Function is listening for (based on the Wildcard Subscribe). Take a look specifically at the UIManager.cs file:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 … // Get the kills and deaths value of all players and store them in an INT array: int[] kills = new int[playersSorted.Length]; int[] deaths = new int[playersSorted.Length]; int[] otherScore = new int[playersSorted.Length]; for (int i = 0; i < playersSorted.Length; i++) { kills[i] = playersSorted[i].Kills; deaths[i] = playersSorted[i].Deaths; otherScore[i] = playersSorted[i].OtherScore; if (notPublished) { if (playersSorted[i].PlayerName == Connector.PNNickName) { MyClass mc = new MyClass(); mc.username = Connector.PNNickName; mc.score = playersSorted[i].Kills.ToString(); string json = JsonUtility.ToJson(mc); PublishMessage(json, _leaderboardChannelPub); } notPublished = false; } } … private async void PublishMessage(string text, string channel) { await PNManager.pubnubInstance.pubnub.Publish() .Channel(channel) .Message(text) .ExecuteAsync(); }

The Function that is listening on this channel will then be triggered and you can then sort, update the new scores in the KV store module, and update the players of the new leaderboard updates. For the Unity Showcase Game, the following logic in the Function does exactly that:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 //This function takes a string from a unity game that contains either a username AND and a score, OR a refresh message. //The function then looks at the message and creates a user/score json and sends it back. Putting the highest score in 0 and the lowest score in [9] //If the score submitted is lower than [9] then trhe messages succeeds without intervention //sending a refresh will trigger this function without any intervention. export default (request) => { const db = require("kvstore"); const pubnub = require("pubnub"); //uncomment if you want to see the raw message //console.log(request.message); //The format of the message sent is "{\"username\":\"Bob\",\"score\":\"10\",\"refresh\":\"\"}" and as such we need to parse it //You wont be able to use the test payload until you remove the parse // var json = request.message; //uncomment this and comment the line below to be able to use the test payload (as well as debug console) var json = JSON.parse(request.message); let { username, score } = json; //create some arrays to ultimately be able to position the leaderboard correctly - there's more elegant ways to do this, this function is designed to explain var scorearrayprevious = []; var scorearraynew = []; var usernamearraynew = []; var usernamearrayprevious = []; //db.removeItem("data"); //uncomment this code if you need to wipe the database -- for future you could always send a message to trigger this, but that is out of the scope for this workshop db.get("data").then((value) => { if(value){ console.log("value", value); //uncomment this if you want to see the value let i = 0; //we use some and score > item to parse through where the submitted score will sit, if the score is greater than the item we're on, then it get's slotted in to the array at that spot value.score.some(item => { if(parseFloat(score) > parseFloat(item) || (parseFloat(item) == 0 && score.length > 0)){ //Parse into float 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; //store the db.set("data", value); return true; //break out of the loop using Array.prototype.some by returning true } i++; }); //publish the message to a *new* or *different* channel pubnub.publish({ "channel": "leaderboard_scores", "message": value }).then((publishResponse) => { console.log("publish response", publishResponse); }); } else { //Initial Data, used only on the first call db.set("data", { "username":["---","---","---","---","---","---","---","---","---","---"], "score":["0","0","0","0","0","0","0","0","0","0"]}); } }); return request.ok(); };

The Function then publishes the leaderboard updates to all players via the leaderboard_scores channel. Back in your application, you need to subscribe to this channel via the Subscribe call:

1 2 3 4 5 6 pubnub.Subscribe<string>() .Channels(new string[] { // subscribe to channels "leaderboard_scores” }) .Execute();

Since you are subscribed to the leaderboard_scores channel, you can receive the updated leaderboard scores from the Function once it has finished via the Message Event Listener. You can then parse the message and update your leaderboard UI accordingly. For instance, to update the Unity Showcase Game UI leaderboard:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 private void OnPnMessage(PNMessageResult<object> result) { // Enable the button once we have established connection to PubNub if (result.Channel.Equals(PubNubUtilities.chanLeaderboardSub)) { Dictionary<string, object> msg = JsonConvert.DeserializeObject<Dictionary<string, object>>(result.Message.ToString());// as Dictionary<string, object>; var usernames = (msg["username"] as Newtonsoft.Json.Linq.JArray).ToObject<string[]>(); var scores = (msg["score"] as Newtonsoft.Json.Linq.JArray).ToObject<string[]>(); if (usernames[0] != null) { namePos1.text = usernames[0]; kdPos1.text = scores[0]; } if (usernames[1] != null) { namePos2.text = usernames[1]; kdPos2.text = scores[1]; } if (usernames[2] != null) { namePos3.text = usernames[2]; kdPos3.text = scores[2]; } if (usernames[3] != null) { namePos4.text = usernames[3]; kdPos4.text = scores[3]; } if (usernames[4] != null) { namePos5.text = usernames[4]; kdPos5.text = scores[4]; } } }

You can take a look at the Leaderboard.cs file for more details about the leaderboard implementation for the Unity Showcase Game.

What’s Next

In this how-to guide, you’ve learned how to add a leaderboard and scoreboard to your Unity game. To add a scoreboard, you can use PubNub’s low-latency platform to send and receive messages containing relevant information during a match, such as kills, deaths, and assists to update players in real time. For leaderboards, Functions can host code to update player scores and update leaderboards for players to view once their match has finished.

Game devs can learn more with the following resources:

Feel free to reach out to the Developer Relations Team at devrel@pubnub.com for any questions or concerns.