Well, like you’ve probably guessed, you’ll have to use DataStores. Now, you can find many tutorials online on how to use them, but you’ll need to know the two most important calls, SetAsync()
and GetAsync()
.
Their usage is very simple. Think of a datastore as a huge dictionary (as in, it has key-value pairs). When you use local Value = GetAsync(Key)
, you are basically doing local Value = Array[Key]
, which you can assign to a variable to read it. When you are doing SetAsync(Key, Value)
, you are essentially doing Array[Key] = Value
. Of course, they need to be wrapped in pcall
functions to ensure that it doesn’t kill your code.
Now, as for the structure of the datastore, this one is up to you to decide. If you are simply saving what chapter they are on, then you can store it as a string and set up the world based on that string. If each chapter has special properties and spawns, then a dictionary approach might be best. If you are saving universal data (as in, player has unlocked x
collectibles throughout, has unlocked y
door, and s on), then a more complex dictionary is recommended. As for how I’d name the keys, it’d probably be [USER ID]_Save1
and so on, and of course, there would be a key called [USER ID]_AllSaves
which would be an array of all the save names (the keys) and some basic info about them so I can access them in the loading screen.
Although the code below is not meant to be taken as an example, it is a good visual representation of what I mean;
-- Services --
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Variables --
local JoinSavedGameEvent = ReplicatedStorage.RemoteEvent
-- Tables --
local PlayerDataStores = {}
local PlayerSavedGames = {}
-- Scripting --
Players.PlayerAdded:Connect(function(Player)
local UserId = Player.UserId
if not PlayerDataStores[UserId] then
PlayerDataStores[UserId] = DataStoreService:GetDataStore(tostring(UserId).."_SAVEDATA")
end
local DataStore = PlayerDataStores[UserId]
if not PlayerSavedGames[UserId] then PlayerSavedGames[UserId] = {} end
local Success, AllSaves = pcall(function()
return DataStore:GetAsync("UserSaves")
end)
if Success and AllSaves then
-- GENERATE THE UI FOR THE USER
end
end)
JoinSavedGameEvent.OnServerEvent:Connect(function(Player, Save)
local SaveFile = PlayerSavedGames[Player.UserId][Save]
if SaveFile then
-- FETCH THE INFO AND TELEPORT TO THE PLACE (You can use TeleportOptions to give the server some basic instructions such as what save it is)
end
end)
And as for the actual game itself, it would fetch the teleport data and then access the datastore using the DataStore:GetAsync("Save1")
call and get all the necessary info to set up the world.
When it comes to the structure of these datastores, I would do something like this:
DataStore["UserSaves"] = {
Save1 = {
SaveName = "Save1", -- The name of the DataStore key
Progress = 54, -- % Progress
CurrentWorld = "Chapter 2", -- Where the player is currently at
LastPlayed = 12244122312312, -- Timestamp of when they last played this save file
},
Save2 = {
SaveName = "Save2", -- The name of the DataStore key
Progress = 13, -- % Progress
CurrentWorld = "Chapter 1", -- Where the player is currently at
LastPlayed = 1254646484656, -- Timestamp of when they last played this save file
}
}
DataStore["Save1"] = {
LastCheckpoint = "Checkpoint_13_L", -- The last checkpoint (you could even make this their last position in the world)
CurrentWorld = "Chapter 2", -- Where the player is currently at
Progress = 54, -- % Progress
LastPlayed = 12244122312312, -- Timestamp of when they last played this save file
CollectiblesFound = { -- All the collectibles data
Collectible1 = false,
Collectible2 = true,
Collectible3 = true
},
DoorsOpen = { -- All the doors data
Door_1_R = true,
Door_2_L = false
},
CurrentPlayerData = { -- Player data
Health = 150,
MaxHealth = 200,
IsHurt = true
}
-- And so on
}
Now, if you are going to be updating this data as the player is playing, then I recommend that you use UpdateAsync()
instead. This one is a little more complicated so you’ll need to research it, but it is much safer when updating specific data in a table. Of course, you could just use SetAsync()
without an issue since you don’t have to worry about data being written from multiple servers.
Also, if you are wondering why I am storing the SaveName
even though I am using it as a key, there are two reasons. One, I do like being able to access the key from inside the array if I just have the value, and two, Savei
might just represent the save location (Save1
might be top left, Save2
top right, Save3
bottom left, Save4
bottom right), meanwhile, the actual save might be different. This will allow you to have the DataStore key independent from the actual indexing key.
Hopefully this helped you even a little when it comes to approaching such system. It is complex, so we can’t really do anything more than theorize methods that would work for your system.