Data Persistence Tutorial
Introduction
This tutorial will help you understand and use Data Persistence effectively in your places.
Prerequisites
- You must be proficient in using Lua in Roblox
What is Data Persistence?
Data Persistence is a way to save data to a player, and load it again if they join that place again, even on a different server. It is done completely with Lua, with calls to load and save data on a specific player. It is Per-User-Per-Place (PUPP), meaning you can't save anything to the server (for example if you wanted a high score board to work for every server, which isn't currently possible) but save data to specific players.
Why would this be useful?
Data Persistence opens up a vast number of possibilities, many of which you probably already wanted to do and some which may have already been achieved with messy other ways (such as typing in load codes). Here's a few of the things you could use with Data Persistence with:
- Save Level, XP and Gold in RPGs
- Save models people have built in building games
- Save position a player has got to in an obby
- Save score in all sorts of games
Using Data Persistence
DataReady
You cannot save data to a player immediately after a player has joined your place - you must wait until data saving is ready, by reading a value Roblox has added into every player called DataReady. When that is true, you can save and load data from the player.
For a quick and easy way of waiting for DataReady in a player, you can use the WaitForDataReady function, which makes your script wait until DataReady is true:
game.Players.PlayerAdded:connect(function(player) player:WaitForDataReady() end)
That was easy, wasn't it? Those few lines won't achieve any saving or loading though! We need to use the methods in the player objects to save and load data from the player.
Getting started
Firstly, we will need a score in the player to save! I'm going to be creating a leaderboard, but only create the player's score after it has been loaded (we will cover this later). This is a tutorial on data persistence, not leaderboards, so I'm not going to explain the code. Here is the script so far:
game.Players.PlayerAdded:connect(function(player) player:WaitForDataReady() -- create leaderboard local ls = Instance.new("IntValue") ls.Name = "leaderstats" ls.Parent = player --create the score stat local score = Instance.new("IntValue") score.Name = "Score" score.Parent = ls end)
This will create a leaderboard in the player. However, you may have noticed that we haven't set the score's value; we haven't loaded it yet. Let's do that now.
Saving and loading overview
You both load and save data with key/value pairs, where `key` is the string index you load and save it from. When loading and saving, the key you load a specific piece of data as must be the same you saved it as or you'll be loading something completely different and that probably hasn't been saved.
The save limit for a user in a place is 45,000 bytes, which shouldn't be a problem unless you saving absolutely masses of data.
The first thing you will need to know is that loading or saving data may not always work correctly,for a number of reasons. When using data persistence, you should place all data persistence calls in pcall()s so that if it doesn't work, it won't break the script and you can catch the error. More about this will be explained below.
Loading data
The methods to load data from the player are:
- LoadNumber(string key)
- LoadString(string key)
- LoadInstance(string key)
- LoadBoolean(string key)
key, as explained previously, is the index we are going to be loading and saving the data from. So we can change the player's score's key easily, without going the entire script and finding where we load and save to the player, we will create a variable with the key to put at the beginning of the script.
scoreKey = "PlayerScore"
There we go. Now whenever we want to load or save the player's score, we will use scoreKey as the key.
As mentioned, we should put the load and save calls in pcall()s, so if it breaks, it won't crash the entire script. We can then print the error message if it doesn't work.
If a load function has nothing to load, it will return 0 (for LoadNumber), "" (for LoadString) or nil (for LoadInstance). This may be the same as their actual score in the game, so don't put a big error saying, "YOU HAVEN'T GOT ANYTHING SAVED!" because they may just have had 0 points/whatever you are saving anyway. If it does have something to load and loads successfully, it will return whatever it loaded.
So that you understand this part of the script - unless you are proficient with using pcall already - you should view the pcall page. We will load the key, check if it worked, then set the player's leaderboard score to what LoadNumber returned if it did. Here is what I have:
local succ,ret = pcall(function() player:LoadNumber(scoreKey) end)
if succ then
if ret ~= 0 then
player.leaderstats.Score.Value = ret
else
print("Nothing to load/score was 0")
end
else
print("Error:",ret)
end
Hooray! We've loaded the score that was saved! But wait - we haven't saved it yet!
Saving Data
Before we can load data from the player, we first need to save it. The functions to do this are:
- SaveNumber(string key, float value)
- SaveString(string key, string value)
- SaveInstance(string key, RBX.lua.Instance (Object) value)
- SaveBoolean(string key, bool value)
As mentioned before, we have these key and value arguments in our functions. key is the string index we load and save them from.
You cannot save data to a player after s/he has left the game. As most times we will need to save data as they are leaving, we can use the PlayerRemoving event, which fires just before a player leaves. In this function, we save the player's score using SaveNumber.
game.Players.PlayerRemoving:connect(function(player)
local succ,ret = pcall(function() player:SaveNumber(scoreKey,player.leaderstats.Score) end) --if a player leaves before the leaderstats have been created, as it's in a pcall it won't crash the script
if succ then
print("Successfully saved")
else
print("Saving error:",ret)
end
end)
Complete Source
Using that script, we are now saving the player's score whenever he leaves, and loading it again when he rejoins! In case you found that difficult to follow, here's the complete source of that script:
scoreKey = "PlayerScore" game.Players.PlayerAdded:connect(function(player) player:WaitForDataReady() -- create leaderboard local ls = Instance.new("IntValue") ls.Name = "leaderstats" ls.Parent = player --create the score stat local score = Instance.new("IntValue") score.Name = "Score" score.Parent = ls local succ,ret = pcall(function() player:LoadNumber(scoreKey) end) if succ then if ret ~= 0 then player.leaderstats.Score.Value = ret else print("Nothing to load/score was 0") end else print("Error:",ret) end end) game.Players.PlayerRemoving:connect(function(player) local succ,ret = pcall(function() player:SaveNumber(scoreKey,player.leaderstats.Score) end) --if a player leaves before the leaderstats have been created, as it's in a pcall it won't crash the script if succ then print("Successfully saved") else print("Saving error:",ret) end end)
Quick Reference
Here's a reference for all of the data functions:
WaitForDataReady( ) | |
Returns | nil |
Description: | This method pauses your script until the saved data is ready to be accessed. |
Member of: | Player |
SaveNumber( string key, float value ) | |
Returns | nil |
Description: | Saves a number value that can be reloaded from any server via LoadNumber. The DataCost taken up by a saved number can be removed by saving 0 as the value. |
Member of: | Player |
SaveString( string key, string value ) | |
Returns | nil |
Description: | Saves a string that can be reloaded from any server via LoadString. The DataCost used by a saved string can be removed by saving an empty string ("") as the value. |
Member of: | Player |
SaveInstance( string key, RBX.lua.Instance (Object) value ) | |
Returns | nil |
Description: | Saves a Roblox instance, such as a part that can be reloaded on another server via LoadInstance. The DataComplexity used is the Object's DataCost property. Saving nil removes the entry and frees up DataComplexity units.. |
Member of: | Player |
SaveBoolean( string key, bool value ) | |
Returns | nil |
Description: | Saves a boolean value that can be reloaded from any server via LoadBoolean. |
Member of: | Player |
LoadNumber( string key ) | |
Returns | number |
Description: | Returns a number value that was previously saved to the player via SaveNumber with the same key. If the key doesn't exist, it returns 0, not nil. |
Member of: | Player |
LoadString( string key ) | |
Returns | string |
Description: | Returns a string value that was previously saved to the player via SaveString with the same key. Returns an empty string ("") if key doesn't exist, not nil. |
Member of: | Player |
LoadBoolean( string key ) | |
Returns | bool |
Description: | Returns a boolean value that was previously saved to the player via SaveBoolean with the same key. Returns false if the key doesn't exist, not nil. |
Member of: | Player |
LoadInstance( string key ) | |
Returns | RBX.lua.Instance (Object) |
Description: | Loads an instance value that was previously saved to the player via SaveInstance. |
Member of: | Player |