There's much to go through and explain in this DataStore script that needs to be said since I've seen many individuals have been loading data this way. Thankfully my explanation follows a lot of Roblox's own article on the RobloxDev site.
I see a problem when needing more than one DataStore. DataStores give you many options like saving to different scopes but your case could be done an easier way.
DataStores become even easier to use when you understand that they can save tables. Even subtables within the table for some data organization. This is useful to know because you use Folders and IntValues to show the player the data they have. All we need to do is convert them to a table.
###We can start in the PlayerAdded event which is usually when you load player data:
local Players = game:GetService("Players")
local DSS = game:GetService("DataStoreService")
local LeaderboardStore = DSS:GetDataStore("Leaderboards")
Players.PlayerAdded:Connect(function(player)
local playerData
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local money = Instance.new("IntValue")
money.Name = "Money"
money.Parent = leaderstats
local totalcoffee = Instance.new("IntValue")
totalcoffee.Name = "Total Coffee"
totalcoffee.Parent = leaderstats
local key = player.UserId.."_data"
local success, _error = pcall(function()
LeaderboardStore:UpdateAsync(key, function(data)
playerData = data
end)
end)
if success then
if playerData then
money.Value =
totalcoffee.Value =
else
money.Value = 0
totalcoffee.Value = 0
end
else
warn("Data for player could not be loaded.")
end
end)
After creating the custom key for the player, UpdateAsync is used here to set our playerData variable to any previous data the player may have. This is also wrapped in a pcall() function to check if calling for the data fails for some reason.
Below that we use the first result of the pcall, success variable of type boolean, to check if the DataStore call had worked. If it didn't, then we could handle the player differently. If success was true then it means the DataStore did work but it doesn't really mean that the player had any data to start.
###Next we need to actually save data... but how?
Your original code does this by saving (By using :SetAsync()) everytime the IntValues change through the .Changed() event. This is NEVER the way you want to go about saving because you will exceed data store limits. You could read on that here.
Instead you should only save at important moments or use some interval. A point you should always save that I will touch on is when the player leaves through PlayerRemoving
First we need to convert our 'leaderstats' folder to a table that will serve as the data to be saved. We can do this with a new function with a generic for-loop:
local function requestPlayerData(player)
local data = {}
local folder = player.leaderstats
for _, object in pairs(folder:GetChildren()) do
if object:IsA("IntValue") then
data[object.Name] = object.Value
end
end
return data
end
We define a table that we will write our IntValues to using the Name as the key and Value as the value. We then return that table once it's done.
We then can continue with the PlayerRemoving:
Players.PlayerRemoving:Connect(function(player)
local key = player.UserId.."_data"
local dataToSave = requestPlayerData(player)
local success, _error = pcall(function()
LeaderboardStore:SetAsync(key, dataToSave)
end)
end)
When we call the requestPlayerData() function, we are returned with a proper table that the DataStore can use to save. Almost that simple.
We then bring all the code together to get a more secure DataStore system:
local Players = game:GetService("Players")
local DSS = game:GetService("DataStoreService")
local LeaderboardStore = DSS:GetDataStore("Leaderboards")
local function requestPlayerData(player)
local data = {}
local folder = player.leaderstats
for _, object in pairs(folder:GetChildren()) do
if object:IsA("IntValue") then
data[object.Name] = object.Value
end
end
return data
end
Players.PlayerAdded:Connect(function(player)
local playerData;
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local money = Instance.new("IntValue")
money.Name = "Money"
money.Parent = leaderstats
local totalcoffee = Instance.new("IntValue", leaderstats)
totalcoffee.Name = "Total Coffee"
totalcoffee.Parent = leaderstats
local key = player.UserId.."_Data"
local success, _error = pcall(function()
LeaderboardStore:UpdateAsync(key, function(data)
playerData = data
end)
end)
if success then
if playerData then
money.Value = playerData[money.Name]
totalcoffee.Value = playerData[totalcoffee.Name]
else
money.Value = 0
totalcoffee.Value = 0
end
else
warn("Data could not be loaded for "..player.Name)
end
end)
Players.PlayerRemoving:Connect(function(player)
local key = player.UserId.."_data"
local dataToSave = requestPlayerData(player)
local success, _error = pcall(function()
LeaderboardStore:SetAsync(key, dataToSave)
end)
end)
The last thing I had added to the above code are lines 44 and 45. If any data retrieved is found for the player then the playerData table can be indexed using the Names of our IntValues just like we had it set up in our requestPlayerData() function!
That wraps up my explanation in using tables to save and when not to save for DataStores. This might be pretty lengthy so do make sure to read the articles I had linked.
#####If I missed something or got something wrong please let me know.
4 comments
Is there even a need for lines 20 and 23?
Also, keep in mind that using .Changed might overload the system, and having a button to save is a MUCH better way.
Thanks!
If my answer had helped you then click on the 'Approve Answer' button below my answer to mark this question as solved.