How to use module scripts for player data?

So my current setup for player data is a folder in server storage and when a player joins I add a folder with there name in it and whenever they buy something from my games shop I add a value to their folder and like this works although after browsing the devforums I found out that doing player data in module scripts and stuff is better although I’m still a bit new to module scripts and not good with them yet so I don’t know how complicated it is to use module scripts for this.

Although it seems like doing this is better as I’ll be able to access the playersdata from anywhere as it’s in a module script and there’s probably other benefits as well.

So if someone could kindly explain to me the basics of how to go about doing this that would be great and maybe some simple examples to get started :slight_smile:

2 Likes

I used the example given by Quotetory:

It works perfectly for my needs.

2 Likes

There are lots of examples, but I recommend that you have very good knowledge of tables first before switching to a ModuleScript system, because it can get confusing pretty fast if you don’t know what you’re doing. This entire workflow is based on tables/metatables

A player data module can be quite simple if you don’t need changed events. However, making one with changed events isn’t very complicated either. If you want to have easy access to the player’s data from other scripts and have a changed event that automatically fires when data is changed you can use interface tables that other scripts use to access data. These interface tables can fetch the values from the actual stats or change them and fire events. This is achieved by using a metatable.

Here’s an example player data modulescript meant to be used in server scripts. I have tested it a little and it seems to work. I didn’t include an event, but I wrote where to fire events.
For the client you could make something similar, but you won’t need everything in this on the client. For example, the client probably won’t set data, so the __newindex might be useless on client. You may also need to add something that the serverside modulescript doesn’t have to the client.

local function createStats() -- used to create a new stat table with default values for each player
	local defaultStats = { -- store the statNames as keys and default values as values in this table
		statName1 = defaultValue1, 
		statName2 = defaultValue2,
		-- and so on
	}
	return defaultStats
end

-- the stat handling system is below
local playerStatInterfaces = {} -- tables stored here are used for interacting with a player's stats. This is the return value of the module

local statList = {} -- tables stored here have the actual stats of the players

-- this table is set as the metatable for every playerStatInterface table
local playerStatInterfaceMt = {}

-- the function set as the __index metamethod runs when the table that has playerStatInterfaceMt as its metatable is indexed and the key/index
-- (statName in this situation) isn't found in that table. Because none of the stats are stored in interfaces,
-- the function below always runs when fetching a player's data.
function playerStatInterfaceMt:__index(statName)
	return statList[self[1]][statName] -- self[1] is a reference to the player, which is the first and only value in the interface table
end

-- __newindex metamethod is similar to __index but this function runs when trying to add a new key/index to the table that has
-- playerStatInterfaceMt as its metatable. The function below always runs when setting the value of a player's stat and in the function
-- the values are set to the actual stat table, not the interface
function playerStatInterfaceMt:__newindex(statName, value)
	local plr = self[1]
	-- possibly store the old value to a variable here
	statList[plr][statName] = value
	-- custom changed events can be fired here.
	-- Old value can be saved to a variable before changing it so that it can be given as an argument to :Fire(), if necessary.
end

-- these are used to create and remove stats and interfaces for players (probably only when they join and leave the game)
function playerStatInterfaces:Add(plr)
	-- create a new stats table and store it to the statList that isn't directly accessed by other modulescripts/scripts
	local stats = createStats()
	statList[plr] = stats
	-- the table below is the table that other scripts get when they want the stats of the player.
	-- It accesses the statList with the metamethod functions. The player is used in the metamethod functions __index and __newindex
	local playerStatInterface = setmetatable({plr}, playerStatInterfaceMt)
	self[plr] = playerStatInterface
	return playerStatInterface -- returning it in the this function isn't really necessary because it can be easily accessed otherwise
end

function playerStatInterfaces:Remove(plr) -- call this when the player leaves to prevent memory leaks
	playerStatInterfaces[plr] = nil
	statList[plr] = nil
end

return playerStatInterfaces

To replicate the changed event to the client, you might want to connect a function to your changed event and in this function, fire a remote event with information about the change. (or directly fire the remote event instead of firing any custom events if you don’t need to react to the changes in other server scripts.)

On the client, you could connect a function to the OnClientEvent of this remote and in that function, fire the changed event on the client. (or just listen to the OnClientEvent in every local script or module script that does something when the values change.)

If you want to read more about metatables and metamethods, there’s a Developer hub article about them. https://developer.roblox.com/en-us/articles/Metatables

6 Likes

I know I’m still new to all of this and have a bit of learning to do, but how do I actually use this? :thinking: (I know there are like 100 comments in it)

For the top part of the code you sent would I set it up like this?

local function createStats() -- used to create a new stat table with default values for each player
	local defaultStats = { -- store the statNames as keys and default values as values in this table
		Gold = 0, 
		Essence = 0,
		Caps = 0,
		Dimension = 0,
		Level = 0,
		Sodas = {
			--put owned sodas here?
		},
		Upgrades = {
			--put owned upgrades here?
		},
	}
	return defaultStats
end

And then for my games shop if the player wanted to buy an item do I just use a function to get this data and then I can add an item to the sodas table or upgrades table?

And then what about changing the values like gold for a example do I just make a function that changes it or would I use a script that has uses the function to get the players data and then I can increase or decrease the value.

Sorry if I sound stupid I’m horrible when it comes to coding terminology, but hopefully you understand what I’m asking

1 Like

The code you posted looks correct. To access the player’s data in another modulescript or script you first need to require this modulescript and store the return value to a variable. The return value of the module is the table that has the interface tables for each player. To get the interface table of a spesific player you index the returned table with that player. Remember to add the player to the module’s tables by using the :Add method of the return value of the module
To change it you set a value to that table with the statname as the key. The value won’t really be put into the interface table, but instead the actual data table. To get a value, you just index the interface table with the statname. It isnt’t in the interface table, but it will be fetched to you by the index metamethod.

Here’s some example Code:

local playerStatModule = require(--[[reference to module]]) -- give the modulescript Instance as the argument to the require

local player = -- reference to a player
local playerStats = playerStatModule[player] 
 
print(playerStats.Gold) -- Prints the value of the player's gold stat
playerStats.Gold = 5
print(playerStats.Gold) -- prints 5, because it is now the value of the goldstat
playerStats.Essence = 1
print(playerStats.Essence) -- prints 1

To add a player when they join, and remove them when they leave, you can use the PlayerAdded and PlayerRemoving events. Example:

local Players = game:GetService("Players")

local playerStatModule = require(--[[reference to the modulescript here]])

Players.PlayerAdded:Connect(function(player)
    playerStatModule:Add(player)
end

Players.PlayerRemoving:Connect(function(player)
    playerStatModule:Remove(player)
end

You could also use a simpler system with no metatables if you don’t need to have any changed events.

2 Likes

ahhh alright cool thanks. This is still defiantly a bit confusing to fully understand, but
I’ll experiment with this stuff and hopefully I’ll figure out as I’d like to try and
start using this stuff instead of using folders and object values or int values as I feel like it would be much better and probably more professional(?) lol.

Although is there even a difference? Like if I were to continue doing it my way instead of using modules would it like have any serious impact on a game?

I don’t think using value instances instead of modules would have any serious impact on the game and it could make some things even easier.
Edit: Actually, when reading the code you posted, I realised that the module I posted might not completely work for you. It has nothing that can be used to add changed events inside values that are tables. So, for example, if you wanted to detect changes inside the Sodas table, you would need to either manually fire a changed event in the script that changes it or add something new to the module. So using value instances might be a good idea if you need to listen to changes and you don’t know how to do it properly with a modulescript stat system.