Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
COMMUNITY QUESTION

How do i make it so the leaderboard saves when you leave? Other tutorials have not worked for me.

This is my leaderboard code:

game.Players.PlayerAdded:Connect(function(player) local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player

local clicks = Instance.new("IntValue")
clicks.Name = "Clicks"
clicks.Value = 0
clicks.Parent = leaderstats

local rebirths = Instance.new("IntValue")
rebirths.Name = "Rebirths"
rebirths.Value = 0
rebirths.Parent = leaderstats

end)

I have tried many other tutorials on youtube and I cannot figure it out.

1 answer

You should use DataStoreService in which you can create/get a data store by using DataStoreService:GetDataStore() with the name of the data store. You can get data by using DataStore:GetAsync() and save data by using either DataStore:SetAsync(), DataStore:UpdateAsync(), or DataStore:IncrementAsync() (only in integers).

Before using Data Stores, make sure API Services are enabled in your game settings or follow this instead.

To detect if a player left, just use Player.PlayerRemoving event.

So your script should look like this:

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("GameDataStore") -- you can name it whatever you want, although renaming could lead to reset of data

local function loadData(player: Player) -- if a player joined
	-- Preparing the values
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
	
	local clicks = Instance.new("IntValue")
	clicks.Name = "Clicks"
	clicks.Value = 0
	clicks.Parent = leaderstats
	
	local rebirths = Instance.new("IntValue")
	rebirths.Name = "Rebirths"
	rebirths.Value = 0
	rebirths.Parent = leaderstats
	
	-- Loading the data
	local key = tostring(player.UserId)
	local hasData, data = pcall(DataStore.GetAsync, DataStore, key) -- gets the player's data from the data store
	
	if hasData then -- if the player has data
		clicks.Value = data.Clicks
		rebirths.Value = data.Value
	else
		warn("Player " .. key .. "'s data failed to load or has no data.")
	end
end

local function saveData(player: Player) -- if a player left
	local leaderstats = player:FindFirstChild("leaderstats")
	
	if leaderstats and leaderstats:IsA("Folder") then
		local clicks = leaderstats:FindFirstChild("Clicks")
		local rebirths = leaderstats:FindFirstChild("Rebirths")
		
		if (clicks and clicks:IsA("IntValue")) and (rebirths and rebirths:IsA("IntValue")) then
			local key = tostring(player.UserId)
			local dataToSave = {
				["Clicks"] = clicks.Value,
				["Rebirths"] = rebirths.Value,
			}
			
			local success, err = pcall(DataStore.SetAsync, DataStore, key, dataToSave)
			
			if success then
				print("Data successfully saved!")
			else
				warn(err)
				print("Saving data failed!")
			end
		end
	end
end

Players.PlayerAdded:Connect(loadData) -- if a player joined
Players.PlayerRemoving:Connect(saveData) -- if a player left 

Our basic data store is done, but it's still not safe. There's a small chance of returning errors, so if you want your data to be safer, you can continue reading.

If we just use DataStore:SetAsync() once, there's a chance it could error. If it errors, we can retry the function by repeating pcall-ing a function until success is true. So it should look like this:

local success
repeat
	success, err = pcall(DataStore.SetAsync, DataStore, key, dataToSave)
until success

We won't do the same to DataStore:GetAsync() because it would yield forever if a player is new to the game because it has no data, making success false.

Next is using UpdateAsync instead of SetAsync. UpdateAsync is faster than SetAsync, however it is not recommended if you're using OrderedDataStores which are mostly used for leaderboards.

The script should look like this:

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("GameData") -- you can name it whatever you want, although renaming could lead to reset of data

local function loadData(player: Player) -- if a player joined
	-- Preparing the values
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
	
	local clicks = Instance.new("IntValue")
	clicks.Name = "Clicks"
	clicks.Value = 0
	clicks.Parent = leaderstats
	
	local rebirths = Instance.new("IntValue")
	rebirths.Name = "Rebirths"
	rebirths.Value = 0
	rebirths.Parent = leaderstats
	
	-- Loading the data
	local key = tostring(player.UserId)
	local hasData, data = pcall(DataStore.GetAsync, DataStore, key) -- gets the player's data from the data store
	
	if hasData then -- if the player has data
		clicks.Value = data.Clicks
		rebirths.Value = data.Value
	else
		warn("Player " .. key .. "'s data failed to load or has no data.")
	end
