CFrames, one of the most intimidating and important concept one has to grasp when writing code related to position and rotation. Today, you shall learn a lot about CFrames; how they work and also with visuals and and hopefully have grasped the concept by the end of this long post.
Note: You should be having a understanding of vectors and must be familiar with Luau.
1. Basics
A CFrame is a data type which consists of positional and rotational matrix. It is proven more useful than Vector3
's since not only it has more methods, it also contains the rotational matrix which can be manipulated unlike Vector3
, which only consists of positional data.
The positional matrix is technically a 4x4, not 3x4. The 0,0,0,1
in the last only exists so you can multiply CFrames together, you cannot multiply a 3x4 matrix by a 3x4 because the rule for multiplying 2 matrixes together is that the number of rows in the first matrix must be the same as the number of columns in the left CFrame. The rotational matrix is a 3x3.
Multiplying CFrame A with B
Multiplying 2 CFrames together, you can see that the number of columns in A
is the same as the number of rows in B
. If they weren’t the same (if 0,0,0,-1
didn’t exist), you couldn’t multiply them both.
Positional matrix:
Whenever you print a CFrame, it’s whole matrix is printed out or in other words, the positional and the rotational matrix.
print(CFrame.new()) --> 12 numbers [3 positional, 9 rotational]
-- output: 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1
Directional Vectors
The rotational matrix consists of 3 vectors or directional vectors to be precise, look, right and up. Each vector is made up of 3 numbers of course, x, y and z.
The first vector in the matrix is the right vector, CFrame.new(1,0,0)
, the second being the up vector, CFrame.new(0, 1, 0)
and the last being the look vector, CFrame.new(0, 0, -1)
.
This also means that directional vectors are positioned in a way relative to a part, but are actually relative to the world origin.
LookVector
represents the forward part of a CFrame, RightVector
represents the right direction of a CFrame and UpVector
represents the up direction of a CFrame.
When negating these vectors -
, you get the opposite direction.
Example:
Same rule applies for other directions, i.e right and up vectors.
Note: To reference look, right and top vectors, I’ll reference them by “directional vectors”.
Unit Vectors
They are basically unitized vectors or vectors having a length (magnitude) of 1. You may also have heard people usually say that the directional vectors are unitized vectors. This is true because each directional vector has a length of 1. Unit vectors are also known as vectors with directions, hence “directional vectors”.
A representation of a vector and a unitized vector.
They are really useful because unitized vectors allow us to control a vector’s length An example would be ray casting from the player’s humanoid root part to the mouse, but what if we wanted to limit how long the ray can extend to? Well, take a look at this code.
local MAX_LIMIT = 35
local Players = game:GetService("Players")
local mouse = Players.LocalPlayer:GetMouse()
local character = Players.LocalPlayer.Character or Players.LocalPlayer.CharacterAded:Wait()
local direction = (mouse.Hit.LookVector * MAX_LIMIT)
local ray = workspace:RayCast(character:WaitForChild("Head").Position, direction)
if (ray) then
print(ray.Instance)
end
I multiply the look vector of the mouse by 35 since it has a length of 1 (remember the directional vectors are unitized vectors). The resultant vector will be the same but extended by 35 studs.
2. CFrame math
At this point, you should know the basics of CFrame and what they are, let’s get into the math when working with them.
What If I were to told you that you can actually create a CFrame out of 12 numbers instead of 3.
local cf = CFrame.new(5, 5, 5, 1, 0, 0, 0, 1, 0, 0, 0, -1)
Cool right? This means we can create a CFrame with different rotational values!
Creating a CFrame only by position results in the rotational matrix to have the vectors as if they were relative to the origin or in other words, the rotation of the CFrame would be 0,0,0
. This also means that LookVector isn’t necessarily 0,0,-1
, it is dependent on the rotation of the CFrame of course, same rule applies for other directional vectors as well.
Multiplying 2 CFrames is basically matrix multiplication:
The components in the rotational and positional matrix both added and multiplied.
Think of it like this explicitly:
local cf1 = CFrame.new(0,5,0)
local cf2 = CFrame.new(0, 3, 0)
local result = cf1 * cf2
print(result) --> 0, 8, 0
And here is the difference of positioning of the part relative to the world origin and positioning the part relative to it self.
Relative to world origin:
Relative to it self:
Notice how the first one doesn’t care about the rotation of the part, but the second one cares. Relative positioning.
Have you ever created a new CFrame out of 3 numbers and set a part’s CFrame to that CFrame and saw the rotation of the part was set to 0,0,0
?
The reason for that is because of the rotational matrix. As I previously stated, when creating a CFrame only by position, the rotational matrix have the default values as if they were relative to the world origin or in other words, the rotation of that CFrame will be 0,0,0
. If you don’t want the rotation to be 0,0,0
, you would manually have to set the values of the rotational matrix your self or multiply the CFrame.
local part = workspace.Part
part.CFrame *= CFrame.new(0, 0, 5)
I’m explaining this theoretically, let’s write some code shall we?
I have a part which is rotated about -39 degrees on the Z axis.
Now, I want to position the part about 5 studs relative to itself. I can simply just multiply it’s CFrame by CFrame.new(0, 5, 0)
.
local part = workspace.Part
part.CFrame *= CFrame.new(0, 5, 0)
See how it moved relative to it’s position? Well, remember CFrames also contain the rotational matrix? Since I’m performing matrix multiplication on CFrames specifically, it also accounts for the rotation of the CFrame we’re performing the multiplication on, this proves that CFrames are relative to them selves! CFrame.new(0, 5, 0)
will have a rotation of 0,0,0
. Also, matrix multiplication is different. CFrame.new(0, 5,0) * CFrame.new(0, 3, 0)
results in 0,8,0
, not 0, 15, 0
.
Assume the rotation of the CFrame is 0,0,0
, multiplying it with our part’s rotation results in the rotation 0,0,-39
.
All of us know that any number multiplied by 0 is 0, so why didn’t the part’s rotation change back to 0,0,0
? Well, as I said, matrix multiplication is internally different.
Think of the arithmetic on rotation of the part being performed like this:
(0,0,-39) * (0,0,0) = 0,0,-39 -- or think of them being added explicitly
.
Alright, but what if I added a Vector to a CFrame? The resultant CFrame will be positioned in a way relative to the world origin.
local part = workspace.Part
part.CFrame += Vector.new(0, 5, 0)
Into
See how it didn’t care about the rotation of the part but the rotation of the world origin which is defined to be 0,0,0
? That is because vectors are relative to 0,0,0
. We are just moving the CFrame by 5 studs relative to 0,0,0
without accounting for it’s rotation since vectors don’t contain rotation.
More Information
You may have seen certain vectors exist in CFrames such as XVector
, YVector
and the ZVector
.
They are actually the same as the directional vectors!
CFrame.XVector
is the CFrame’s vector of the first column of the rotational matrix, which means that CFrame.RightVector
is the same as CFrame.XVector
since both of them are the first vectors in the rotational matrix.
CFrame.ZVector
is the CFrame’s vector of the last column of the rotational matrix, which means that CFrame.LookVector
is the same as CFrame.ZVector
since both of them are the last vectors in the rotational matrix.
CFrame.YVector
is the CFrame’s vector of the middle column of the rotational matrix, which means that CFrame.UpVector
is the same as CFrame.YVector
since both of them are the middle vectors in the rotational matrix.
Also, there will be times you may want to calculate directions. The formula for it is simply (target - eye).Unit * DISTANCE
.
Example use case:
local MAX_RAY_LENGTH = 60
local character = --player's character
local mouse = -- player's mouse
local direction = (mouse.Hit.LookVector - character:WaitForChild("Head").Position).Unit
local ray = workspace:RayCast(character.Head.Position, direction * MAX_RAY_LENGTH )
if (ray) then
print(ray.Instance)
end
Subtracting the mouse’s position from the head’s position is the same as putting the tail of the head’s position on the head of the mouse’s position, but having its direction reversed (negated) and then we unitize our direction and multiply it by 60.
Also, if you want to only get the positional components of a CFrame, you can simply index .Position
which is a property of a CFrame.
local cf = CFrame.new(0, 5, 0)
print(cf.Position) --> 0, 5,0 (only 3 numbers, the positional components)
Here are the math operations you SHOULD know when working with CFrames!
-
For converting from degrees to radians when working with CFrames, use
math.rad(degrees)
. For the opposite, usemath.deg(radians)
. -
Rotation on CFrames should always be specified in radians!
Basic math operations that can be performed on CFrames:
3. CFrame methods
CFrame:ToWorldSpace(cf)
This method basically returns CFrame
multiplied by cf
. If I wanted to position a part to another part but with a offset, I can simply use this method.
Say I have 2 parts, named A
and B
. A
is positioned at the origin and B
is positioned about a few studs off the origin.
I want to position A
to B
and then offset it 5 studs on the Y axis relative to B
while A
's rotation is also changed to match B
's rotation.
workspace.A.CFrame = workspace.B.CFrame:ToWorldSpace(CFrame.new(0,5,0))
See how
A
's rotation is exactly B
's rotation? This is because A
's CFrame has changed to B
's CFrame and then it is offsetted by CFrame.new(0,5,0)
relative to B
's CFrame.
CFrame:ToObjectSpace(cf)
This method converts cf
(which is relative to the world origin) relative to CFrame
.
Let’s say I have 2 parts, named part
and AnotherPart
respectively. I want to get part
to look relative to AnotherPart
via using body gyro.
Take a look at this code:
local part = workspace.Part
local another = workspace.AnotherPart
while game:GetService("RunService").Stepped:Wait() do
local relativeCF = another.CFrame:ToObjectSpace(part.CFrame)
part.BodyGyro.CFrame = part.CFrame:ToObjectSpace(relativeCF)
end
Alright so I do get part
's CFrame relative to AnotherPart
and then I convert that relative CFrame relative to part
. I’m first converting part
's CFrame relative to AnotherPart
and capturing it in a variable, then I convert that relative CFrame relative to part
.
To understand why I convert relativeCF
relative to part
, an explanation from the developer hub should make sense:
"To understand object space, imagine you’re a football player. Your team is facing the opponents head on. If you hear your opponent yell “Go left!” That player is certainly referring to a direction relative to their team’s object space. To understand the direction that player is referring to, you would have to translate that direction relative to your player by ToObjectSpace().
CFrame:ToWorldSpace()
as a method to translate a direction from object space to world space. CFrame:ToObjectSpace()
as a method to translate a direction relative to the world origin relative to CFrame
.
AnotherPart
looking at left relative to it self will make part
look at the right relative to it self because AnotherPart
's left is part
's right, relative rotation.
Another example:
So I want to check if B
is forward relative to A
, not the world origin. I can simply write a function which can be reuseable as well.
local function partIsForward(part, target)
return target.CFrame:ToObjectSpace(part.CFrame).Z >= 1.3
end
print(partIsForward(workspace.B, workspace.A)) --> true
It returns a CFrame where part
's origin is relative to target
and checks if the Z axis of the relative position is greater or equal to 1.3 because remember, the Z axis is forward/back. Hope this makes sense.
CFrame:Inverse()
This method just returns the inverse of the whole CFrame matrix or in other words, it “negates” the whole CFrame matrix -
.
local cf = CFrame.new(0, 5, 0)
print(cf:Inverse()) --> 0, -5, -0, 1, 0, 0, 0, 1, 0, 0, 0, 1
CFrame.lookAt(eye, target)
This method takes eye and target as vectors and returns a CFrame where it is positioned at eye
looking at target
.
local eye = Vector3.new()
local target = Vector3.new(0, 50, 0)
local part = Instance.new("Part")
part.Anchored = true
part.CFrame = CFrame.lookAt(eye, target)
part.Parent = workspace
CFrame.new(x, y, z)
This method returns a newly constructed CFrame out of x
, y
and z
. Normally a CFrame is created out of 12 numbers, 3 positional and 6 rotational, but providing only the positional components results the rotational matrix of the CFrame to be the default or in other words, it’s rotation to be 0,0,0
.
local cf = CFrame.new(0,5,0)
-- positional components of cf is 0, 5,0 but the rotational components is the default
-- rotational components: 1,0,0, 0,1,0 0,0,-1
local newCF = CFrame.new(0, 5, 0, 0, 0, 1, 0, 1, 0, -1, 0, 0)
-- positional components of cf is 0, 5,0 but the rotational components is not the default!
-- rotational components: 0, 0, 1, 0, 1, 0, -1, 0, 0
More Information
You may notice that you can also use CFrame.new(eye, target)
instead of CFrame.lookAt(eye, target)
. However, the first one shouldn’t be used for creating a CFrame looking at target
and the latter replaces CFrame.new(position, lookAt)
constructor which accomplished the same task. However, the latter also allows you to specify the up vector.
CFrame.EulerAnglesXYZ(x, y, z)
This method takes x, y, and z as values in degrees converted to radians and returns a CFrame whose rotation in degrees is x, y and z but applied in z, y and x order. Same as CFrame.Angles(x, y, z)
.
local part = Instance.new("Part")
part.Anchored = true
part.CFrame = CFrame.Angles(0, math.rad(60), math.rad(30))
part.Parent = workspace
CFrame:ToEulerAnglesYXZ(x, y, z)
Works the same as CFrame.EulerAnglesXYZ(x, y, z)
but applies rotation in z, x and y order. Same as CFrame:ToOrientation()
.

