Roblox CFrame Simplified
Roblox CFrame defines the position and orientation of models and cameras. CFrame is short for Coordinate Frame that holds the position and rotation vectors. A vector is how we define a coordinate in a 3D world with x, y, and z axises.
Let’s learn some common ways of using Roblox CFrame and understand some of the math it uses.
Once we’re done you’ll have a better grasp of how CFrame works!
What you’ll learn
- Basics of 3D coordinate systems and vectors
- How to use CFrame
- Math formulas for CFrame
- Understand the difference between world, object, and local space
- Quaternion basics
What is a 3D coordinate system?
In Roblox we have three axises to create our worlds in. We can think of the ground as using the x and z axises and anything that goes above or below uses the y axis.
So x = 0, y = 0, z = 0 places us at the origin of the world. In vector form, this is {0, 0, 0}.
This only defines where you are in the world, so we’ll need another vector to define which direction we’re facing. Roblox calls this the Look Vector, the direction that the object is looking at.
When we create a new CFrame, it sets it with the given position and orients it with the given look vector. The look vector also defines which axis is up.
-- by default, the up vector is {0, 1, 0}
-- when look vector is directly above, the up vector switches to the x axis
local position = Vector3.new(0, 0, 10)
local lookAt = Vector3.new(10, 10, 10)
local newCFrame = CFrame.new(position, lookAt)
Position and direction vectors
We also have two different types of vectors that we work with. One is a positional vector that defines a point in the world. The other is a directional vector that defines which way an object is moving.
We can think of directional vectors as if they were a compass.
-- converts a vector as seen by player to a vector as seen by the world
local pointFromPlayerPerspective = Vector3.new(10, 0, 20)
local pointInWorldSpace = playerCFrame:PointToWorldSpace(pointFromPlayerPerspective)
We’ll get into the difference between object space and world space later.
Vectors are also used a lot in physics engines. For example, the speed of an object tells us how fast it’s going but doesn’t tell us where it’s going. A vector tells us both the speed and direction, this is known as velocity.
Velocity = (positionVector1 – positionVector2) / timeElapsed
local positionVector1 = Vector3.new(10, 0, 10)
local positionVector2 = Vector3.new(0, 0, 0)
local velocity = (positionVector1 - positionVector2) / 2
-- velocity becomes {x = 5, y = 0, z = 5 }
local speed = velocity.Magnitude -- 7.071
For some quick calculations we can use Wolfram Alpha.
Difference between radian and degree angles
We’re all familiar with degrees such as turning 180 degrees means we’re looking the exact opposite direction that we were looking at before.
Radian angles are defined by the angle formed by an arc length that equals the radius length.
With the Unit circle, our radius equals 1. So the equivalence between degrees and radians becomes 180 degrees equals Pi. On the unit circle, half the circumference equals Pi.
Remember that the perimeter or circumference of a circle is C = 2 * Pi * radius.
Since a unit circle has a radius of one and the radian angle equals the arc length, a half turn (180 degrees) is equal to half the circumference (1*Pi).
3D engines typically rely on radian angles instead of degrees when calculating positional and rotational vectors.
Luckily, CFrame gives us useful functions such as CFrame.Angles() and math.rad() to convert degrees into radians.
Conversion Examples
local rotationCFrame = CFrame.Angles(math.rad(90), 0, 0)) -- rotates 90 degrees on x axis
Moving a model or part with Roblox CFrame
With Roblox CFrames we have translational and rotational motion. Translation means to move along any axis. For example, we translate along the x-axis by adding an offset to the current CFrame. This offset is a vector with how much to move in its x axis component.
local offset = Vector3.new(5, 0, 0) -- translate on x axis by 5
Rotational movement modifies the orientation vectors within a CFrame. These vectors consist of the look, back, and right vector. These three vectors together define a sphere of rotation that the model can rotate within.
We can use the CFrame:lookAt() function to get a CFrame positioned and oriented to face a given position.
local position = Vector3.new(10, 10, 10)
local someCFrame = CFrame.new(5, 5, 5)
local orientedCFrame = someCFrame:lookAt(position)
Roblox CFrame orientation demystified
CFrames help us to think of position and orientation changes from several perspectives. We use these different perspectives or spaces to simplify how we orient and position objects.
Roblox uses a Scene Graph system to apply effects, scripts, and also position and orient objects in a 3D world.
We first have the world space that defines the global coordinate system for all objects. World space is the simplest to understand since we can easily relate it to our own experiences. Moving an object in world space is the same as if you were moving it in the Roblox Studio 3D editor.
Secondly, we have object space that changes the origin point to the center of the object. Within object space, all coordinates are from the perspective of the object itself. For example, if two objects are facing each other, either object’s left direction is pointing in different directions.
We can use object space to determine if other objects are in front or behind the main object.
local enemyInFrontOfPlayer = enemyPlayerCFrame:ToObjectSpace(mainPlayerCFrame).Z > 0
Difference between object and local space
Lastly, we have a local space that orients the object’s coordinate system to its parent.
When you add a child to a model within the scene graph, the child remains in the same position relative to its parent by changing the child’s position and orientation in local space.
Object and local space are pretty similar with some subtle nuances. We can imagine this as if we were floating in outer space. Gravity controls our natural up vector here. While on earth, we follow its local space coordinate system, but once we reach out into outer space our local space follows the sphere of influence from the sun.
For another example to help make this clearer, imagine the camera is at the origin in world space and we have two houses. One house is floating and rotated 30 degrees on the x-axis. The other house is also floating and rotated 30 degrees on the y-axis.
These houses also contain furniture models anchored to the house’s floor. Moving this furniture using world space coordinates is more complex than moving the furniture in local or object space. In object or local space, we can easily offset the furniture’s position or rotation vectors and then convert them back into world space.
Switching between world and object space
In local space, child objects follow the orientation of the parent.
Using CFrame:toObjectSpace() and CFrame:toWorldSpace() let’s the script switch perspectives.
-- player 1 gives CFrame in object space for a team objective
-- other team members convert the CFrame to world space using player 1’s CFrame
-- to get CFrame relative to themselves
local objectCFrameForOtherPlayer = playerOne:ToWorldSpace(objectiveCFrame):ToObjectSpace(otherPlayer.CFrame)
-- we can now take the difference of the team player’s CFrame to get the distance to the objective
local distance = (objectCFrameForOtherPlayer - otherPlayer.CFrame).Magnitude
-- or calculate the angle to show an arrow on a mini-map
Quaternion basics for rotation CFrames
It may sound scary or complex, but let me break it down so we can understand how it helps us.
Quaternion is a mathematical system that describes how we can easily calculate the rotation of an object in 3D space. A Roblox CFrame represents its rotational space through its look, right, and back vectors. In terms of quaternions, these vectors represent the 3 orthogonal axes labeled i, j, and k. Orthogonal means that they are perpendicular to one another.
The i, j, and k components of a quaternion define the vector that’s being rotated upon. While the real part, or w value, is how much it will rotate.
To visualize this, we can use the unit component rules to see how the quaternion will rotate.
Quaternion multiplication table
axis | 1 | i | j | k |
1 | 1 | i | j | k |
i | i | -1 | k | -j |
j | j | -k | -1 | i |
k | k | j | -i | -1 |
These rules help to avoid gimbal lock, or when more than one axis of rotation are parallel. When in gimbal lock, there’s no way to rotate along all 3 axes. Hence, the object is locked to only rotate on the non-parallel axes.
Also, when multiplying quaternions, the order that’s used is important. Multiplying Q1 * Q2 is not the same as Q2 * Q1.
As you can see in the multiplication table,
i * j does not equal j * i
i * j equals k while j * i equals -k.
We can use this property to rotate clockwise or counter-clockwise.
What’s Next
You now have a better understanding of how Roblox CFrame works as well as the fundamental concepts it uses. This should also help you to understand how world, object, and local spaces allow us to implement gameplay logic.
Please consider signing up for my email newsletter to get the latest updates!
As always, thank you for reading and stay curious!