PcoWSkbVqDnWTu_dm2ix
We use cookies on this site to enhance your user experience

Saving player data using PlayerDataStore

Saving player data using PlayerDataStore

Jul 03 2018, 10:54 AM PST 10 min

Roblox has introduced a module to help saving Player data using DataStoreService rather than Data Persistance. It is a useful and intuitive tool for saving player-specific information.

Features:

  • Supports online/offline players.
  • Local cache which writes to data store periodically which avoids datastore threshold when writing to the same key.
  • Custom serialization/deserialization.
  • Automatically loads playerdata when the player joins.
  • Automatically saves playerdata while game is running and when player leaves the game.

Usage

To use the PlayerDataStore module, first navigate to the Catalog page and take one. It can then be inserted in Studio with InsertService or by clicking on Insert > My Models.

Setup

To use the PlayerDataStore Module in a game, first get the model and insert it into your game’s ServerScriptService. Whenever you need to access or set player data you will need two things. First, create a variable for the module using require, and then a variable for the data for the player you are interested in using GetSaveData.

local PlayerDataStore = require(game.ServerScriptService.PlayerDataStore)
local saveData = PlayerDataStore:GetSaveData(player)

You can also access the player data with the playerId instead of the Player object:

local PlayerDataStore = require(game.ServerScriptService.PlayerDataStore)
local saveData = PlayerDataStore:GetSaveDataById(playerId)

GetSaveData and GetSaveDataById will both yield until the player’s data is actually ready.

Save Data

The most common way to save data with the PlayerDataStore Module is to use the Set function. This function takes a key and a value and saves both in the player data. This data is stored locally in the game and will eventually be stored in the DataStore itself (this will happen when the passive push occurs, when the player leaves the server, or when the data is flushed). Because the data is cached like this the game is not in danger of hitting Game Limits when writing to the same key with quick frequency. This function returns immediately.

local PlayerDataStore = require(game.ServerScriptService.PlayerDataStore)
local saveData = PlayerDataStore:GetSaveData(player)
saveData:Set("exp", 10)

Get Data

Fetching data from the PlayerDataStore Module is done with the Get function. This function simply takes a key and returns the stored value associated with that key. This data is pulled from the local cache so the call will return immediately:

local PlayerDataStore = require(game.ServerScriptService.PlayerDataStore)
local saveData = PlayerDataStore:GetSaveData(player)
saveData:Get("exp")

Update

Updates multiple keys of a SaveData at once and updates the results to the data store, ensuring that no data is lost. This function will yield until the update to the data store is complete.

local PlayerDataStore = require(game.ServerScriptService.PlayerDataStore)
local saveData = PlayerDataStore:GetSaveData(player)
saveData:Update({"PurchaseCount", "Money"}, function(oldPurchaseCount, oldMoney)
    if not oldPurchaseCount then oldPurchaseCount = 0 end
    if not oldMoney then oldMoney = 0 end
    oldMoney = oldMoney + developerProductAmount
    oldPurchaseCount = oldPurchaseCount + 1
    return oldPurchaseCount, oldMoney
end)

In general this function should be reserved for important and infrequent events (such as developer product purchases). That is because this function is in danger of hitting the data store request limit.

Flush

The cache that the PlayerDataStore module uses only gets stored to the Data Store periodically (by default once a minute). This means that Saves made between these pushes won’t actually change information in the Data Store right away. While this is useful because it lowers network traffic and helps avoid the Data Store request limits, sometimes it is useful to have data saved right away. To do so, simply call the Flush function on the save data:

local PlayerDataStore = require(game.ServerScriptService.PlayerDataStore)
local saveData = PlayerDataStore:GetSaveData(player)
saveData:Set("PlayerExperience", 10)
saveData:Flush()

You can also use the FlushAll function on the PlayerDataStore itself to flush all the unsaved data (for all players in the local PlayerDataStore)

local PlayerDataStore = require(game.ServerScriptService.PlayerDataStore)
local saveData = PlayerDataStore:GetSaveData(player)
saveData:Set("PlayerExperience", 10)

local saveData2 = PlayerDataStore:GetSaveData(player2)
saveData:Set("PlayerExperience", 20)

PlayerDataStore:FlushAll()

