Introduction
Hi! Welcome to my first tutorial! We’ll be seeing how to create custom datatypes in Roblox!
What are datatypes?
According to Wikipedia:
In computer science and computer programming, a data type or simply type is an attribute of data which tells the compiler or interpreter how the programmer intends to use the data.
In a Roblox context, datatypes tell Roblox how to run and change the game experience. A full list of Roblox-native datatypes can be found at this handy little page on the Developer Hub:
DataType Index
Custom Datatypes
As of time of writing (2021-07-02T23:07:00Z), there’s no Luau-native way to create custom datatypes. However, using a ModuleScript and some clever dictionary usage, you can emulate custom datatype behavior pretty well. So let’s get started!
Decide on what your datatype will be. I’ve decided on a relatively simple datatype for this tutorial: a “UFrame
” (Universal Frame) datatype, essentially a cross between CFrame
and Vector3
. It will be composed of the components of 2 Vector3
s: one for position, and the other for orientation.
Now that you’ve decided on your datatype, create a ModuleScript in ServerStorage
. Rename the ModuleScript something descriptive. I’ll rename it “UFrame”. Open the module if it’s not yet, and replace where it says “module” with anything. Again, I’ll replace it with “UFrame”.
local UFrame = {}
return UFrame
To start, create a constructor function. I personally use .new()
, as it’s widely used on the Roblox platform.
local UFrame = {}
function UFrame.new()
end
return UFrame
Use the components of your datatype as parameters, and create a dictionary for the constructor function to return.
local UFrame = {}
function UFrame.new(pX, pY, pZ, rX, rY, rZ)
local ReturningData = {}
end
return UFrame
Now populate the dictionary with components. This will be your datatype. You will return this datatype to the calling script.
local UFrame = {}
function UFrame.new(pX, pY, pZ, rX, rY, rZ)
local ReturningData = {}
ReturningData.pX = pX
ReturningData.pY = pY
ReturningData.pZ = pZ
ReturningData.rX = rX
ReturningData.rY = rY
ReturningData.rZ = rZ
return ReturningData
end
return UFrame
That is basically your datatype! But at the moment, it’s pretty useless. It’s just a dictionary, and not a very long one at that. It doesn’t even define any languages! But to extend the datatype’s functionality, you can integrate functions! Here’s how.
Where does this happen?
Functions in datatypes occur in Roblox natively. Good examples are the Vector3 functions:
- Vector3:Lerp()
- Vector3:Dot()
- Vector3:Cross()
- Vector3:FuzzyEq()
These aren’t in the Vector3
library. They can be called like:
Part.Position:Cross(OtherPart.Position)
I want to add a UFrame:Apply(BasePart)
function, so I do the following. This is the same format for your own custom datatype functions.
local UFrame = {}
function UFrame.new(pX, pY, pZ, rX, rY, rZ)
local ReturningData = {}
ReturningData.pX = pX
ReturningData.pY = pY
ReturningData.pZ = pZ
ReturningData.rX = rX
ReturningData.rY = rY
ReturningData.rZ = rZ
function ReturningData:Apply(BasePart: BasePart)
BasePart.Position = Vector3.new(ReturningData.pX, ReturningData.pY, ReturningData.pZ)
BasePart.Orientation = Vector3.new(ReturningData.rX, ReturningData.rY, ReturningData.rZ)
end
return ReturningData
end
return UFrame
That is how you add functions to custom datatypes!
Now, I want to add a UFrameLibrary:GetBasePartUFrame(BasePart)
function. You likely can deduce how to do that. Add a function to the module. This will be the same framework to use when returning your datatype from a function of your own datatype.
local UFrame = {}
function UFrame.new(pX, pY, pZ, rX, rY, rZ)
local ReturningData = {}
ReturningData.pX = pX
ReturningData.pY = pY
ReturningData.pZ = pZ
ReturningData.rX = rX
ReturningData.rY = rY
ReturningData.rZ = rZ
function ReturningData:Apply(BasePart: BasePart)
BasePart.Position = Vector3.new(ReturningData.pX, ReturningData.pY, ReturningData.pZ)
BasePart.Orientation = Vector3.new(ReturningData.rX, ReturningData.rY, ReturningData.rZ)
end
return ReturningData
end
function UFrame:GetBasePartUFrame(Part: BasePart)
local pos = Part.Position -- Shortened
local ori = Part.Orientation
return UFrame.new(pos.X, pos.Y, pos.Z, ori.X, ori.Y, ori.Z)
end
return UFrame
You can even return data from in-datatype functions!
local UFrame = {}
function UFrame.new(pX, pY, pZ, rX, rY, rZ)
local ReturningData = {}
ReturningData.pX = pX
ReturningData.pY = pY
ReturningData.pZ = pZ
ReturningData.rX = rX
ReturningData.rY = rY
ReturningData.rZ = rZ
function ReturningData:Apply(BasePart: BasePart)
BasePart.Position = Vector3.new(ReturningData.pX, ReturningData.pY, ReturningData.pZ)
BasePart.Orientation = Vector3.new(ReturningData.rX, ReturningData.rY, ReturningData.rZ)
end
function ReturningData:Add(otherUF: UFrame)
local newpX = ReturningData.pX + otherUF.pX
local newpY = ReturningData.pY + otherUF.pY
local newpZ = ReturningData.pZ + otherUF.pZ
local newrX = ReturningData.rX + otherUF.rX
local newrY = ReturningData.rY + otherUF.rY
local newrZ = ReturningData.rZ + otherUF.rZ
return UFrame.new(newpX, newpY, newpZ, newrX, newrY, newrZ)
end
return ReturningData
end
function UFrame:GetBasePartUFrame(Part: BasePart)
local pos = Part.Position -- Shortened
local ori = Part.Orientation
return UFrame.new(pos.X, pos.Y, pos.Z, ori.X, ori.Y, ori.Z)
end
return UFrame
Now you know how to create custom datatypes! Please give suggestions and thanks for reading!