Hello, developers!
I’ve been wanting to teach people on how to use Datastore-Service correctly. Everyone seems to dislike datastore service because of dataloss, ratelimits, and the savings abilities it has. However, I am here to show you how to program a datastore correctly, fixing these limitations.
In this tutorial, I will be showing you how to;
- Stop dataloss,
- Learn where and when to save,
-
Save custom items(In section 1, will update this post on how to save other custom items)
If you are new to datastores, no worries. The first few details are about how to create and use a datastore. Even if you aren't new, I would take a peek at it, as I try to explain the process.
SECTION 1: HOW TO MAKE A DATASTORE
Summary
Creating the Datastore
Intro
(P.S, make sure you have this button checked. You can find it after you publish your game in game settings → security.)
To get started, you need to first create your datastore.
Start off by calling the Datastore-service.
local Datastore = game:GetService("DataStoreService")
Then, create your datastore for your game.
local Data = Datastore:GetDataStore("gameData")
You can name “gameData” anything you would like. If you rename it, be careful. This changes the already existing one into a new datastore.
Don’t forget to add the PlayerService
!
local Players = game:GetService("Players")
The next step is to create the three main functions for the datastore.
function PlayerAdded(Player)
-- To get the data and create the leaderstats, folders, etc.
end
function PlayerLeaving(Player)
-- To save the data
end
game:BindToClose(function()
-- To give the server time to save
end)
Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerLeaving)
In the PlayerAdded
function, we need to make the leaderstats and other game aspects that we want saved.
function PlayerAdded(Player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = Player
local Coins = Instance.new("IntValue")
Coins.Name = "Coins"
Coins.Value = 25
Coins.Parent = leaderstats
local Weapons = Instance.new("Folder")
Weapons.Name = "Weapons"
Weapons.Parent = Player
end
Right now we have a simple leaderstats and weapon folder script that we insert into the player.
Next, we have to wait for the server to save our data. In roblox studio, the game shuts down too fast for the server, making it unable to save the data. Luckily, there is an easy fix to this. Just add wait(3)
to the game:BindToClose
function.
game:BindToClose(function()
wait(3) -- You can save for longer, but wait atleast 3 seconds
end)
This doesn’t mean it will save, yet.
To save the data, we first need to get the data.
Getting the Player’s Data
Body
We need to get the player’s data in order to save it!
But what if they don’t have any data? No worries. Pcall, meaning protective call, will be able to help us. Pcall will not stop the code if an error were to happen. This is good because normally code will break if an error ocours.
We can use this when calling the datastore.
local Success, Returned = pcall(function()
return Data:GetAsync(Player.UserId) -- Returns a table with all the saved data, key excluded
end)
This might look a bit weird, but let me explain. We are making a pcall function and assigning two variables to it. The first variable is controlled by the pcall. If an error were to happen, the first variable which in our case is “Success” would return false. If no errors were to happen, it would return true. All the other variables after that is ours. So when we return the player’s data, we are returning to the second variable, which is called “returned”. If there is no player data or the success variable returns false, the returned variable would be nil because there isn’t anything saved to the datastore unless the player rejoins. Lastly, if there is an error, the returned argument will contain the error message.
Another important thing here is that we are getting the data through the player’s key. Each player has a unique key when creating an account. This is your userid. When saving and getting the player’s data, it is important to save and get the player’s key, or else everyone will have the same data!
Loading the Player’s Data
Body
How can we load the player’s data once we have it? Well, the returned value is actually a table. (Tables)
This table holds all of the player’s data, like the amount of coins they have. So how do we access it? For starters, tables have something called an index
. (What is indexing? - #5 by C_Sharper)
Lets say I have a table. I want to get the first item in the table. How would I do that? With indexing. Example code:
local myTable = {"Apple", "Pear", "Banana"}
print(myTable[1]) -- Apple
print(myTable[2]) -- Pear
print(myTable[3]) -- Banana
-- Indexes start at 1
local myTable2 = {
Cash = 50,
Supplies = {
"Beans",
"Bag",
"Boots"
}
}
print(myTable2["Cash"]) -- 50
print(myTable2["Supplies"]) -- [1] = "Beans", [2] = "Bag", [3] = "Boots" (Only shows in the output tab)
print(myTable2["Supplies"][1]) -- "Beans"
print(myTable2[1]) -- nil
As you can see, each value in myTable
has an index to represent each string.
In myTable2
, the indexes aren’t represented by numbers, but by string. If a value is a variables, it won’t have a number as an index. The index will become the string’s name instead.
So you would do this:
local myTable2 = {
Cash = 50,
Supplies = {
"Beans",
"Bag",
"Boots"
}
}
print(myTable2["Cash"]) -- 50
This can also be achevied by doing:
print(myTable2.Cash) -- 50
Both ways work.
This is how we are going to load and set the player’s data into our leaderstats.
function PlayerAdded(Player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = Player
local Coins = Instance.new("IntValue")
Coins.Name = "Coins"
Coins.Value = 25
Coins.Parent = leaderstats
local Weapons = Instance.new("Folder")
Weapons.Name = "Weapons"
Weapons.Parent = Player
local Success, Returned = pcall(function()
return Data:GetAsync(Player.UserId) -- Returns a table with all the saved data, key excluded
end)
----------------------------------------------------------------------------------------------------------------------------------------
if Success and Returned then
else
print(Returned) -- Prints the error, if the player is new to the game, the expected output is 'nil'
end
end
We have to start off by making sure there are no errors and that the player has data.
If there is an error, we can just print it. So if everything passes the check, then we are set. All we have to do now is set the coins value to the saved data and load in the weapons.
if Success and Returned then
local WeaponCount = 0
Coins.Value = Returned["Coins"]
for _,_ in pairs(Returned["Weapons"]) do -- Looping through the player's weapons
WeaponCount += 1 -- If we can loop through once, we know they have more than zero weapons and can now load in their weapons. Without this, we could get an error because the player might not have any weapons
break
end
if WeaponCount > 0 then -- If the player has weapons
for _,v in pairs(Returned["Weapons"]) do -- Loops through the table again
local i = Instance.new("BoolValue") -- Create a new instance
i.Name = v -- Set the instance name to v; v is the 'object' or table value that is in the loop, so all we need is the name of it
i.Parent = Weapons -- Parent the new instance into the weapons folder
end
else
print(Player.Name.." has no weapons") -- If the weapon count is zero, then print
end
else
print(Returned) -- Prints the error, if the player is new to the game, the expected output is 'nil'
end
On the fourth line, you can see that we are accessing the player’s data and finding the “Coins” value through indexing. This variable’s value is a number value. Check out the comments to learn how we get the weapons from the player!
After you are finished, you should get ‘nil’ printed out as we haven’t saved the data yet.
Are we done now? Nope! We are just getting the player’s data, they don’t have anything saved yet.
All we have to do now is save the player’s data!
Saving the Player’s Data
Body
We are almost finished!
There is something you should know. Datastore-service can only hold one custom argument at a time. So in order to save all of the leaderstats, weapons, etc, we need to put them all in one big table.
function PlayerLeaving(Player)
local PWeapons = Player:FindFirstChild("Weapons"):GetChildren()
local PCoins = Player:FindFirstChild("leaderstats"):FindFirstChild("Coins").Value
end
Before we do that, these two lines inside the function are really important. This is where most people mess up. You have to save items, values, strings, etc to variables if you want to avoid dataloss. These should also be at the very front of the function, so we don’t have weird corruptions with data.
After you have saved these stats in variables, we need to save them to the datastore. For me, I made a weapons folder that will hold all of the player’s weapons. I need to loop through the folder and put them into a weapons table. This is because the weapons table will go inside the table we will save. This makes it more organized and clean.
function PlayerLeaving(Player)
local PWeapons = Player:FindFirstChild("Weapons"):GetChildren()
local PCoins = Player:FindFirstChild("leaderstats"):FindFirstChild("Coins").Value
-- Getting the information the second the player is leaving will stop dataloss
local WeaponTable = {}
for i,v in ipairs(PWeapons) do
table.insert(WeaponTable, i, v.Name) -- All we need is the name, for once the player is loaded in we can give their weapons back after the values have been inserted into the weapons folder.
end
end
Now that we have the weapons part done, we can save all of the data into the big table we will be using to save to the datastore. We don’t need to put the coins into a ‘CoinsTable’ because its already as neat as it can be.
local PlayerTable = {
Coins = PCoins, -- You can access this when the playerjoins by Returned["Coins"]. This will return the PCoins value.
Weapons = WeaponTable -- This is a table inside of a table.
}
Finally, we can save the data.
Data:SetAsync(Player.UserId, PlayerTable) -- Saves it to the player's key! Only this player will have access to their data
We also have to save the data to the BindToClose
function just in case roblox servers crash.
game:BindToClose(function()
for _,v in ipairs(Players:GetPlayers()) do
task.spawn(function() -- This function makes it so our function below doesn't yield
PlayerLeaving(v)
end)
end
wait(3)
end)
Make sure you are putting all of your data you want to save in the PlayerTable
. If not, your other values you are saving will delete themselves.
Final Product
Conclusion
After creating, getting, loading, and saving the data, it should look like this.
(If you haven’t already, please look at how I did it, or this won’t help you.)
local Datastore = game:GetService("DataStoreService")
local Data = Datastore:GetDataStore("newData")
local Players = game:GetService("Players")
function PlayerAdded(Player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = Player
local Coins = Instance.new("IntValue")
Coins.Name = "Coins"
Coins.Value = 25
Coins.Parent = leaderstats
local Weapons = Instance.new("Folder")
Weapons.Name = "Weapons"
Weapons.Parent = Player
local Success, Returned = pcall(function()
return Data:GetAsync(Player.UserId)
end)
if Success and Returned then
local WeaponCount = 0
Coins.Value = Returned["Coins"]
for _,_ in pairs(Returned["Weapons"]) do
WeaponCount += 1
break
end
if WeaponCount > 0 then
for _,v in pairs(Returned["Weapons"]) do
local i = Instance.new("BoolValue")
i.Name = v
i.Parent = Weapons
end
else
print(Player.Name.." has no weapons")
end
else
print(Returned)
end
end
function PlayerLeaving(Player)
local PWeapons = Player:FindFirstChild("Weapons"):GetChildren()
local PCoins = Player:FindFirstChild("leaderstats"):FindFirstChild("Coins").Value
local WeaponTable = {}
for i,v in ipairs(PWeapons) do
table.insert(WeaponTable, i, v.Name)
end
local PlayerTable = {
Coins = PCoins,
Weapons = WeaponTable
}
Data:SetAsync(Player.UserId, PlayerTable)
end
game:BindToClose(function()
for _,v in ipairs(Players:GetPlayers()) do
task.spawn(function()
PlayerLeaving(v)
end)
end
wait(3)
end)
Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerLeaving)
You have learned how to use Datastore-serivce, congrats!
You can now just save about anything in 76 lines of code.
FAQ
Info
- Q: Setting async will remove any other data you have saved, so why are you using it?
- A: If you want to add a new value to the datastore, insert it into the table that you are using to save the other data in. I am using it because its the most simple way of saving, and one of my favorites.
- Q: How would I save ranks, capes, hats, etc?
- A: The same way you would save weapons, but edited so its like leaderstats.
- Q: Why is this important?
- A: Its very important. There are other saving methods, like Datastore 2. However, knowing how code works is better than just following a guide on how to save and get it, even if it is longer.
SECTION 2: HOW TO FIX DATALOSS
Summary
Dataloss isn’t normal. Here are some tips you can cross off to help you along the way.
-
Save the player’s data to a variable right after the
PlayerRemoving
event fires. -
Do not save unless the player is leaving the game. Auto-savers usually queue up datastores and cause dataloss. Only put them in your game if really have to.
-
Did you print? Did you check the output? These are simple things that a-lot of people forget to do, they usually fix dataloss problems after doing this.
-
Comment out pcalls. Pcalls stop errors and return them. However, the error message might not make sense or it isn’t returning the error properly. Once commented, you should find out why it isn’t saving.
-
Do you have the
game:BindToClose
function, and are you using it correctly? Studio shuts down too fast making the server unable to save. Addwait(3)
to the function and see if it works. -
Have you tried following section one? It explains everything in detail and it might just be the fix to your dataloss.
This is my first time ever creating a tutorial. I really hope this helps a-lot of you. I hope to create more of these.
Thank you!
- DeveloperBLK