CFrame:GetComponents()
Returns all the components of the whole CFrame. Same as CFrame:components()
.
local x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22 = CFrame.new(0, 0, 1):GetComponents()
local upVector = Vector3.new(r01, r11, r21)
print(upVector) --> 0, 1, 0
CFrame:Lerp(goal, value)
Returns a CFrame interpolated between CFrame
and the goal. The goal is determined by value
. If value
is 1, then it will return the full value of goal
, else if it is .5 (half), it will return half the value of goal
and etc.
workspace.Part.CFrame = workspace.Part.CFrame:Lerp(CFrame.new(5, 5, 0), .5)
CFrame.fromMatrix(eye, rightVector, upVector, zVector)
This CFrame method takes all it’s arguments as vectors and returns a CFrame positioned at eye
whose rotational matrix consists of rightVector
, upVector
, zVector
. Pretty straightforward right?
local angle = CFrame.Angles(math.rad(30), math.rad(50), 0)
workspace.Part.CFrame = CFrame.fromMatrix(workspace.Part.Position, angle.RightVector, angle.UpVector)
Fin!
You’ve reached the end of this long post, I’m sure by now you should be familiar with the fundamental concept of CFrames and their main methods. Feel free to ask any questions!
- 1
- 2
- 3
- 4
- 5
0 voters
- Yes
- No
0 voters
- Understanding OOP and making weapons using them
- Understanding vectors
- Implementing turret rotation out of CFrames
0 voters