Trying to save and load multiple folders when joining the game

Hey everyone, I’m brand new to DataStores and would like a little extra guidance from those who are familiar.

I am trying to save, and then when the player rejoins, load a number of folders that look like this.

image

I was able to get everything to save on my own, but couldn’t get it to load, so I scrapped it in hopes of trying a different strategy, but I’m just stumped this time.

I looked around on the forums and found people trying to accomplish something similar, but was never able to find anyone in the exact same boat. Any ideas?

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local PlayerPages = DataStoreService:GetDataStore("A")
local ShopData = require(game.ReplicatedStorage.GameMaterials.Modules.ShopData)



function PlayerAdded(p)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	
	local playerSkills = Instance.new("Folder")
	playerSkills.Name = "playerskills"
	
	local shopItems = Instance.new("Folder")
	shopItems.Name = "shopItems"
	
	local equippedItems = Instance.new("Folder")
	equippedItems.Name = "equippeditems"
	
	local pages = Instance.new("IntValue")
	pages.Name = "Pages"
	pages.Parent = leaderstats
	
	local passive1 = Instance.new("StringValue")
	passive1.Name = "Passive1"
	passive1.Parent = playerSkills
	
	local passive2 = Instance.new("StringValue")
	passive2.Name = "Passive2"
	passive2.Parent = playerSkills
	
	local regenRate = Instance.new("NumberValue")
	regenRate.Name = "regenRate"
	regenRate.Value = 0.3
	regenRate.Parent = playerSkills
	
	local storedID = Instance.new("StringValue")
	storedID.Name = "storedid"
	storedID.Parent = playerSkills
	
	local storedCost = Instance.new("NumberValue")
	storedCost.Name = "storedcost"
	storedCost.Parent = playerSkills
	
	for i, v in pairs(ShopData) do
		print(v.ItemID)
		local owned = Instance.new("BoolValue")
		owned.Name = v.ItemID
		owned.Parent = shopItems
	end
	
	for i, v in pairs(ShopData) do
		local equipped = Instance.new("BoolValue")
		equipped.Name = v.ItemID
		equipped.Parent = equippedItems
	end
	
	equippedItems.Parent = p
	shopItems.Parent = p
	leaderstats.Parent = p
	playerSkills.Parent = p
	
	
	-- Separator from just creating the folders and variables under each player
	
	
	
	
	
	
	
	
	
	for i = 5, 0, -1 do
		local success, data = pcall(function()
			return PlayerPages:GetAsync(p.UserId)
		end)

		if success then
			
		else
			print("Something went wrong with loading the data.")
			if i == 0 then
				p:Kick("Something went wrong loading your data. If problem persists, try again later.")
			end
		end
	end
end



function playerRemoved(p)
	
		local success, newPages = pcall(function()
			return PlayerPages:SetAsync(p.UserId)
		end)
		
		if success then
			print("Saved " .. p.Name .. "'s data successfully!")
		end
		
end

Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(playerRemoved)

I stripped it of the code that was trying to save or load anything to prevent confusion.

In order for us to help, we would need to know how you save your data

Currently, to save my data, I created a table that stored every single value from each folder.
I do this by looping through each folder and inserting it into the table, then using SetAsync to save the entire table of values. I couldn’t understand how to set the values to their correct location again though, since now I would have one table of all my variables that needed to be divided back into four folders.

Unfortunately, this doesn’t paint a clear enough picture. Please send your code so I can map out your schema

Okay, so, here’s how data stores work:
On DataStore:GetAsync(key) it only requires one parameter: that being the key. The key can just be a string containing almost any character on your keyboard. If you set it to the player’s UserId for example, it can be used to retrieve that player’s stored data if you’ve previously used SetAsync with the same key.

If you’re having trouble understanding that, you can think about it like files and directories on a computer or mobile device. The key is like the name of a text file or a config file for your game. If you access it, you will be given whatever the file contains inside. It can be a string, or a table, etc.

If that file hasn’t been created yet, the value that GetAsync returns will be nil.

SetAsync on the other hand, while it does require that same key, will also require you to pass on a value to save. Think about it like the text file example from before. Whatever you provide SetAsync as a value, it will store inside that text file on Roblox’s servers.

Inside the script you provided, I noticed you weren’t providing a value for SetAsync to save, meaning no player data will actually be saved.

