How to make a simple global leaderboard

im really confused right now, but my scripts to enforce the leaderboard dont work correctly, for example the script the saves the daily (minute) value saves but is supposed to be wiped out by a new key when the player joins when a new mminute begins, however it isnt working and keeps the old save
leaderboard script

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
local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"].." D:"..dateTable["day"].." H:"..dateTable["hour"].." I:"..dateTable["min"]

local dataStoreService = game:GetService("DataStoreService")
--The data store service
local dataStore = dataStoreService:GetOrderedDataStore(key)
--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 --PointsService
			local w = plr.Value.Value --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

while true do
	local newdateTable = os.date("*t", os.time())
	print(newdateTable["min"])
	if newdateTable["min"] ~= dateTable["min"] then
		key = "Y:"..newdateTable["year"].." M:"..newdateTable["month"].." D:"..newdateTable["day"].." H:"..newdateTable["hour"].." I:"..newdateTable["min"]
		min = newdateTable["min"]
		for i,v in game.Players:GetChildren() do
			v.Value.Value = 0
		end
		print("lower than value")
		print(dateTable["min"])
	else
	end
	wait(1)
end

save script

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"].." D:"..dateTable["day"].." H:"..dateTable["hour"].." I:"..dateTable["min"]
print(key)
print(dateTable["min"])
game.Players.PlayerAdded:Connect(function(player)
	local value = Instance.new("IntValue")
	value.Name = 'Value'
	value.Parent = player
	local stats = game.DataStoreService:GetDataStore(key)
	local data = stats:GetAsync(player.UserId)
	if data then
		value.Value = data.Tests
	end
	value.Value = value.Value + 1
end)

game.Players.PlayerRemoving:Connect(function(player)
	local stats = game.DataStoreService:GetDataStore(key)
	local data = stats:SetAsync(player.UserId, {
		["Tests"] = player.Value.Value
	})
end)

while true do
	local newdateTable = os.date("*t", os.time())
	key = "Y:"..newdateTable["year"].." M:"..newdateTable["month"].." D:"..newdateTable["day"].." H:"..newdateTable["hour"].." I:"..newdateTable["min"]
	wait(1)
end

The dateTable, which is basically the main data of the script, is pre defined and is always changed by a loop that compares it to the current time, and changes it if it is different in terms of the type of time that changed, which in my case was a minute, however it does not seem to work on the save script even though i think it set a new key which would be the new time

1 Like

I have the rest of the script done myself but wanted to see how to make weekly leaderboards. I ended up here and used your method. However, I was wondering if making a new datastore every week will create needless amounts of data stores that never get deleted?

1 Like

On Roblox, this isn’t as much of a problem. It would take more computational power to remove all of your entries then it would to just use a new datastore.

As a Roblox Developer, you don’t have to worry about database usage as long as it’s within your quotas.

2 Likes

Ok, thanks! this really helped me on my quest for a weekly leaderboard!

2 Likes

Something that might be more applicable than DataStores for that use case would be Memory Stores because data in Memory Stores expire after a duration of time that you set (currently, the limit is up to 30 days max according to the announcement thread for the feature).

3 Likes

The problem with using MemoryStore expiration is it is relative to the time the data is saved, not since a certain point.

MemoryStores could be used for a leaderboard that needs to be updated constantly, but your quota is much better used in other features then leaderboards, in my opinion.

1 Like

That’s true – in this use case then, from what I understand, the primary benefit of Memory Stores would moreover be the reduction in data you’d have to manually clear as a result of GDPR Data Erasure Requests because none of the data would be persistent so it’d be removed anyway.


Since there currently isn’t a built-in way to allow Roblox to automatically purge any info contained in DataStores upon receiving a data erasure request, it would probably save a lot of time that would have been spent clearing the UserId and associated data from each key (especially for games where multiple leaderboards are “reset” dozens or hundreds of times per year).

But there are / will probably be ways that would make that easier in the future so I guess it’d be dependent on how each game prefers to approach leaderboards & if they already use Memory Stores for other features in their game.


I’m not as knowledgeable about these yet so I appreciate the insight :+1:

3 Likes

It appears you haven’t set the SortOrder of the UIListLayout to LayoutOrder.

3 Likes

How would i make it so, if the old value is smaller than the new one then update?

By doing that check and then returning it. You’d also want to reverse the sort order (set smallestFirst to true).

Hello, this works but does it work globally or only in one server? I saved some values in a data store to my userid and when my friend tested it too it didn’t show?

This should work globally. There is a delay in between requests, so make sure you wait at least a minute before deciding it doesn’t work. Otherwise, it might be your specific set up that is broken, in which case make sure you followed the guide properly.

Quick question, say if a player is 9 hours ahead of another player in USA California (the time the os.time gives I believe).

It is 3PM, January 1st, 2020 for that player in California. Would the player 9 hours ahead of the California player see a different leaderboard? (since it’s 12AM, January 2nd, 2020 and a new day for that player)

Since we use UTC time, timezones are irrelevant. It’s not a good idea to try and adjust to timezones, as you’ll need to keep track of a lot more data.

If you want to make it easier for players to understand, you can always add a “resets in” text somewhere which counts down to the end of the day (using UTC time).

yeah that’s smart! Actually, like you said, I’ve seen “time in UTC” and “resets in hh mm ss” before (like in ZO and a donate game). That’ll be enough to let the players know.

Also, if you do resets, you should also have an update counter which tells you how long before new data will be retrieved.

There’s a lot of little things that can be added - every little feature makes your game feel more polished.

1 Like

Hey, i need a little help. Send me the source code because it got deleted the orignal source code of the leaderboard

The source code is still there. Please make sure you read the notice about PlayerPoints at the top, though.

oh okay, i actually understanded. Im working on a private Project, and i was searching a global leaderstats for my own game.