DataStore for RPG experience

I recently started learning about and implementing data stores to my games. I decided to try making an RPG but wanted to know what the best way to structure my data is without repeating the same thing over and over again to get all stats in. I want to be able to store all things that would go under player data like weapon, armor, all inventory things, exp, levels, money, stats, skills, and more like that. If I were to use the data storage system I currently made, it would end up getting very very cluttered in the code. Does anyone have recommendations on a way I can set up a system that better fits my goal? Thank you in advance and here is the current code I am using (just for levels atm).

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local Database = DataStoreService:GetDataStore("Data")
local SessionData = {}


function WipeData(UID)

	if SessionData[UID] then 
		Database:RemoveAsync(UID)
		print("WIPED SESSION DATA FOR " .. UID)
	elseif Database:GetAsync(UID) then
		Database:RemoveAsync(UID)
		print("WIPED STORED DATA FOR " .. UID)
	elseif UID == "All" then
		local pages: DataStoreKeyPages = Database:ListKeysAsync()
		while true do
			local keys: {DataStoreKey} = pages:GetCurrentPage()
			for _, key in ipairs(keys) do
				Database:RemoveAsync(key.KeyName)
			end
			if pages.IsFinished then break end
			pages:AdvanceToNextPageAsync()
		end
		print("All data has been wiped!")
	end

	
end

function PlayerLeaving(player)

	if SessionData[player.UserId] then
		print("Found data for ", player.UserId)
		local success = nil
		local errorMsg = nil
		local attempt = 1
		repeat
			success, errorMsg = pcall(function()

				Database:SetAsync(player.UserId, SessionData[player.UserId])
				print("Data saved as:", SessionData[player.UserId])
			end)
			attempt += 1
			if not success then
				warn(errorMsg)
				task.wait(3)
			end
		until success == true or attempt >= 5
		if success == true then
			print("Data saved for:", player.Name)
		else print(attempt)
		end

	end
end

local function LoadData(player)
	local Data = Instance.new("Folder")
	Data.Name = "Data"
	local character = player.Character or player.CharacterAdded:Wait()
	
	
	local Level = Instance.new("IntValue")
	Level.Name = "Level"
	Level.Parent = Data


	local Success = nil
	local playerData = nil
	local attempt = 1

	repeat Success, playerData = pcall(function()
			return Database:GetAsync(player.UserId)
		end)
		attempt += 1
		if not Success then
			warn(playerData)
			task.wait(3)
		end
	until Success == true or attempt >= 5

	if Success  then

		print("Grabbed data for ",player.UserId,":", playerData)
		if not playerData then
			print("Creating new data...")
			playerData = {
				["Level"] = 1,
			}
		end
		SessionData[player.UserId] = playerData
	else
		warn("Unable to get data")
		player:Kick("Data loading issue. Try again later.")
	end
	--int loading
	Level.Value = SessionData[player.UserId].Level 
	--int sync
	Level:GetPropertyChangedSignal("Value"):Connect(function() 
		SessionData[player.UserId].Level = Level.Value 
	end)
	-- Object loading
	
	Data.Parent = player
end


Players.PlayerRemoving:Connect(PlayerLeaving)
Players.PlayerAdded:Connect(LoadData)
2 Likes

I’d highly recommend checking out ProfileService. It almost entirely prevents data loss for your users (which my game had a huge issue with before I switched) and makes saving your player’s data much easier. It’ll also help your code be less cluttered.

The way I’ve set up my inventory system (for items specifically) uses the item’s name as an index to store the number of that item the player has. Once you retrieve that array when reading the data, you can use the index to find the stats of that weapon in a data module. This way you can store the least amount of data as far as I know.

2 Likes

Thank you for the recommendation! I was planning on using folders with int/string values to identify the data globally by linking the values to data within the data stores. Would this still be possible with ProfileService?

2 Likes

Although it might work, I wouldn’t recommend storing that data using values stored in folders. What you can do with ProfileService, is view the ‘Profile’ (the players data) from anywhere in your game. This allows you to just read the profile instead of actual instances which you’d have to constantly update the values of.

2 Likes

Oh! I looked at the post you referenced and it mentioned only being accessible by the server. Would it be possible to read profiles on the client through local scripts?

2 Likes

What you can do is use a RemoteFunction to request the specific data that you need from the server when you need it

2 Likes

If you don’t mind, could I have a simple example of this?

2 Likes

Alright give me a few minutes I’ll make some example code

2 Likes

On the client:

local GetWeaponsRemoteFunction = ReplicatedStorage:WaitForChild("remotes"):WaitForChild("Inventory"):WaitForChild("GetWeaponData")

local weaponsData = GetWeaponsRemoteFunction:InvokeServer()

for weaponName, v in pairs(weaponsData) do
	print(weaponName)
end

On the server:

GetWeaponsRemoteFunction.OnServerInvoke = function(player)
	local profile = Manager.Profiles[player]
	
	return profile.Data.Inventory.WeaponsInventory
end

The Manager here is a module you can use to get each player’s profile.

2 Likes

Alright I really appreciate the help! I’m going to look more into the ProfileService and try to use it for my game!

2 Likes

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