Okay, so now that you know that, here’s how you could go about saving and loading your player data:
You know how you have variables inside the player added function? Here’s an example using a simple money saving method:

local DataStoreService = game:GetService("DataStoreService")
local PlayerData = DataStoreService:GetDataStore("PlayerData", "1") -- If you ever need to do a global data reset, you can change any of those two strings provided. I normally use the second parameter as a number so I can reset the data as 'versions'

game.Players.PlayerAdded:Connect(function(player)
    local money = Instance.new("NumberValue") -- You can use whatever values you create in here
    money.Name = "Money"
    money.Parent = player
    
    -- Of course you should make sure to wrap this into a pcall function like your script already does and maybe some attempt code like it also had
    local data = PlayerData:GetAsync(player.UserId) -- This uses the player's UserId as a key/file name
    if data then
        money.Value = data.Money or 50 -- Set the player's money to their stored money value. If there's no money saved, just default it to $50.
    end
end)

game.Players.PlayerRemoving:Connect(function(player)
    -- Find the money value
    local money = player:FindFirstChild("Money")
    
    -- Create the data table
    local data = {}
    
    -- Make sure the money value found before actually exists
    if money then
        -- Set the money property of the table to the player's money
        data.Money = money.Value
    end
    
    PlayerData:SetAsync(player.UserId, data) -- There's the key/file name again, storing the player's data as a table (so you can add any amount of player data values if needed)
    -- Don't forget to wrap this in a pcall function and possibly attempt code as well if you feel it's needed. You could also look into UpdateAsync on the DevForum if you're interested in how to protect against data loss - but that's out of scope for this post
end)

That being said, that code above is completely untested pseudocode. But it should still technically work. And even if it doesn’t, you should’ve still learned a lot from reading it!

Let me know if you need anything else explained. If not, good luck!

1 Like

Thank you for helping explain it, I had a system like this running before, what I am trying to do now is just the next step beyond just saving and loading one single value, where now I am trying to save and load a series of values, that exist withing some different folders.

1 Like

The code block I sent in the original post is the entire script. There is no saving/loading system visible because it was removed in an effort to hopefully get some suggestions from other scripters on how I should navigate saving and loading multiple folders at once, since I had tried it before but only made partial progress and then hit a road block.

Just to make sure we are on the same page, I know that currently nothing is being saved, or loaded, it’s all just blank, they are still there so that they can be fed the information when I understand how to properly handle the values that are being saved.

We need some understanding of what data should be saved. Show us what it is you’re saving in the Explorer

When creating your data table, the best way to serialise and save the data is to iterate through each folder that you want to save.

An example of saving iteratively:

-- This function would be located outside of player removing. You could also change it to behave more like the loading function below where it scans through the entire player automatically So you don't have to manually assign which folders to scan and save.
local function SaveFolderToData(data, folder: Folder)
    local values = {}
    data[folder.Name] = values
    
    for _, obj: ValueBase in folder:GetChildren() do
        values[obj.Name] = obj.Value
    end
end

-- This would be located within the player added function; after you've created the values
local data = {}

SaveFolderToData(data, player.Inventory)
-- Other folders etc.

-- And you can then perform your SetAsync implementation here using that data table

For loading the data, you could probably do something more akin to this:

-- This would be located outside the player added function. Automatically scans through the entire saved data table using recursion.
local function ScanDataToLoad(data, parent: Instance)
    for name, value in data do
        -- Make sure the instance actually exists
        local obj = parent:FindFirstChild(name)
        if (not obj) then
            warn(`{name} is not a valid data value`)
            
            continue -- Continue behaves like return but instead within a loop
        end
        
        -- If the data value is a table, scan through the folder instead.
        if (typeof(value) == "table") then
            ScanDataToLoad(value, obj)
            
            continue
        end
        
        -- Load the stored data value
        obj.Value = value
    end
end

-- Located inside player added after creating the instances required
ScanDataToLoad(data, player)

If you have any other questions, feel free to ask them!

1 Like

I sent it earlier but I’ll send it again, I’m saving all folders underneath the player
image

You can see the pathway, the only children of the player that are being saved are the contents of each folder.

Regarding saving that data, I ended up doing exactly that, but I felt like I was being inefficient by iterating through each folder in its own for loop, so I was trying to find a way to loop through all four folders, in just one for loop.

