Plot Saving System

How do I go about making a Plot Saving System?

Here is my example plot:

I want to get all of the parts that are inside the red hitbox, and I want to save them to a table {}

How do I save their positions, size, name, descendants, etc if the player leaves, and then load it in when a player joins back the next day?

Preparation

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.

  1. Getting parts inside of a part using WorldRoot::GetPartBoundsInBox() or WorldRoot::GetPartsInPart() - found here and here respectively.
  2. 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-resources e.g. example found here

Saving

  • Once serialised, you can save the data to the DatastoreService
  • 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

  1. Read the data from the DatastoreService
  2. Deserialise the data
  3. 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.

1 Like

Possibly a problem?

image

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?:

  1. Have a instance property value with more than 255 Characters.
  2. Having instance data that exceeds 255 bytes.
  3. Encode children of a unsupported instance.

You do not have to store them. You can just store “Hydrogen” in this case within an array and clone it whenever a player joins.

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:

  1. Store some reference number | string of the object they’ve placed from ReplicatedStorage;
  2. Its location within the placement area;
  3. 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:

  • “Saving Data” tutorial found here
  • “Data Stores” walkthrough guide found here
  • DatastoreService documentation here
  • “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.)

Yep, you can do that on Player Removing:

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.

No worries, and that sounds interesting!

That’s right, yes.

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.

1 Like

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.

1 Like

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