Roblox allows data to be saved on its servers. The primary purpose of this feature is to store player data between sessions, keeping their stats, inventory, and other data intact. In this tutorial, we’ll create a system that automatically stores a player’s money and experience so that it can be retrieved when they play the game again.
If you want to explore the Roblox data storage system in more depth before you begin, please see the Articles/Data store|Data Stores
article.
API access must be active in order to access and test data stores in Roblox Studio. See the Articles/Data store|Data Stores
article for instructions on enabling API access.
Creating a Data Module
Before we dive into data storage, let’s set up the system which keeps track of a player’s money and experience during a game session. We’ll start by creating a ModuleScript
, a special type of script that can be referenced from other scripts. An ideal place for this ModuleScript
is within ServerStorage.
-- Set up table to return to any script that requires this module script
local PlayerStatManager = {}
-- Table to hold player information for the current session
local sessionData = {}
local AUTOSAVE_INTERVAL = 60
-- Function that other scripts can call to change a player's stats
function PlayerStatManager:ChangeStat(player, statName, value)
local playerUserId = "Player_" .. player.UserId
assert(typeof(sessionData[playerUserId][statName]) == typeof(value), "ChangeStat error: types do not match")
if typeof(sessionData[playerUserId][statName]) == "number" then
sessionData[playerUserId][statName] = sessionData[playerUserId][statName] + value
else
sessionData[playerUserId][statName] = value
end
end
-- Function to add player to the "sessionData" table
local function setupPlayerData(player)
local playerUserId = "Player_" .. player.UserId
sessionData[playerUserId] = {Money=0, Experience=0}
end
-- Connect "setupPlayerData()" function to "PlayerAdded" event
game.Players.PlayerAdded:Connect(setupPlayerData)
return PlayerStatManager
Note that the PlayerStatManager:ChangeStat()
function handles either numeric or non-numeric changes. This means you can safely call the function with either a number (positive or negative) or string value as the value
parameter.
Saving Player Data
Now let’s start storing actual information using data stores.
Initialize the Data Store
First we’ll add a new variable for the data store in the same ModuleScript
and call DataStoreService/GetDataStore|GetDataStore()
to open a new PlayerData data store.
-- Set up table to return to any script that requires this module script
local PlayerStatManager = {}
local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")
-- Table to hold player information for the current session
local sessionData = {}
local AUTOSAVE_INTERVAL = 60
You can name your data store whatever is convenient. Data stores are shared between all places in a game, so if your game has multiple places, they can all access the same data store by its name.
Read/Write Initial Data
Next, let’s change how the setupPlayerData()
function works. Currently, it just creates new data for a player when they join the game, but that data isn’t saved anywhere! Now, with access to the playerData
data store, we can call GlobalDataStore/GetAsync|GetAsync()
to check if it’s holding any information for the player. If that call returns valid data, we save it to the sessionData
table; otherwise we save new player data to the sessionData
table.
-- Function to add player to the "sessionData" table
local function setupPlayerData(player)
local playerUserId = "Player_" .. player.UserId
local data = playerData:GetAsync(playerUserId)
if data then
-- Data exists for this player
sessionData[playerUserId] = data
else
-- Data store is working, but no current data for this player
sessionData[playerUserId] = {Money=0, Experience=0}
end
end
-- Connect "setupPlayerData()" function to "PlayerAdded" event
game.Players.PlayerAdded:Connect(setupPlayerData)
Save Data on Exit
It’s good practice to save a player’s data when they exit the game. This can be done with a new savePlayerData()
function bound to the Players/PlayerRemoving|PlayerRemoving
event.
-- Function to save player's data
local function savePlayerData(playerUserId)
if sessionData[playerUserId] then
playerData:SetAsync(playerUserId, sessionData[playerUserId])
end
end
-- Function to save player data on exit
local function saveOnExit(player)
local playerUserId = "Player_" .. player.UserId
savePlayerData(playerUserId)
end
-- Connect "setupPlayerData()" function to "PlayerAdded" event
game.Players.PlayerAdded:Connect(setupPlayerData)
-- Connect "saveOnExit()" function to "PlayerRemoving" event
game.Players.PlayerRemoving:Connect(saveOnExit)
Auto-Save
Lastly, it’s useful to make the system handle unexpected events like game crashes. We can do this with a function that waits for AUTOSAVE_INTERVAL
seconds (60), loops through all of the players in the sessionData
table, and uses the savePlayerData()
function to save the current information. Note that this function is initially run in a coroutine using the spawn()
function.
-- Function to periodically save player data
local function autoSave()
while wait(AUTOSAVE_INTERVAL) do
for playerUserId, data in pairs(sessionData) do
savePlayerData(playerUserId)
end
end
end
-- Start running "autoSave()" function in the background
spawn(autoSave)
-- Connect "setupPlayerData()" function to "PlayerAdded" event
game.Players.PlayerAdded:Connect(setupPlayerData)
With all of the above implemented, we now have a simple stat saving system that automatically saves the players’ data.
Handling Data Store Failures
Requests to data stores, like all network calls, may occasionally fail due to poor connectivity or other issues. As you learned in the Articles/Data store|Data Stores
article, these calls should be wrapped in pcall()
to catch and handle errors. Let’s apply this practice to the current script:
-- Set up table to return to any script that requires this module script
local PlayerStatManager = {}
local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")
-- Table to hold player information for the current session
local sessionData = {}
local AUTOSAVE_INTERVAL = 60
-- Function that other scripts can call to change a player's stats
function PlayerStatManager:ChangeStat(player, statName, value)
local playerUserId = "Player_" .. player.UserId
assert(typeof(sessionData[playerUserId][statName]) == typeof(value), "ChangeStat error: types do not match")
if typeof(sessionData[playerUserId][statName]) == "number" then
sessionData[playerUserId][statName] = sessionData[playerUserId][statName] + value
else
sessionData[playerUserId][statName] = value
end
end
-- Function to add player to the "sessionData" table
local function setupPlayerData(player)
local playerUserId = "Player_" .. player.UserId
local success, data = pcall(function()
return playerData:GetAsync(playerUserId)
end)
if success then
if data then
-- Data exists for this player
sessionData[playerUserId] = data
else
-- Data store is working, but no current data for this player
sessionData[playerUserId] = {Money=0, Experience=0}
end
else
warn("Cannot access data store for player!")
end
end
-- Function to save player's data
local function savePlayerData(playerUserId)
if sessionData[playerUserId] then
local success, err = pcall(function()
playerData:SetAsync(playerUserId, sessionData[playerUserId])
end)
if not success then
warn("Cannot save data for player!")
end
end
end
-- Function to save player data on exit
local function saveOnExit(player)
local playerUserId = "Player_" .. player.UserId
savePlayerData(playerUserId)
end
-- Function to periodically save player data
local function autoSave()
while wait(AUTOSAVE_INTERVAL) do
for playerUserId, data in pairs(sessionData) do
savePlayerData(playerUserId)
end
end
end
-- Start running "autoSave()" function in the background
spawn(autoSave)
-- Connect "setupPlayerData()" function to "PlayerAdded" event
game.Players.PlayerAdded:Connect(setupPlayerData)
-- Connect "saveOnExit()" function to "PlayerRemoving" event
game.Players.PlayerRemoving:Connect(saveOnExit)
return PlayerStatManager
Data Store Retries
As a final measure of reliability, it can be useful to retry data saves if the first attempt fails. This functionality can be added to the existing script with a simple repeat
loop in the savePlayerData()
function:
-- Function to save player's data
local function savePlayerData(playerUserId)
if sessionData[playerUserId] then
local tries = 0
local success
repeat
tries = tries + 1
success = pcall(function()
playerData:SetAsync(playerUserId, sessionData[playerUserId])
end)
if not success then wait(1) end
until tries == 3 or success
if not success then
warn("Cannot save data for player!")
end
end
end