Note: Alternatively, the server could just track all of the instances that a player creates at runtime; this would allow you to skip step (1) of this section, and possibly even (2) depending on how you store the data at runtime.
Getting parts inside of a part using WorldRoot::GetPartBoundsInBox() or WorldRoot::GetPartsInPart() - found here and here respectively.
You would need to be able to serialise the Instances within the region to be able to save them to the DataStore, you can find examples of this in #resources:community-resourcese.g. example found here
You may need to consider compressing the data depending on how many instances you’re expecting to save per player - some of these techniques were discussed in a thread I’d commented on previously here
Loading
Read the data from the DatastoreService
Deserialise the data
Iterate through the data and instantiate it again
Examples
One example that I often see cited in these threads relates to Egomoose’s placement system, that can be found here.
Their tutorial and example walks through some of these steps, namely: placing objects, serialising objects and saving them to the datastore.
Every part has these descendants in it (along with a script that gets put inside each part when the game runs). Does the serialiser store this? Would this break any of the following rules for the serialiser?:
Have a instance property value with more than 255 Characters.
In all honesty I will need everything about datastores explained to me. I am a complete fool at it. I don’t know how to store a table in a datastore, I’ve heard it was impossible?
Based on your picture I had assumed you were making a building game where users could place any arbitrary instance and modify them in-game, similar to Studio’s build tools.
However, this sounds more like you’re letting users place prefab models/instances that you already have stored in ReplicatedStorage? If this is the case then you haven’t got a need for an instance serialiser - you would just need to:
Store some reference number | string of the object they’ve placed from ReplicatedStorage;
Its location within the placement area;
Any other properties they’ve changed that you need to record
This is still technically serialisation but you wouldn’t need to serialise the entire instance.
Since you already know how that instance and its components are put together as they’re already present within your game, you can just insert them again when loading and update the properties to match the saved data.
This sounds fine, except for (3) as it very much depends on what you mean by this exactly?
There are some properties of instances that aren’t able to be set at runtime, e.g. you can’t change the MeshId property of a Instance<MeshPart> at runtime; details of that issue here
When a player leaves, loop through all the items placed within the plot and save the item name as well as the CFrame of the item in object space relative to the plot. Here is a script to give you a rough idea:
local HttpService = game:GetService("HttpService")
local plotInstance = workspace.Plot -- The base of the plot
local plotItems = plotInstance.Items -- Folder which contains the items
local function serializeCFrame(targetCFrame: CFrame)
local success, response = pcall(function()
return HttpService:JSONEncode({targetCFrame:GetComponents()})
end)
return (success and response or {0, 0, 0})
end
local function deserializeCFrame(serializedString: string)
local success, response = pcall(function()
return HttpService:JSONDecode(serializedString)
end)
return (success and CFrame.new(unpack(response)) or CFrame.new())
end
local function savePositions()
local itemOffsets = {}
for _, currentItem: BasePart in plotItems:GetChildren() do -- Loop through items
table.insert(itemOffsets, {
Name = currentItem.Name, -- Save name
Offset = serializeCFrame(plotInstance.CFrame:ToObjectSpace(currentItem.CFrame)) -- Save offset in object space relative to plot base
})
end
return itemOffsets -- Now you can save the returned table to data store
end
local function loadPositions(offsetInfo: {[number]: {Name: string, Offset: CFrame}}) -- Fetch the stored data and pass in the data
for _, data in offsetInfo do -- Loop through table
local clonedItem = ...:Clone() -- Clone the part based off of saved name
clonedItem.CFrame = plotInstance.CFrame * deserializeCFrame(offsetInfo.Offset) -- Position it based on the saved offset
clonedItem.Parent = plotInstance.Items -- Parent it
end
end
That’s quite a broad subject that would probably take up a thread on its own, sadly.
An example that I still think would be helpful for you - especially because you seem to be using prefabricated instances - is to take a look at Egomoose’s placement system guide. This guide includes information on how to save information like this. Again, that’s found here.
Storing a table isn’t particularly difficult. In fact, that’s one of the datatypes they expect you to use when calling ::SetAsync - references to that can be found here. Though it should be noted that, ideally, you should be using ::UpdateAsync as defined here.
That being said, I have seen people have issues with it. Though, the common pitfall for beginners here is that they don’t realise that you have to serialise the data found within the table. For example, you can’t save Vector3 or Color3 types, so you have to serialise those to a format DatastoreService will accept.
In the case of Color3 types, examples of serialised data could be a table containing each component or a hex triplet string etc.
You will be best served by Roblox’s own documentation when looking for more information on Datastores. Though, I’m sure there may also be some good tutorials in #resources:community-tutorials if you needed a bit more help.
Either way, I would recommend taking a look at the following create.roblox.com articles available:
“Usage Guide for Data Stores” information found here
P.S. If you’d prefer to read through example code, or use someone else’s library, a good example of a wrapper around DatastoreService is ProfileService - that can be found here.
Sorry I didn’t brief you on the game earlier. It’s a merge game where you drag around the periodic table of the elements and merge them together to get new elements. You are correct by saying I have models of everything already in ReplicatedStorage. So I would just have to save the part name and position then clone a new part and put it in that same spot when loading in?
I’m still really confused on the serialisation thing. How would I save it to the data store then load it, and then load that data to the player’s plot?
Edit: I don’t really need to load CFrame values (such as orientation, etc), just the position. This should make it so I only need to store the X, Y, and Z values.)
local TablePart = {}
for _, Part in pairs(PlotPartLocation:GetChildren()) do
TablePart[Part.Name] = Part.Position
end
Datastore:SetAsync(Id, TablePart)
and that on player added
local TablePart = Datastore:GetAsync(Id)
for Part, Position in pairs(TablePart) do
local Clone = ReplicatedStorage:FindFirstChild(Part.Name, true)
Clone:Clone()
Clone.Parent = PartPlotLocation
Clone.Position = Position
end
Of course it is just an exemple, your Datastore saving system should be better as that.
The only problem in there is that the position will not be the same depending of the player plot, like if the player didn’t have the same plot location than his previous one, then the part will be at the other plot location and not at your current one.
So you can use attachments to fix it, like you can put an attachment for each object into the center part of the plot (where all object are merged), then change attachments name to the reference object name and save their name/position instead of the parts, then when loading create new attachments at the center part of the plot, position them, and clone objects using attachments name and place them at the attachments world position.
In short, save attachments instead of parts, and load, create, rename and place attachments at first which will allow you to clone and position object at the right position in your current plot location.
There are some improvements that you can make here as well, for example, you could give each of these models a corresponding integer value in a module; and then when saving/loading you would match the model / number. This means you would only have to save an integer rather than a long string, and you could update the model names in the future without breaking the save data for players who saved the old model name etc.
That makes things slightly easier for you. The simplest way to serialise a Vector3 would be to convert it to a table containing a value for each of its XYZ components.
Following your question about serialising Vector3 values, you could do something like this:
PLEASE NOTE: In all honesty, you really are better off looking at the links I sent above; especially the tutorials and documentation by Roblox. Though, ideally, you should be looking at Egomoose’s example guide alongside it as a point of reference - it covers the majority of this question so please do look at them!
The example I’m giving you here is just thrown together, this could be optimised and as mentioned previously, this information could be held by the server at runtime instead of serialising at the end; be warned!
Example code
----------------------------------------------
-- --
-- Known Datatypes --
-- --
----------------------------------------------
-- i.e. a table containing known datatypes
-- that we want to be able to serialise
-- or deserialise when saving data
local types = { Color3 = 0, Vector3 = 1 }
-- i.e. a table containing a list of known
-- models that can be represented as
-- an integer value
local prefabs = {
[0] = {
Name = 'ModelName',
Source = nil, -- e.g. game:GetService('ReplicatedStorage').SomeModelInstance
},
}
----------------------------------------------
-- --
-- Transforming Datatypes --
-- --
----------------------------------------------
-- i.e. a set of functions to both serialise
-- and deserialise data for each of the
-- desired datatypes as defined in `types`
local transformers = { }
local color3Transformer = { }
transformers[types.Color3] = color3Transformer
local vector3Transformer = { }
transformers[types.Vector3] = vector3Transformer
-- serialise/deserialise a `Color3` to and from a `table`
function color3Transformer.serialise(col)
return { _t = types.Color3, _v = col:ToHex() } -- represents a color in a string hex triplet form
end
function color3Transformer.deserialise(col)
if typeof(col) ~= 'string' then
return Color3.new(1, 1, 1)
end
return Color3.fromHex(col)
end
-- serialise/deserialise a `Vector3` to and from a `table`
function vector3Transformer.serialise(vec)
return { _t = types.Vector3, _v = { vec.X, vec.Y, vec.Z } }
end
function vector3Transformer.deserialise(vec)
if typeof(vec) ~= 'table' then
return Vector3.zero
end
return Vector3.new(vec[1], vec[2], vec[3])
end
----------------------------------------------
-- --
-- Serialising Datatypes --
-- --
----------------------------------------------
local serialise
function serialise(val, seen)
local t = typeof(val)
seen = seen or { }
local value = t ~= 'nil' and seen[val] or nil
if value then
return value
end
if t == 'table' then
value = { }
seen[val] = value
for k, v in next, val, nil do
value[serialise(k, seen)] = serialise(v, seen)
end
elseif types[t] then
local methods = transformers[types[t]]
if methods then
value = methods.serialise(val)
end
else
value = val
end
return value
end
----------------------------------------------
-- --
-- Deserialising Datatypes --
-- --
----------------------------------------------
local deserialise
function deserialise(val)
local t = typeof(val)
if t ~= 'table' then
return val
end
local typed = val._t
local value = val._v
if typed and value then
local methods = transformers[typed]
if methods then
val = methods.deserialise(value)
end
return val
end
value = { }
for k, v in next, val, nil do
value[deserialise(k)] = deserialise(v)
end
return value
end
----------------------------------------------
-- --
-- Example Usage --
-- --
----------------------------------------------
-- e.g. some random color to use
local bc = BrickColor.Random()
local color = Color3.new(bc.r, bc.g, bc.b)
-- e.g. example data that you'd like to save
local data = {
-- each table in this list represents
-- a model that's being saved
{
Prefab = 0, -- describes the model source info from `prefabs`
Position = Vector3.zero, -- describes the position of the model relative to the placement grid
Color = color, -- describes some example color property
},
{
Prefab = 0,
Position = Vector3.zero,
Color = color
},
}
-- e.g. serialise the data when saving;
-- after doing this we would save it to `DatastoreService`
local serialisedData = serialise(data)
-- Note: this will look something similar to this in the output...
-- {{Color={_t=0,_v="efb838"},Prefab=0,Position={_t=1,_v={0,0,0}}},{Color={_t=0,_v="efb838"},Prefab=0,Position={_t=1,_v={0,0,0}}}}
print(serialisedData)
-- e.g. deserialising the data when loading;
-- after doing this we would match the `Prefab` property
-- with the model in the `prefabs` list and insert it into
-- the game after applying our `Color` properties etc
local deserialisedData = deserialise(serialisedData)
-- Note: This should now look exactly like the initial `data` variable
print(deserialisedData)
When a player enters the plot area, iterate through the parts inside the hitbox and store their positions, sizes, names, and other relevant information in a table. You can use the GetDescendants() function to get all the descendants of the hitbox and filter out the parts you want to save.
Here’s an example code
local hitbox = -- reference to the red hitbox
local function savePlot()
local parts = {}
for _, part in ipairs(hitbox:GetDescendants()) do
-- Filter out the parts you want to save, e.g., by checking their tags or names
if part:IsA("BasePart") then
local partData = {
Position = part.Position,
Size = part.Size,
Name = part.Name,
-- Add more properties as needed
}
table.insert(parts, partData)
end
end
-- Save the parts table to a data store or file for persistence
end
local function loadPlot()
-- Load the saved parts table from the data store or file
for _, partData in ipairs(savedParts) do
local part = Instance.new("Part")
part.Position = partData.Position
part.Size = partData.Size
part.Name = partData.Name
-- Set other properties as needed
-- Parent the part to the plot area or the workspace
end
end
-- Connect the savePlot and loadPlot functions to appropriate events, such as player leaving or joining the plot area
For player-specific plot data, you can use the DataStoreService to save and load the parts table for each player. Make sure to associate the saved data with the player’s unique identifier, such as their UserId.
Yeah, I’m lost. There’s so much information said here and I can’t actually remember it all so that’s on me, but I guess I’ll have to revisit this problem when the rest of my game is completed. Thank you all for your time and I will be revisiting this when it is the last thing I need to add.
Thank you EVERYONE for your help! I have successfully done it. I put everything into a single table and inserted a single table for every element in the plot. So it looked like this:
{
{
[1] = "Hydrogen",
[2] = Position.X,
[3] = Position.Y,
[4] = Position.Z,
[x] = [other variables and stuff here]
},
{
same again
},
}
I didn’t actually realise you could save direct tables, so what I saved was just:
local data = {
-- all the tables and stuff in here
}
And it worked! Thanks everyone for your support again, it was a lot easier than what I was worried about… lol.