I need some help with structuring my code and framework of my game. For example it could be anything but in my situation im making a project with many unlockable abilities and different magics.
What ive been doing is having the players inventory like this:
–Main folder in server storage with the players name.
–A folder that has owned moves
–A string value which has the players magic stored
–A folder which has the players items collected
And whenever a player unlocks a move it creates a string value which has the moves name as the value. And when the player leaves the data stores creates a table and puts all the values in it. Same thing happens with the items except i put the items name in a separate table and when the player joins it gets those names and clones the items to the players inventory.
(This script is on the server)
But it feels unreliable and inefficient and I want the players magic value to be visible to the client without it being tampered with.
Im not asking for someone to completely make something for me im just asking for ideas to give me a better visual on how to structure it. If anyone has some ideas on what i could do better and if anyone has their own examples it would be really helpful. Sorry for asking for too much.
I’ve seen a lot of developers use this pattern where they unpack a table of data from datastores into a hierarchy of folders and value object instances, and then iterate through all the folders with GetChildren or GetDescendants to build a Lua table to store them back to the datastore.
But why? The only benefit I can think of is that it allows visually inspecting the user’s data when debugging in Studio, without having to code debugging UI to display the content of the data. For complex user data though, this is a huge amount of overhead on the server. What functionality does unpacking into value objects get you? I’m generally curious what the use case is here, and to see if there is perhaps another, lighter weight solution that meets your needs.
In my own games, I don’t use value objects. All user data on the server, including what comes from datastores, I keep in Lua tables, usually a dictionary where the keys are either playerId or the player instance, depending on whether or not the data needs to be maintained across player disconnect/reconnect.
To be honest i dont really know, its my first time making anything more than a simple value cash value save on data stores. Is there a benefit to using lua tables over instances?
forget to mention other scripts can access the value instances so its easier to keep track.
I also do not use ValueBase objects for my data, however, I personally think it is fine if people do use it.
Although obviously more expensive than tables, data that is only periodically being read and written to should have little/no visible performance issues in a real game environment. The main benefit of using this pattern is because it’s very easy - it has automatic persistence and replication for all the clients, something that is very tricky to do for most people and has numerous ways of handling them internally.
The only performance issue I can reasonably think of is when it comes to data that is constantly being updated very fast, in which case it is better to use attributes, or like you said, dictionaries, which in this case if you’re doing something like this, you might as well stick to a table-based data structure for all your data.
The biggest issue with using Instances is the very hard support for intellisense. If you are trying to get your script to autocomplete, the easiest way is to pre-build your leaderstats or your data folder and clone it. However, accessing it from anywhere else without the use of tables will lose it’s intellisense support.
I recommend you use ModuleScripts to store your data instead. You can create tables inside them to store your information and then create “Getter” and “Setter” functions to get and set data in said tables. You can then use ServerScripts or LocalScripts to write to and read from the tables in the ModuleScript using the created functions. Keep in mind though that data written to module scripts does not replicate.
But then again, the OP is unpacking into ServerStorage, rather than ReplicatedStorage, so that might not be a concern. As I understand it, the main reason developers used to unpack data stores into value objects was for the server-to-client replication, before RemoteEvents were a thing. Now, of course, RemoteEvents are the best option and give you lots more control over what replicates, when, and to whom.
Ah, just saw this point, apologies. In this case, it would be better to use dictionaries like @EmilyBendsSpace has said, and use @player356377’s suggestion of using setter and getter functions. You’re already losing one of the main important uses of Instances which is client persistence and replication.
However, there is no point in hiding it from the client for the sake of “tampering protection.” Clients can easily change any data on the client anyways, but only for themselves, and will be the same with both cases. The main important thing is you don’t let the client control the server.
It’s mostly a personal preference thing. I prefer using RemoteEvents and ModuleScripts, mainly due to the abstraction they provide. ModuleScripts let you conveniently define getter and setter functions minimizing the chance of bugs in code interacting with stored data. There is no difference between using a RemoteEvent and placing value instances in ReplicatedStorage. What makes RemoteEvents beneficial in my opinion is the fine control over what you want to replicate.
Do you want the client to access it? If not, store it in a server container.
The server and client does not share the same environment when you require a ModuleScript. You may have functions only the server can use, it would make sense to store it on the server.
If you have confidential information like an API key or even DataStore names, store it on the server so the client is unable to read those information.
You don’t have to, it’s just a suggestion. If you don’t understand how to use OOP, that is completely fine. Stick to a single ModuleScript that handles data with getter/setter functions.
Example:
local DataManager = {
Stored = {}
}
function DataManager:addPlayer(player: Player)
-- get data via DataStore
DataManager.Stored[player] = loadedData
return loadedData
end
function DataManager:removePlayer(player: Player)
-- maybe do save operations
DataManager.Stored[player] = nil
end
function DataManager:setGold(player: Player, amount: number)
if DataManager.Stored[player] ~= nil then
DataManager.Stored[player].Gold = amount
end
end
function DataManager:save(player: Player)
-- perform save operation
end
do
local function onPlayerAdded(player: Player)
local loadedData = DataManager:addPlayer(player)
if loadedData == nil then
player:Kick("Failed to load data. Please try again in a few seconds.")
end
end
Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in Players:GetPlayers() do
task.spawn(onPlayerAdded, player)
end
end
return DataManager
Use ProfileService, it’s one of the most commonly used DataStore modules people use simply because of how versatile, easy, and effective it is. It will do the complicated stuff for you while you can focus on the more important things. I’ve made many magic/skill systems with it and it’s my go to. Let me know if you need help with it.
If you’d like to keep using ObjectValues, you can definitely work it in with ProfileService. I would connect a .Changed to each Value and update the profile data directly after.