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")
local function loadData(player: 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
local key = tostring(player.UserId)
local hasData, data = pcall(DataStore.GetAsync, DataStore, key)
if hasData then
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)
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)
Players.PlayerRemoving:Connect(saveData)
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")
local function loadData(player: 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
local key = tostring(player.UserId)
local hasData, data = pcall(DataStore.GetAsync, DataStore, key)
if hasData then
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)
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()
return dataToSave
end)
until success
end
end
end
Players.PlayerAdded:Connect(loadData)
Players.PlayerRemoving:Connect(saveData)
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")
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)
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
local key = tostring(player.UserId)
waitForRequestBudget(Enum.DataStoreRequestType.GetAsync)
local hasData, data = pcall(DataStore.GetAsync, DataStore, key)
if hasData then
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?)
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()
return dataToSave
end)
until success
end
end
end
for _, player in ipairs(Players:GetPlayers()) do
coroutine.wrap(loadData)(player)
end
Players.PlayerAdded:Connect(loadData)
Players.PlayerRemoving:Connect(saveData)
game:BindToClose(function()
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!