end

local function saveData(player: Player) -- if a player left
	local leaderstats = player:FindFirstChild("leaderstats")
	
	if leaderstats and leaderstats:IsA("Folder") then
		local clicks = leaderstats:FindFirstChild("Clicks")
		local rebirths = leaderstats:FindFirstChild("Rebirths")
		
		if (clicks and clicks:IsA("IntValue")) and (rebirths and rebirths:IsA("IntValue")) then
			local key = tostring(player.UserId)
			local dataToSave = {
				["Clicks"] = clicks.Value,
				["Rebirths"] = rebirths.Value,
			}
			
			local success
			repeat
				success = pcall(DataStore.UpdateAsync, DataStore, key, function() -- saves the data
					return dataToSave
				end)
			until success
		end
	end
end

Players.PlayerAdded:Connect(loadData) -- if a player joined
Players.PlayerRemoving:Connect(saveData) -- if a player left

BUT IT'S STILL NOT SAFE!!

I can't explain the rest because there's a 10k words limit :( But don't worry there's a link at the very bottom which explains everything!

So the final script should look like this:

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("GameData") -- you can name it whatever you want, although renaming could lead to reset of data

local function waitForRequestBudget(requestType: Enum.DataStoreRequestType)
	local currentBudget = DataStoreService:GetRequestBudgetForRequestType(requestType)
	while currentBudget < 1 do
		currentBudget = DataStoreService:GetRequestBudgetForRequestType(requestType)
		task.wait(5)
	end
end

local function loadData(player: Player) -- if a player joined
	-- Preparing the values
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
	
	local clicks = Instance.new("IntValue")
	clicks.Name = "Clicks"
	clicks.Value = 0
	clicks.Parent = leaderstats
	
	local rebirths = Instance.new("IntValue")
	rebirths.Name = "Rebirths"
	rebirths.Value = 0
	rebirths.Parent = leaderstats
	
	-- Loading the data
	local key = tostring(player.UserId)
	
	waitForRequestBudget(Enum.DataStoreRequestType.GetAsync)
	local hasData, data = pcall(DataStore.GetAsync, DataStore, key) -- gets the player's data from the data store
	
	if hasData then -- if the player has data
		clicks.Value = data.Clicks
		rebirths.Value = data.Value
	else
		warn("Player " .. key .. "'s data failed to load or has no data.")
	end
end

local function saveData(player: Player, dontWait: boolean?) -- if a player left
	local leaderstats = player:FindFirstChild("leaderstats")
	
	if leaderstats and leaderstats:IsA("Folder") then
		local clicks = leaderstats:FindFirstChild("Clicks")
		local rebirths = leaderstats:FindFirstChild("Rebirths")
		
		if (clicks and clicks:IsA("IntValue")) and (rebirths and rebirths:IsA("IntValue")) then
			local key = tostring(player.UserId)
			local dataToSave = {
				["Clicks"] = clicks.Value,
				["Rebirths"] = rebirths.Value,
			}
			
			local success
			repeat
				if not dontWait then
					waitForRequestBudget(Enum.DataStoreRequestType.UpdateAsync)
				end
				success = pcall(DataStore.UpdateAsync, DataStore, key, function() -- saves the data
					return dataToSave
				end)
			until success
		end
	end
end

for _, player in ipairs(Players:GetPlayers()) do
	coroutine.wrap(loadData)(player) -- incase PlayerAdded event didn't work, we simply load everyone's data
end

Players.PlayerAdded:Connect(loadData) -- if a player joined
Players.PlayerRemoving:Connect(saveData) -- if a player left
game:BindToClose(function() -- if the game is being shutdowned
	if RunService:IsStudio() then
		task.wait(2)
	else
		local finished = Instance.new("BindableEvent")
		local allPlayers = Players:GetPlayers()
		local leftPlayers = #allPlayers
		
		for _, player in ipairs(allPlayers) do
			coroutine.wrap(function()
				saveData(player, true)
				leftPlayers -= 1
				if leftPlayers == 0 then
					finished:Fire()
				end
			end)()
		end
		
		finished.Event:Wait()
	end
end)

This tutorial came from this DevForum post so credits to him!

This discussion is preserved as it was. New answers, comments, and votes are closed.