Regarding loading, I think I will try something like this, but if possible, could you give a few details as to how loading the data works? I havent seen something like this before

So regarding the saving, you could indeed just use one for loop within the player to check each folder/value, and do the recursive scan technique I showed within the loading function.

Regarding my loading function, it utilises recursion because it works like this:

The first step is to look at each stored value within the retrieved data. The first layer of recursion will check the instances within the player.
Inside the for loop, it first checks if the data is a table. If so, the data must then translate to a folder if you saved it correctly.

The next step within a folder is to scan the table’s stored values. So the function then calls itself to check that stored data within the folder, and the above process happens again, only one branch down from the player.

If the stored data isn’t a table, then it’s most likely just a value inside the player that you can set. It’s as simple as parent[data_name].Value = data_value inside the for loop.
For example, on the first step parent would be player. data_name and data_value were retrieved from the for loop as a key and value pair.

Here’s a crude little diagram I whipped up just now in paint:

1 Like

I ended up figuring it out in my own kind of way.

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local PlayerPages = DataStoreService:GetDataStore("A")
local ShopData = require(game.ReplicatedStorage.GameMaterials.Modules.ShopData)



function PlayerAdded(p)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	
	local playerSkills = Instance.new("Folder")
	playerSkills.Name = "playerskills"
	
	local shopItems = Instance.new("Folder")
	shopItems.Name = "shopItems"
	
	local equippedItems = Instance.new("Folder")
	equippedItems.Name = "equippeditems"
	
	local pages = Instance.new("IntValue")
	pages.Name = "Pages"
	pages.Parent = leaderstats
	
	local passive1 = Instance.new("StringValue")
	passive1.Name = "Passive1"
	passive1.Parent = playerSkills
	passive1.Value = "Empty"
	
	local passive2 = Instance.new("StringValue")
	passive2.Name = "Passive2"
	passive2.Parent = playerSkills
	passive2.Value = "Empty"
	
	local regenRate = Instance.new("NumberValue")
	regenRate.Name = "regenRate"
	regenRate.Value = 0.3
	regenRate.Parent = playerSkills
	
	local storedID = Instance.new("StringValue")
	storedID.Name = "storedid"
	storedID.Parent = playerSkills
	storedID.Value = "Empty"
	
	local storedCost = Instance.new("NumberValue")
	storedCost.Name = "storedcost"
	storedCost.Parent = playerSkills
	
	for i, v in pairs(ShopData) do
		print(v.ItemID)
		local owned = Instance.new("BoolValue")
		owned.Name = v.ItemID
		owned.Parent = shopItems
	end
	
	for i, v in pairs(ShopData) do
		local equipped = Instance.new("BoolValue")
		equipped.Name = v.ItemID
		equipped.Parent = equippedItems
	end
	
	equippedItems.Parent = p
	shopItems.Parent = p
	leaderstats.Parent = p
	playerSkills.Parent = p
	
	
	-- Separator from just creating the folders and variables under each player
	
	
	
	

	
	
	
	
	for i = 5, 0, -1 do
		local success, data = pcall(function()
			return PlayerPages:GetAsync(p.UserId)
		end)
		
		local loadedData = {}
		
		if success then
			for i, v in pairs(p:GetDescendants()) do
				if v:IsA("Folder") then
					for i, v in pairs(v:GetChildren()) do
						table.insert(loadedData, v)
					end
				end
			end
			
			for i, v in pairs(loadedData) do
				v.Value = data[i]
			end
			
		else
			print("Something went wrong with loading the data.")
			if i == 0 then
				p:Kick("Something went wrong loading your data. If problem persists, try again later.")
			end
		end
	end
end



function playerRemoved(p)
		
		local data = {}
		
		for i, v in pairs(p:GetDescendants()) do
			if v:IsA("Folder") then
				for i, v in pairs(v:GetChildren()) do
					table.insert(data, v.Value)
				end
			end
		end
		
		print(data)
		
		local success, newPages = pcall(function()
			return PlayerPages:SetAsync(p.UserId, data)
		end)
		
		if success then
			print("Saved " .. p.Name .. "'s data successfully!")
		end
		
end

Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(playerRemoved)

Might not be 100% optimized, but for my first time ever saving information that is scattered across several folders, I’m quite proud of it.

1 Like