If you are playing a game, such as Twisted Murderer or Mad Paintball, and you buy in-game currency or a character, or level up, you expect that to save. You expect that, next time you play this game, your progress, your purchases, will not be gone.
So now, you're making your own game. And you want to make sure your game saves some important data for the player, too. How do you do that?
You use the DataStoreService
.
The DataStoreService
is an awesome API specifically meant for saving and loading data for your game. Literally in its name - it stores data! (No it doesn't mean it is a store to buy data from.)
How do you use the DataStoreService
? I'll teach you.
The first step is to get the DataStoreService into your script. You do this by loading it in, just like any other ROBLOX Service.
local DSS = game:GetService("DataStoreService")
After this, you get your specific DataStore
. See, the DataStoreService
is not actually where you save your data, it is simply an API to get your DataStores
. The DataStore
is actually where all your data is saved and loaded from. And here is how to get a DataStore
:
local datastore = DSS:GetDataStore("GeneralSaveData", "Players")
The DataStoreService:GetDataStore()
function has two parameters, name
and scope
. The name
parameter is required, and it just means the actual name of your DataStore
. You can name your DataStore
anything, even if it is "BillyBobJoeManLikesUnicorns". However, it helps to name your DataStore
according to its purpose. The reason I have mine named "GeneralSaveData" is that it is where I am saving the general data for each player, so the name makes perfect sense.
The second parameter, scope
, is a bit more tricky. Thankfully, this parameter is not required, so you can just leave it empty and only provide a name
argument. But, if you're feeling adventurous, you can use the scope
argument to have different, sort of, groups of DataStores
. Let's say you saved general data for every player, but that you also saved general data for your game itself. You could then have two DataStores named "GeneralSaveData", but each in a different scope (e.g. "Players" and "Game"). You can sort of see it as a folder structure on your computer. You can have two different folders, each with files that have the same name. But because the files are in different folders, there isn't a problem!
So, great, now you know how to make and have a DataStore
! How do you use it?
Let's start with how to save data. There are couple things you're going to need in order to save your data. The first thing is, you're going to need a key
. I don't mean a key to unlock a door or even a fancy wireless key for one of those push-to-start cars. No, in this case, the key
is a string
, and it is sort of a name for the data itself. Since we're working with data for players, we're going to want to make this key
specific to the player. The best way to do this is to create the key
based off of the player's userId
, because this is the only thing we can be sure won't change for each player's account. What I like to do is create a function inside of my data script that generates a key
by just giving it a player as an argument. Here's what I normally do:
function generateDataKey(player) local ret = "uid_" .. player.userId return ret end
For my account, my key
from this function would be "uid_3276148".
The next thing you need is the data itself. The data has to be in the form of primitives
(e.g. numbers, strings, and booleans), but these primitives
CAN be stored in a table
. Typically, this is how I store data, because it is easy to access. So let's create a mock table
, assuming the player has two leaderstats - Points and Wins.
function generateDataTable(player) local dataTable = { Points = player.leaderstats.Points.Value, Wins = player.leaderstats.Wins.Value } return dataTable end
The last part of saving data is to actually tell your DataStore
to save it! This is done using your key
and your data
, in that order. And then all you have to do is call the SetAsync
method on your DataStore
. I like to put this in its own function that takes a player as an argument, and then it calls the generateDataKey
and generateDataTable
functions right there. Here's what I normally do:
function saveDataForPlayer(player) local key = generateDataKey(player) local data = generateDataTable(player) datastore:SetAsync(key, data) end
What this function does, is it saves whatever data you give it with the assigned key
. Now it safely stored away in ROBLOX's systems, ready to be loaded back in at any time! We'll discuss loading data in my next blog post, since this one is so long already. So make sure to check back on Thursday, May 21 for my next post! For now, here is the script we've made, in total:
local DSS = game:GetService("DataStoreService") local datastore = DSS:GetDataStore("GeneralSaveData", "Players") function generateDataKey(player) local ret = "uid_" .. player.userId return ret end function generateDataTable(player) local dataTable = { Points = player.leaderstats.Points.Value, Wins = player.leaderstats.Wins.Value } return dataTable end function saveDataForPlayer(player) local key = generateDataKey(player) local data = generateDataTable(player) datastore:SetAsync(key, data) end
So, from this, how can we load in our data again? Say, when the player joins the game on a new server? It's actually sort of simple. We will use our datastore
variable's :GetAsync(key)' method. Keep in mind, we used our
generateDataKey` function to generate a key to store our entire data table with. So we will use this function again to get that data table back! Here's what I mean:
function loadDataForPlayer(player) local key = generateDataKey(player) local data = datastore:GetAsync(key) end
So, our function now loads the data in. But how do we go about using this data and giving the player their stats back? My answer would be the same way we got the data in the first place - just setting one value to another. In our generateDataTable
function, we wrote this:
Points = player.leaderstats.Points.Value
So, now, we will write something like this:
player.leaderstats.Points.Value = data.Points
Make sense? We're taking the value we saved FROM the Points
leaderstat, and we're just putting it back there! So let's make our new inputDataToPlayer
function.
function inputDataToPlayer(player, data) player.leaderstats.Points.Value = data.Points player.leaderstats.Wins.Value = data.Wins end
And now, let's alter our loadDataForPlayer
function to call inputDataToPlayer
function loadDataForPlayer(player) local key = generateDataKey(player) local data = datastore:GetAsync(key) inputDataToPlayer(player, data) end
Voila! All of our functions are written!
But one problem.
Nothing really works...
Why?
Well, we've written a lot of functions. But we haven't actually called our save or load functions anywhere! But, when SHOULD we call these functions? Let's discuss this.
When to load data...Well, probably only need to load a player's data once - when they join the game.
What about saving? Times to save data are more complicated. At least when the player leaves the game...And probably more often than that, too. Maybe we should save every time the player's Win stat changes, since that one seems important and will only happen once every round. Yeah, let's go with that!
So here is how we implement this (if you don't know about Events, please refer to this blog post):
game.Players.PlayerAdded:connect(function(player) loadDataForPlayer(player) --Load first thing when they join player.leaderstats.Wins.Changed:connect(function() saveDataForPlayer(player) --Save the data when Wins changes value end) end) game.Players.PlayerRemoving:connect(saveDataForPlayer) --Save data when the player leaves the game
And that's it! Our saving and loading data idea works now that we've made all our functions and implemented them! Keep in mind, you will have to alter some of these functions to make them work for your game, but the general concept is the same.
And, to close out, here is all the code compiled into one:
local DSS = game:GetService("DataStoreService") local datastore = DSS:GetDataStore("GeneralSaveData", "Players") function generateDataKey(player) local ret = "uid_" .. player.userId return ret end function generateDataTable(player) local dataTable = { Points = player.leaderstats.Points.Value, Wins = player.leaderstats.Wins.Value } return dataTable end function saveDataForPlayer(player) local key = generateDataKey(player) local data = generateDataTable(player) datastore:SetAsync(key, data) end function inputDataToPlayer(player, data) player.leaderstats.Points.Value = data.Points player.leaderstats.Wins.Value = data.Wins end function loadDataForPlayer(player) local key = generateDataKey(player) local data = datastore:GetAsync(key) inputDataToPlayer(player, data) end game.Players.PlayerAdded:connect(function(player) loadDataForPlayer(player) --Load first thing when they join player.leaderstats.Wins.Changed:connect(function() saveDataForPlayer(player) --Save the data when Wins changes value end) end) game.Players.PlayerRemoving:connect(saveDataForPlayer) --Save data when the player leaves the game