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.
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 OrderedDataStore
s 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!