As I moved from Godot and Unreal Engine 5 to Roblox(beacuse it’s easier to make games and earn some money out of it) I am trying to make a game in it with the new scripting language and Studio. I am trying to achieve a datastore saving system that saves as the title says every existing tool in the player’s backpack like thegame called “Fisch” or the new game “Beaks” is doing. I have watched many tutorials but they didnt help me with this beacuse they were using the types of tools saving system or something like that, there was a folder in serverstorage that had tools that needed to save. I am making the Beaks like style game that need the “advanced” type of saving system so it will save like the weight mutations and rarity of the tool that i have.
Any help will be appreciated.
I’m going to be honest but it does sound somewhat hard to me and my explanations might not be the best but the method might not work for you because I believe those tutorials save tools based on matching names from a folder (static tools). But Beaks/Fisch style games generate unique instances with varying properties
From what I know so far, I believe you could store and recreate attributes, not just values in scripts, as Attributes are native to Roblox and easier to serialize and you can simply just use GetAttributes()
and/or SetAttribute()
Thank you for your responce, I wanted to ask, how to do that beacuse I am using luau as a scripting lanugage for like 2 weeks from now and I dont know some thing.
I believe you could loop through the tools in the player’s backpack and then store each tool’s name and attributes in a table AND then (sorry for repeating and then) save that table with DataStore:SetAsync()
, as so, when the player joins, the GetAsync()
gets on, recreating the tools with their saved attributes
I’m as honest as possible saying that in 2 years since I started scripting I barely know anything of this kind.
But still, here is a simple example:
local function ToolSave(player)
local toolsData = {}for _, tool in ipairs(player.Backpack:GetChildren()) do if tool:IsA("Tool") then table.insert(toolsData, { Name = tool.Name, Rarity = tool:FindFirstChild("Rarity") and tool.Rarity.Value, Weight = tool:FindFirstChild("Weight") and tool.Weight.Value }) end end return toolsData
end
Thank’s mate, I will do best out of this example(if its working of course) Oh and about scripting, I am 9 to 10 years now in it soo I have maybe bigger knowlege about it but definely not about luau. Maybe in this post there will write soemone more experieced in this scripting laguage. This will not only help me but any fellow developers that want to make a system like this. Anyways I will try to make something out of this, if it will work I will post it here so everyone can see it and make that kind of saving system.
You’re totally much more experienced and skilled than I, I hope you’ll have no trouble sorting this out!
This is something that is easily possible using my player save library DMOP.
Defining a structure like this:
Means that all Players have a directory called EquippedItems, which is an associative array.
Items in that array have an array called TagsArray, a number called NumberMutation, and a string called DisplayName.
With a system like this, you only have to worry about using/updating the data stored within the Player, and it will be persistent with DMOP.
My system also supports Data Types, which can make your DataModel look nicer (and easier to use). It also supports save structure conversion if you ever want to change the layout of your save structure without wiping player data.
It is very easy to work with, I’d suggest at least giving it a try (I’ll help you if you need)
Sorry for a late resonce but I was working in my garden. Can you tell me more about it and how to set it up to support this type of data saving? I actually made some big progress in 30 minutes that i was working on it, here is what i got now it is saving it but not perfectly so the modifed values, names and most of the things like the offsets isnt saving now:
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local CollectionService = game:GetService("CollectionService")
local ToolStore = DataStoreService:GetDataStore("AdvancedDynamicToolSave")
local function vec3ToTable(v) return {v.X, v.Y, v.Z} end
local function tableToVec3(t) return Vector3.new(t[1], t[2], t[3]) end
local function cframeToTable(cf)
local components = {cf:GetComponents()}
return components
end
local function tableToCFrame(t)
return CFrame.new(unpack(t))
end
local function serializeInstance(obj)
local data = {
ClassName = obj.ClassName,
Name = obj.Name,
Properties = {},
Children = {}
}
if obj:IsA("BasePart") then
data.Properties.Size = vec3ToTable(obj.Size)
data.Properties.Color = {obj.Color.R, obj.Color.G, obj.Color.B}
data.Properties.Material = obj.Material.Name
data.Properties.CFrame = cframeToTable(obj.CFrame)
data.Properties.Anchored = obj.Anchored
data.Properties.Orientation = vec3ToTable(obj.Orientation)
end
local mesh = obj:FindFirstChildOfClass("SpecialMesh")
if mesh then
data.Properties.Mesh = {
MeshType = mesh.MeshType.Name,
MeshId = mesh.MeshId,
TextureId = mesh.TextureId
}
end
for _, child in ipairs(obj:GetChildren()) do
if child:IsA("Instance") and not child:IsA("Script") then
table.insert(data.Children, serializeInstance(child))
end
end
return data
end
local function serializeTool(tool)
local toolData = {
Name = tool.Name,
Attributes = tool:GetAttributes(),
Values = {},
Structure = {}
}
for _, child in ipairs(tool:GetDescendants()) do
if child:IsA("ValueBase") then
table.insert(toolData.Values, {
ClassName = child.ClassName,
Name = child.Name,
Value = child.Value
})
end
end
for _, child in ipairs(tool:GetChildren()) do
if child:IsA("Instance") and not child:IsA("Script") then
table.insert(toolData.Structure, serializeInstance(child))
end
end
return toolData
end
local function recreateInstance(data)
local inst = Instance.new(data.ClassName)
inst.Name = data.Name
local props = data.Properties or {}
if inst:IsA("BasePart") then
if props.Size then inst.Size = tableToVec3(props.Size) end
if props.Color then inst.Color = Color3.new(unpack(props.Color)) end
if props.Material then inst.Material = Enum.Material[props.Material] end
if props.CFrame then inst.CFrame = tableToCFrame(props.CFrame) end
if props.Anchored ~= nil then inst.Anchored = props.Anchored end
if props.Orientation then inst.Orientation = tableToVec3(props.Orientation) end
if props.Mesh then
local mesh = Instance.new("SpecialMesh")
mesh.MeshType = Enum.MeshType[props.Mesh.MeshType]
mesh.MeshId = props.Mesh.MeshId
mesh.TextureId = props.Mesh.TextureId
mesh.Parent = inst
end
end
for _, childData in ipairs(data.Children or {}) do
local child = recreateInstance(childData)
child.Parent = inst
end
return inst
end
local function recreateTool(data)
local tool = Instance.new("Tool")
tool.Name = data.Name
for key, value in pairs(data.Attributes or {}) do
tool:SetAttribute(key, value)
end
for _, valData in ipairs(data.Values or {}) do
local val = Instance.new(valData.ClassName)
val.Name = valData.Name
val.Value = valData.Value
val.Parent = tool
end
for _, objData in ipairs(data.Structure or {}) do
local part = recreateInstance(objData)
part.Parent = tool
end
return tool
end
local function saveTools(player)
local backpack = player:FindFirstChild("Backpack")
if not backpack then return end
local serialized = {}
for _, tool in ipairs(backpack:GetChildren()) do
if tool:IsA("Tool") and CollectionService:HasTag(tool, "Echo") then
table.insert(serialized, serializeTool(tool))
end
end
pcall(function()
ToolStore:SetAsync(tostring(player.UserId), serialized)
end)
end
local function loadTools(player)
local backpack = player:WaitForChild("Backpack")
local success, data = pcall(function()
return ToolStore:GetAsync(tostring(player.UserId))
end)
if success and data then
for _, toolData in ipairs(data) do
local tool = recreateTool(toolData)
tool.Parent = backpack
end
else
warn("No tools loaded for", player.Name)
end
end
Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function()
task.delay(1, function()
loadTools(player)
end)
end)
end)
Players.PlayerRemoving:Connect(saveTools)
That looks good! I do have some confusion over why you are storing the actual instance properties of the parts. Typically you have a list of cloneable items, and when you load them, you can adjust their properties based on the modifiers the item has.
Setting up DMOP is very simple. You just place the script in ServerScriptSevice
, and then create a Folder called DataModel
in ServerStorage
.
All Folders/BaseValues in the DataModel will correspond to directories within the Player. This is a little hard to wrap your head around when you write it out, but let me show you:
If you set up the DataModel like this (DaysPlayed is a NumberValue):
All Players will have this structure as well (they take the default values from DataModel):
To make it so that players can have many items with modifiers, you need an Array, not just a static directory. You specify that like this (using the name “key” in this case):
So now PlayerItems is an array within the Player. The player spawns with an empty array, which looks like this:
The server can just add Folders into the PlayerItems in the Player, and they will save:
Side Note
I want to differentiate between using the names "key" and "value" for the array types. Tables in lua have this structure:
local tbl = {
key = value
}
When you give the folder the name “key”, that means the folder name will represent a key in the array. Likewise, the name “value” means that the folder name will represent a value in the array.
So we used “key” because we want the value of the element to be a table (that’s what Folders represent).
Side Note End
Now we just have to structure the data we want to save for each item in the DataModel. You can do this for whatever data you may need to save. I don’t suggest saving the instanced values (Such as CFrames), but DMOP does supprt the core ones (CFrame, Vector3, Color3).
Your structure might look something like this (i did not fully populate it, for brevity):
Now here is the meaty part that you were really asking about. How do you give Players these items?
It would be inconvenient to make these folders yourself, So DMOP provides data interfacing through the Interface
module. Giving a Player one of these items would look something like this:
local Interface = require(game:GetService("ServerScriptService"):WaitForChild("DMOP"):WaitForChild("Interface"))
local PlayerItemDataModel = Interface.GetDataModelDescendant("PlayerItems").key
local ItemProperties = {
Tags = { "Tag1", "Tag2", "Tag3" },
InstancedData = {
CFrame = CFrame.new()
-- Leaving Color unset will give it an empty color (which is the default value for Color3)
},
DisplayName = "Some Display Name"
}
local InstancedItemData = Interface:TableToModel(ItemProperties, PlayerItemDataModel)
-- you can pass this as a third parameter to TableToModel
InstancedItemData.Name = "KeyInTheArray"
InstancedItemData.Parent = plr.PlayerItems
How the Player looks after running that code:
DMOP also provides the reverse function ModelToTable
to turn one of these Directories into a Table with the same structure you’d give to TableToModel
. With this, you have all of the information you need to save/load/modify these items.
The way you actually do it is extremely similar to your current code, just without interacting with the DataStore at all. You have access to the Player Data within the Player instance itself.
Consider using ModelToTable
to obtain a table of all deserialized player items (Roblox instances are properly deserialized i.e. the CFrame folder becomes a CFrame instance).
How do I do initial loading?
If you need to verify that a player’s data has finished being loaded, either use the ClientLoadedEvent
ObjectValue as a RemoteEvent or bind to the Loaded
bindable.
How to make PlayerItem a DataType instead?
You might want to make the PlayerItem be a DataType, which makes the code and DataModel look nicer.
To do this:
-
make a folder in
ServerStorage
called whatever you want, preferablyDataTypes
. -
Set the
CustomDataTypeHolder
ObjectValue equal to the folder you just made (the ObjectValue is a child ofDMOP.Interface
). -
Copy the
DataTypeTemplate
next to the ObjectValue, and put it in your DataTypes folder. Name it the data type you want in all lowercase. -
Now you just have to put all children of
PlayerItems.key
into that new template module, like this:
Now that playeritem
is a valid Data Type, you can refactor DataModel
like this:
This is much nicer because now:
-
PlayerItem
is a re-usable data type. - You won’t have to dig through
DataModel
to modify it - it makes the instancing code nicer because you don’t need a reference to the DataModel:
local Interface = require(game:GetService("ServerScriptService"):WaitForChild("DMOP"):WaitForChild("Interface"))
local ItemProperties = {
Tags = { "Tag1", "Tag2", "Tag3" },
InstancedData = {
CFrame = CFrame.new()
-- Leaving Color unset will give it an empty color
},
DisplayName = "Some Display Name"
}
local InstancedItemData = Interface:TableToModel(ItemProperties, "PlayerItem")
-- you can pass this as a third parameter to TableToModel
InstancedItemData.Name = "KeyInTheArray"
InstancedItemData.Parent = plr.PlayerItems
Conclusion
I hope this mega-long post helps you, if you do decide to use DMOP. Im always open for questions, or even helping you get it set up if you decide to use it.
I use it literally every single time I need to save client data because I absolutely hate interacting with the DataStoreService (and I made it so I’m biased).
Thanks for the tutorial mate! I am confued about it to be honest. Like I tried to make the datamodel then the playeritems and the key, it isnt showing in the player instance. And i am cofused how it would save every proerty of every item in the backpack, can you please tell me how it is doing and mayeb give me a script that just makes it so i can test it and maybe modiy it by myself?
Yes. I will make a test place with this functionality and send it to you. Give me about an hour.
It is strange that nothing is appearing in the Player. Make sure your structure looks like ServerStorage > DataModel > PlayerItems
The name of DataModel
is case-sensitive. And DMOP should be in ServerScriptService
.
Also check the console to see if there is an error relating to restricted module
, Someone else had that issue and I have not been able to find a good fix…
Here is the place with basic functionality:
TestBackpackItemSaving.rbxl (84.9 KB)
Make sure you save the place and enable Studio Access to API Services option in Game Settings.
It has a custom backpack controller which uses the name of items in the Backpack as their slot ID. So to change slots you just rename the Instances in Backpack
and Hotbar
.
(Hotbar
is for storing the information associated with items in the Backpack
, because the Backpack
holds tools)
When you join, you will be given a debug item in the first slot. You should stop the simulation after you recieve it and comment out the following line of code (TestGiveHotbarItem line 42): CreateDebugHotbarItem(plr)
.
Then you can test out moving the item between slots, rejoining and stuff.
To access information associated with a slot in the Backpack, you can access the Hotbar
folder. That is how you can get custom images/text to appear with the custom Backpack UI
Yea it works but i meant that it should save every tool and its properites from the backpack not from a folder like roblox’s Fisch or Beaks do. Anyways thank you.
Look here, I mean in that way like it is on my screenshot, when i leave the items and properties of it saves.
It is unlikely that any game will store the raw information of a tool directly in the tool itself. This is because when an item is stored in the Backpack, it is not guaranteed to be in the Backpack at all times.
You can use a structure like how I sent you, and still have those properties persist. It is all about the data that you save. I sent you a very rudimentary template to expand on, and it is not incompatible with what you have in mind.
For example, you should store the custom properties of your item in PlayerItems
. When you instance these tools, you can make those modifications. I even commented a part of the TestGiveHotbarItem
script to signal where you should do that at
Yea but I mean store the enitre tool or the binary code of it in soem kind of a database, i made something like that in my script that just needs to store more and better inforamtions about the tool, is it possible with your system?
You can store all of the information that you need. DMOP is made to be easily expandable. What in particular do you have in mind that seems like it might be incompatible?
Okay so look on my screenshot, i want to store all of the data that there is, the localscript in it changes the look, the name and the wieight in the name + mutation for the tool. So now how to use your system to store this data? Remember that if i shoot down a new this type of npc’s are called "echoes’ in my game are allways diffrent so it needs to store unique data for every of the new “echo”. Can you please show me how to do it?
Take a look at this, I updated the place I sent with a new data type called TypeData
. Look at the changes I made to the DataModel and DataTypes directories.
TestBackpackItemSaving.rbxl (86.0 KB)
I made it so that a PlayerItem also saves a child called TypeData. Look at how the data is instanced when the player joins the game.
I don’t suggest storing the data in the tool like you are currently doing, but i have provided an example in the place on how you would load that data into the tool with DMOP
and this is why i dont suggest it:
because then you have the information in multiple places where it’s really only needed in one place.