How to save CFrames/Positions/Color3

What you need to know


  • DataStoreService
  • Variables
  • Strings
  • Functions
  • pcall
  • return
  • typeof (optional)

Saving CFrames


Using DataStoreService you can only save strings or numbers. So how can we use this to save CFrames? Well, we can use the tostring() to convert the CFrame to a string. When we print this out:

local stringCFrame = tostring(CFrame.new(0, 0, 0))
print(stringCFrame)

We get:

"1, 2, 3, 1, 0, 0, 0, 1, 0, 0, 0, 1"

(Don’t worry about the numbers after 3 they are just about the rotation)
As you see we get a total of 12 numbers. And since this is a string we can successfully save this to DataStoreService without any problems. We can also use typeof() to see if it is a string or a cframe. So this is what it would like like to save:

--// Services
local DataStoreService = game:GetService("DataStoreService")

--// DataStores
local cframeDataStore = DataStoreService:GetDataStore("CFrame")

--// Services
local DataStoreService = game:GetService("DataStoreService")

--// DataStores
local cframeDataStore = DataStoreService:GetDataStore("CFrame")

--// CFrame to save
local stringCFrame = tostring(CFrame.new(1, 2, 3))
print(stringCFrame,typeof(stringCFrame))

--// Save
local success, result = pcall(function() -- Wrap in pcall if error
	cframeDataStore:SetAsync("CFrame",stringCFrame)
end)
if success then -- If saving unsuccessfull then warn server
	print("Saved CFrame!")
else
	warn(result)
end

Okay now we have saved the CFrame but how do we turn the CFrame string back into a CFrame? Well we can use the string.split() and we can separate it by “,” to get a table of all 12 numbers in order, the table will look something like this:

local table = {1, 2, 3, 1, 0, 0, 0, 1, 0, 0, 0, 1}

So now if we get the data and convert it to a cframe and print it out it will look like this:

--// Services
local DataStoreService = game:GetService("DataStoreService")

--// DataStores
local cframeDataStore = DataStoreService:GetDataStore("CFrame")

--// CFrame to save
local stringCFrame = tostring(CFrame.new(1, 2, 3))
print(stringCFrame,typeof(stringCFrame))

--// Save
local success, result = pcall(function() -- Wrap in pcall if error
	cframeDataStore:SetAsync("CFrame",stringCFrame)
end)
if success then -- If saving unsuccessfull then warn server
	print("Saved CFrame!")
else
	warn(result)
end

--// Function to convert back to CFrame
local function convertToCFrame(String)
	local splittedString = string.split(String,",")
	local newCFrame = CFrame.new(
		splittedString[1],
		splittedString[2],
		splittedString[3],
		splittedString[4],
		splittedString[5],
		splittedString[6],
		splittedString[7],
		splittedString[8],
		splittedString[9],
		splittedString[10],
		splittedString[11],
		splittedString[12]
	)
	return newCFrame
end

--// Get saved cframe
local savedCFrame
local success, result = pcall(function() -- Wrap in pcall if error
	savedCFrame = cframeDataStore:GetAsync("CFrame")
end)
if success then -- If saving unsuccessfull then warn server
	savedCFrame = convertToCFrame(savedCFrame) -- Convert to cframe
	print(savedCFrame,typeof(savedCFrame))
else
	warn(result)
end

And there you have it! That’s how you save CFrames.

Saving Positions


Now if you learned what we did on saving CFrames we can use the same technique on saving positions. Except it’s easier converting because we only have 3 numbers instead of 12! So the function of converting it will look like this:

local function convertToPosition(String)
    local splittedString = string.split(String,",")
    local position = Vector3.new(
        splittedString[1], 
        splittedString[2], 
        splittedString[3], 
    )
    return position
end)

Saving Color3’s


Now Colors are a bit different because when you use Color3.fromRBG it won’t actually print out number 0 - 255 but instead of a number between 0 and 1. A number 0 - 1 is actually a Color.new not Color3.fromRBG. So the function will look like this instead:

local function convertToColor3(String)
    local splittedString = string.split(String,",")
    local position = Color3.new(
        splittedString[1], 
        splittedString[2], 
        splittedString[3], 
    )
    return position
end)

Conclusion


In this tutorial you have learned how to convert Numbers to strings and back to Numbers! I hope you have learned something from this, as this is actually my first tutorial! And have a good rest of your day! (Also you feedback would be great!)

Feedback
  • Terrible
  • Bad
  • Okay
  • Decent
  • Good
  • Very Good
  • Perfect

0 voters

This isn’t how I would save CFrame data or other multi-value things, but it is cool to get a different perspective. Thanks for this.

Your welcome! This is really the only way I know how to save CFrame’s and decided to share this, so how would you save CFrame data or other mulit-value things?

Instead of converting to a string, I’d unload a CFrame to a table, so that way I can just read the table instead of having to parse the commas from it when I load in data.

Okay so you would do this?: {CFrame:GetComponents()}
But how would you convert it back to a cframe?
sorry for so many questions

Save the CFrame components, and then when needed you can pack the components back up into a CFrame using CFrame.new(components)

Pro tip: when saving Color3’s to a DataStore I recommend converting the Color3 into a hex code to save a few extra bytes of data.

All of your conversion functions can be simplified. It would also help to specify a datatype to convert to as a parameter so you can avoid rewriting the same function over three times with just slight differences. It might be a little unconventional, but this is what I’d do:

-- Using str because of syntax highlighting, otherwise try to be clear with names
local function convertStringToType(str, datatype)
    local values = string.split(str, ",")
    return datatype.new(table.unpack(values, 1, #values))
end

-- Test code
local serialisedCFrame = "1, 2, 3, 1, 0, 0, 0, 1, 0, 0, 0, 1"
local deserialisedObject = convertStringToType(serialisedCFrame, CFrame)
print(deserialisedObject, typeof(deserialisedObject))

The main takeaway from this conversion function is the usage of table.unpack instead of hardcoding all the values into the datatype creation. As you can have an arbitrary number of values appearing and since the goal of this function is to unify them all, we use unpack to insert the values into the creator function. Just helps to be more clean.

Overall though, not bad. Sometimes it’s hard to remember that running tostring on these objects actually serialises the values and it just feels weird to do sometimes. I prefer to do serialisation myself by accessing the necessary values (Position XYZ, Color3 RGB, CFrame GetComponents), putting them into a table and then saving them that way. It skips needing to deal with the string datatype With this method, you’d have to go through the string datatype to table and then to the proper Roblox datatype.

I think it’s cleaner to work with lesser datatypes, so I opt not to work with strings at all and go straight to tables when serialising.

2 Likes