Note that both Flush() and FlushAll() both yield.

Serialization / Deserialization

The PlayerDataStore Module works with DataStoreService which means that it still can technically only store numbers, booleans, strings, or Lua tables (not Roblox types such as Parts or Vector3s). Fortunately, if you need to store a special type of object, the PlayerDataStore allows you to define a custom function for how to convert a Roblox object into types that Data Stores will accept and back again.

To define custom serialization/deserialization functions, look for the following code in the PlayerDataStore module:

local SERIALIZE = {}
local DESERIALIZE = {}
-- Put your entries here --
         --vvvvv--

         --^^^^^--

To define a function for either, simply add the function to the appropriate table. The function name should be the exact name of the key you want to save. For example, lets say your game has an IntValue that holds the player’s score and we want to store that using the PlayerDataStore Module (note that in a real game you would probably just want to store the number itself and not the whole IntValue). To serialize an IntValue (get the relevant data that can be stored), one would simply want to get the IntValue/Value property:

SERIALIZE.ScoreObject = function(scoreObject)
     return scoreObject.Value
end

To deserialize the IntValue, we have to create a new Instance of IntValue and then assign the number that was stored (as the Data Store only held the number itself, not the instance):

DESERIALIZE.ScoreObject = function(value)
     local object = Instance.new("IntValue")
     object.Name = "ScoreIntValue"
     object.Value = value
     return object
end

Lastly, here is how we would use this in a game to print the current score and assign it a new number. Note that when we get the IntValue Instance from the PlayerDataStore module, we actually are just getting a new instance that was created by the saved data. If we make changes to the IntValue, the saved data will not change until we save the IntValue to the module:

local PlayerDataStore = require(game.ServerStorage.PlayerDataStore)
local saveData = PlayerDataStore:GetSaveData(player)
local valueObject = saveData:Get("ScoreObject")
print(valueObject.Name, valueObject.Value) -- prints: ScoreIntValue 
local newScoreObject = Instance.new("IntValue")
newScoreObject.Value = 4
 -- Even though normally you cannot save objects to the data 
 -- store, the custom serialize you provided handles it
saveData:Set("ScoreObject", newScoreObject)
saveData:Flush()

Example

Save place visits

It is a simple matter to store the number of place visits with the PlayerDataStore Module. To store a visit counter per player, simply put the following in a Script:

local PlayerDataStore = require(game.ServerScriptService.PlayerDataStore)
game.Players.ChildAdded:connect(function(player)
local saveData = PlayerDataStore:GetSaveData(player)
saveData:Set("VisitCount", (saveData:Get("VisitCount") or 0) + 1)
end)

Save Developer Product purchases

When using developer products, it is important to keep track of the purchaseId as ProcessReceipt can occasionally be called more than once for the same purchase (and you only want to handle the purchase one time). With the PlayerDataStore Module one can store purchases per player (along with the developer product itself). In this example, the game awards 10 units of in game money to a player who makes a purchase. The PlayerDataStore Module is used to keep track of if a purchaseId has been handled (by keeping all purchaseIds in a table along with a boolean), and also the amount of in game money the player has. In this case, Update is used instead of Set.

local MarketplaceService = Game:GetService("MarketplaceService")
local developerProductAmount = 10

MarketplaceService.ProcessReceipt = function(receiptInfo)
  -- create variable for player data
  local playerData = PlayerDataStore:GetSaveDataById(receiptInfo.PlayerId)
  -- create update function for purchase history and ingame money for player data
  playerData:Update({"Purchases", "InGameMoney"}, function(oldPurchases, oldMoney)
    -- create purchases table if it does not exist yet
    if not oldPurchases then oldPurchases = {} end
    -- check if this transaction has already been processed via purchaseId
    if not oldPurchases[receiptInfo.PurchaseId] then
      -- this transaction hasn't been processed 
      -- if player did not have money before start money at 0
      if not oldMoney then oldMoney = 0 end
      -- give player in-game money
      oldMoney = oldMoney + developerProductAmount
      -- flag purchase as handled
      oldPurchases[receiptInfo.PurchaseId] = true
    end
    return oldPurchases, oldMoney
  end)
  return Enum.ProductPurchaseDecision.PurchaseGranted
end
Tags:
  • store
  • information
  • players