I am creating a game myself using Profile Service. I wanted to implement global leaderboads into my game, but figured out it’s not so easy as we all expected. When searching the forum I found that I wasn’t the only person with this issue.
Why is it not easy?
When making a leaderboard system, most people use Roblox’ DataStoreService to save data. When creating a leaderboard they just call the Sorted Datastore. Since Profile Service does not support this, that is not an option. Even worse, the API does not have any function to fetch e.g. the best 100 players.
What’s an alternative?
I have discovered a good strategy to tackle this problem. So I will guide you into the right direction.
You will be using MessagingService and will create a SubscribeAsync() function. Then you will create a while loop, with a desired refresh rate. This loop will check how many Coins the lowest player on the board has, if the player has a higher amount, you will create a table with the playerName and how many Coins they have. Then you will PublishAsync() using the topic and Encoded table. In the SubscribeAsync() function you will decode the table. Then you can do the rest of the logic such as adding a tag for the player, calculating which position they actually have etc.
Now you may have noticed when a new server opens, it will put the best player on the board who’s actually playing right now. That is why we create a DataStore that will hold a sorted table with the best players, and update that table after every refresh. When a new server opens, we just call that DataStore and load the leaderboard with the best players.
I would recommend setting a refresh rate between 60 and 300 seconds. To make sure the DataStore does not get overloaded.
If you had the same problem, I hope this post helped you.
local datastoreservice = game:GetService("DataStoreService")
local datastore = datastoreservice:GetDataStore("Test")
local OrderedStore = datastoreservice:GetOrderedDataStore("Ordered")
local LeaderBoard = workspace.LeaderBoard
local Names = LeaderBoard.SurfaceGui.Frame.Names:GetChildren()
local Ranks = LeaderBoard.SurfaceGui.Frame.Ranks:GetChildren()
local Values = LeaderBoard.SurfaceGui.Frame.Values:GetChildren()
local function RemoveTableValues()
table.remove(Names, 1)
table.remove(Ranks, 1)
table.remove(Values, 1)
end
local function CopyData(Key, data)
OrderedStore:SetAsync(Key.KeyName, data)
end
local function AssignRanks(index, KeyInstance)
if Ranks[index] and Ranks[index].ClassName == "TextLabel" then
Ranks[index].Text = index
end
if Names[index] and Names[index].ClassName == "TextLabel" then
Names[index].Text = KeyInstance.key
end
if Values[index] and Values[index].ClassName == "TextLabel" then
Values[index].Text = KeyInstance.value
end
end
RemoveTableValues()
while true do
local Pages = datastore:ListKeysAsync()
local Page = Pages:GetCurrentPage()
for Every, Key in Page do
local success, data = pcall(function()
return datastore:GetAsync(Key.KeyName)
end)
if data and success then
pcall(CopyData(Key, data))
end
end
local OrderedPages = OrderedStore:GetSortedAsync(false, 100)
local OrderedPage = OrderedPages:GetCurrentPage()
for index, KeyInstance in OrderedPage do
AssignRanks(index, KeyInstance)
end
task.wait(60)
end
Profileservice uses datastoreservice, so when you make a profilestore you’re making a datastore. Just switch the datastore name to your profilestore’s name.
I have 3 different leaderboards and I want each one to resemble a stat. Is there a way to expand the current script to fit all 3 leaderboards on, or is it easier to just make 3 different scripts, 1 for each leaderboard?
I use Profile Service and Ordered Data Stores.
When a player leaves, using Profile Service, if they value I want indexed in the ordered data store has changed, I update the ODS at that point.
So I essentially maintain two data stores. The primary one with Profile Service, and a separate Ordered Data Store.
Yeah, you can put all 3 leaderboards in a folder. And then loop through them.
Also sidenote, don’t copy the data over into a different datastore like I did in my script. I figured this out the hard way. It led to throttling issues with the datastore and the data took so long to appear on the leaderboards.
I was a scripter with about 6-7 months experience back then so I wasn’t too strong on datastores and so I didn’t realise copying the data over would cause issues.
The problem is let’s say you have 100-200 keys naturally because you’ve had 100-200 people join your game in its entire lifetime. SetAsync/GetAsync calls have a limit per minute. And this is based on how many concurrent users you have. So if you only have 1 ccu but 100-200 keys of data you will reach the limit very quickly and the datastore will throttle.
The solution I found was to simply use the name of your profilestore with the method Datastoreservice:GetOrderedDataStore(“YourProfileStoreName”). Since the profileservice module still uses datastoreservice under the hood.
You’ll have to modify profileservice’s module to where it uses the method :GetOrderedDataStore instead of :GetDataStore("). I haven’t used profileservice in so long since i’ve already implemented the features the module provides. I don’t use any modules for datastores anymore I just write the code myself. just gives me alot more control.