Edit: May 3rd, 2023
Roblox has recently announced their plan to remove support for PointsService on June 30th. Please be advised that this tutorial has not been updated to remove PointsService yet. It’s on my to do list. In the mean time, see the FAQ “Why only pointsService? How can I use it without pointsService?”
Global leaderboards are great for adding competition to a game. Players want to become the best of them all, and will work hard to compete to become the best. If you make your leaderboard look nice, players will do anything to be better than the rest.
Taken from my game, The Tower Tycoon
Just look how hard people worked just to have a number and their name displayed on a virtual board.
Before You Begin
For this tutorial, it is recommended you know the basics of manipulating parts in studio and making a surface gui .
This tutorial is based off of pointsService for simplicity sake. This can be changed. For more info, check the FAQ at the bottom once you have read through. For now, don’t worry about it.
I don't want to read the tutorial, just give me the leaderboard
Don’t want to make the physical leaderboard and just want to code it? Skip to the coding section (has download for basic leaderboard without script). Don’t want to do that either? Download for the whole thing is at the end. (FAQ is available there too)
Let’s Begin!
With that out of the way, welcome. Today I will be showing you how to make a nice global leaderboard to let your players compete to become the best.
Surface GUI Designing
-
The first step to making a global leaderboard is to make the physical part it will be on. It can be any size, and you can add cylinders to create a rounded leaderboard, change colors, add transparency, and more. For this tutorial however, we will use a simple 6x9x1 part. It is important to make the leaderboard look nice and appealing, but this can be done later. For now, let’s keep it simple.
-
The second step is we want to make a Surface GUI to hold our leaderboard. You can do this by clicking the + icon on the part, and typing in Surface GUI.
-
Now that we have a Surface GUI, we want to add a scrolling frame to hold the important stuff. Name it “ScrollingFrame”, so we can reference it later. Set the size to give a bit of space at the top, as we want to have a header at the top. Style it to your liking, such as color, ect. I recommend setting scrollBarThickness to 20.
-
Add a UIListLayout to the Scrolling Frame. Name it “UI”, and set
SortOrder
toLayoutOrder
. -
There’s one more thing we need to do before we get into the code; We need a frame to display the information. Make a sample frame such as the below one (Called “Sample”), and have imageLabel named “Image”, and 3 textLabels named “Place”, “PName”, and “Value”. Parent “Place” to the Image, as it will make it easier to scale (Scale & Pos = {0.5, 0},{0.5, 0}).
-
We now have a Surface GUI that’s ready to be scripted! Add a script (not Local Script) to the surface gui. Parent the frame “Sample”.
It’s Coding Time!
Time to code! At this point, you should have a leaderboard like this: Leaderboard Without Script.rbxl (21.7 KB).
We need to define all of our variables first, so we can easily reference items. (You can make your variable names longer, but I prefer shorter names. If you can remember them, use them.)
local sg = script.Parent --Surface GUI
local sample = script:WaitForChild("Sample") --Our Sample frame
local sf = sg:WaitForChild("ScrollingFrame") --The scrolling frame
local ui = sf:WaitForChild("UI") --The UI list layout
Nest, we need to choose the name for our global Ordered Data Store
and set it to a variable
. This will allow us to use Roblox to sort the data for us, without having to worry about thousands of entries to sort through. Note: Changing the key will change where roblox fetches our data from. This is useful for having multiple leaderboards, or just resetting data.
local dataStoreService = game:GetService("DataStoreService")
--The data store service
local dataStore = dataStoreService:GetOrderedDataStore("Leaderboard")
--Get the data store with key "Leaderboard"
--Want to make different leaderboards?
--Change this key to a different string ("Leaderboard2", ect.), and you can have multiple leaderboards!
I want daily leaderboards!
Not all people want all-time leaderboards. To acheive daily leaderboards, simply create a unique key for each day using os.date()
local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"].." D:"..dateTable["day"]
--Unique key for each day
Now, start a loop to update data and fetch data.
wait(5)
while true do
--will add wait(120) at the end
--loop that runs every 2 minutes
Then, we need to update the data. We loops through all the players, and update their data. In this tutorial, we use player points as the data to store (How To Award Player Points). However, you can easily reroute this to feature and value by defining w as something else.
for i,plr in pairs(game.Players:GetChildren()) do--Loop through players
if plr.UserId>0 then--Prevent errors
local ps = game:GetService("PointsService")--PointsService
local w = ps:GetGamePointBalance(plr.UserId)--Get point balance
if w then
pcall(function()
--Wrap in a pcall so if Roblox is down, it won't error and break.
dataStore:UpdateAsync(plr.UserId,function(oldVal)
--Set new value
return tonumber(w)
end)
end)
end
end
end
Now, we need to fetch our data. We can do this by using our dataStore we defined earlier.
local smallestFirst = false--false = 2 before 1, true = 1 before 2
local numberToShow = 100--Any number between 1-100, how many will be shown
local minValue = 1--Any numbers lower than this will be excluded
local maxValue = 10e30--(10^30), any numbers higher than this will be excluded
local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue)
--Get data
local top = pages:GetCurrentPage()--Get the first page
Yay! We now have our data, nice and organized with it’s key (userid), and value (points). But wait, we don’t have the player’s image or username! We need to fetch those before displaying our data.
local data = {}--Store new data
for a,b in ipairs(top) do--Loop through data
local userid = b.key--User id
local points = b.value--Points
local username = "[Failed To Load]"--If it fails, we let them know
local s,e = pcall(function()
username = game.Players:GetNameFromUserIdAsync(userid)--Get username
end)
if not s then--Something went wrong
warn("Error getting name for "..userid..". Error: "..e)
end
local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
--Make a image of them
table.insert(data,{username,points,image})--Put new data in new table
end
Our data is all organized and tidy! Now, we want to take this tidy data and display it! (username, points, image)
ui.Parent = script
sf:ClearAllChildren()--Remove old frames
ui.Parent = sf
for number,d in pairs(data) do--Loop through our new data
local name = d[1]
local val = d[2]
local image = d[3]
local color = Color3.new(1,1,1)--Default color
if number == 1 then
color = Color3.new(1,1,0)--1st place color
elseif number == 2 then
color = Color3.new(0.9,0.9,0.9)--2nd place color
elseif number == 3 then
color = Color3.fromRGB(166, 112, 0)--3rd place color
end
local new = sample:Clone()--Make a clone of the sample frame
new.Name = name--Set name for better recognition and debugging
new.LayoutOrder = number--UIListLayout uses this to sort in the correct order
new.Image.Image = image--Set the image
new.Image.Place.Text = number--Set the place
new.Image.Place.TextColor3 = color--Set the place color (Gold = 1st)
new.PName.Text = name--Set the username
new.Value.Text = val--Set the amount of points
new.Value.TextColor3 = color--Set the place color (Gold = 1st)
new.PName.TextColor3 = color--Set the place color (Gold = 1st)
new.Parent = sf--Parent to scrolling frame
end
wait()
sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)
--Give enough room for the frames to sit in
wait(120)
end--End the while loop
Our full code masterpiece!
local sample = script:WaitForChild("Sample") --Our Sample frame
local sf = sg:WaitForChild("ScrollingFrame") --The scrolling frame
local ui = sf:WaitForChild("UI") --The UI list layout
local dataStoreService = game:GetService("DataStoreService")
--The data store service
local dataStore = dataStoreService:GetOrderedDataStore("Leaderboard")
--Get the data store with key "Leaderboard"
wait(10)
while true do
for i,plr in pairs(game.Players:GetChildren()) do--Loop through players
if plr.UserId>0 then--Prevent errors
local ps = game:GetService("PointsService")--PointsService
local w = ps:GetGamePointBalance(plr.UserId)--Get point balance
if w then
pcall(function()
--Wrap in a pcall so if Roblox is down, it won't error and break.
dataStore:UpdateAsync(plr.UserId,function(oldVal)
--Set new value
return tonumber(w)
end)
end)
end
end
end
local smallestFirst = false--false = 2 before 1, true = 1 before 2
local numberToShow = 100--Any number between 1-100, how many will be shown
local minValue = 1--Any numbers lower than this will be excluded
local maxValue = 10e30--(10^30), any numbers higher than this will be excluded
local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue)
--Get data
local top = pages:GetCurrentPage()--Get the first page
local data = {}--Store new data
for a,b in ipairs(top) do--Loop through data
local userid = b.key--User id
local points = b.value--Points
local username = "[Failed To Load]"--If it fails, we let them know
local s,e = pcall(function()
username = game.Players:GetNameFromUserIdAsync(userid)--Get username
end)
if not s then--Something went wrong
warn("Error getting name for "..userid..". Error: "..e)
end
local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
--Make a image of them
table.insert(data,{username,points,image})--Put new data in new table
end
ui.Parent = script
sf:ClearAllChildren()--Remove old frames
ui.Parent = sf
for number,d in pairs(data) do--Loop through our new data
local name = d[1]
local val = d[2]
local image = d[3]
local color = Color3.new(1,1,1)--Default color
if number == 1 then
color = Color3.new(1,1,0)--1st place color
elseif number == 2 then
color = Color3.new(0.9,0.9,0.9)--2nd place color
elseif number == 3 then
color = Color3.fromRGB(166, 112, 0)--3rd place color
end
local new = sample:Clone()--Make a clone of the sample frame
new.Name = name--Set name for better recognition and debugging
new.LayoutOrder = number--UIListLayout uses this to sort in the correct order
new.Image.Image = image--Set the image
new.Image.Place.Text = number--Set the place
new.Image.Place.TextColor3 = color--Set the place color (Gold = 1st)
new.PName.Text = name--Set the username
new.Value.Text = val--Set the amount of points
new.Value.TextColor3 = color--Set the place color (Gold = 1st)
new.PName.TextColor3 = color--Set the place color (Gold = 1st)
new.Parent = sf--Parent to scrolling frame
end
wait()
sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)
--Give enough room for the frames to sit in
wait(120)
end
I want the top bar from the first photo!
For those wanting a top bar, you can duplicate the sample frame and put it outside of the scrolling frame. Remember, you can mess with colors and text, and make a really cool leaderboard.
FAQ (Frequently Asked Questions)
How do I make multiple leaderboards?
Duplicate the leaderboard, and then refer to above to set a different scope:
How do I make a daily, monthly, or yearly leaderboard?
To make leaderboards “Reset” we can simply change where we save and get the data from. os.time() gives us the number of seconds since the unix equinox. os.date(time) convertes those seconds into a date.
You will need to store each leaderboard in a separate key.
To acheive daily leaderboards, simply create a unique key for each day using os.date()
local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"].." D:"..dateTable["day"]
--Unique key for each day
To acheive monthly leaderboards, simply create a unique key for each month using os.date()
local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"]
--Unique key for each month
To acheive yearly leaderboards, simply create a unique key for each year using os.date()
local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"]
--Unique key for each year
How do I "Reset" the data, not just the data fetched, for every day / month / year?
Why only pointsService? How can I use it without pointsService?
I used pointsService in this tutorial for simplicity sake. If you want to pull the value from somewhere, you can easily reroute the value by defining w as something else.
In the example below, we set w to the player’s cash leaderstat. plr is the player, so reroute w however you like.
for i,plr in pairs(game.Players:GetChildren()) do--Loop through players
if plr.UserId>0 then--Prevent errors
local w = plr.leaderstats.Cash.Value--Get value
if w then
pcall(function()
--Wrap in a pcall so if Roblox is down, it won't error and break.
dataStore:UpdateAsync(plr.UserId,function(oldVal)
--Set new value
return tonumber(w)
end)
end)
end
end
end
I can't scroll on my leaderboard! How do I fix it?
If you put a part inside of the leaderboard, it might prevent you from scrolling. To prevent this, move the part that has the Surface GUI FORWARD. This way, the scrolling frame is over the part, which originally was blocking the scrolling frame.
Thanks for reading!
Thanks for reading! (and hopefully following along, thought it’s totally fine if you just wanted the leaderboard) If there is anything you are confused about, or think I missed, let me know. Feel free to PM me, or reply below. I can help solve your problem!
Downloads
Here all all the respective downloads for each of the stages. Feel free to use them as you please.
Download Notice
The most up to date code is above, though they should all be up to date. If you find any that are not, please notify me. Thanks!
Model without code: Leaderboard Without Script.rbxl (21.7 KB).
Full Model: Global Leaderboard Explained - Roblox
Downloadable Full Model: Global Leaderboard With Script.rbxm (12.5 KB)
Un-copylocked Working DEMO: Leaderboard with script - Roblox
Downloadable Working DEMO: Leaderboard With Script Working DEMO.rbxl (24.7 KB)