Load/save progress System

Hello everyone! :wave::slightly_smiling_face:

I’ve been playing some games (including Portal, Portal 2) and there was one system I would love to have in Roblox but I couldn’t achieve it. The system where I’m talking about is a load and save progress system. I will explain it below:

  1. A player (for now I call him Bob) enters the game.
  2. Bob waits until the loading screen is gone and he comes to a menu that shows some buttons: Continue game, Load game, New game, and Options.

When Bob clicks on Continue game, the game will immediately send him to another place in the chapter where he left the game the last time he played.
In other words: the game will load the last saved slot.

When Bob clicks on Load game, the menu will disappear and shows a menu with all the save slots. When Bob clicks on a save slot (for example “Chapter 2”) Bob gets teleported to another place in Chapter 2 of the story.

When Bob clicks on New game, the game will immediately send him to another place and the story will start all the way at the beginning.

Now, this idea may sound pretty complicated (for me), but when it comes to datastores, I have no idea where to begin (.__.).

5 Likes

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.

6 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.