Math for Roblox Developers #1: Vectors

About Me (& Why Math?)

Hi! I’m Lugical. I’ve been on the platform since around 2014, and developing since 2017. Outside of Roblox, I’m a college student, currently double majoring in Computer Science, and…yep, you guessed it: Math. I’ve been a bit inactive more recently, but I’m hoping to jump back into it. So why not write up a series of tutorials for the DevForums?

While I have plans to make future tutorials, I figured to start off with vectors, as it’s definitely the most frequent “part” of math that shows up. You’ll have likely come across the term Vector3 and Vector2. If you’re like me, you probably didn’t really grasp the point of it at the start (or have misconceptions). This is here to help.

This tutorial will be a bit long (maybe too long), but the aim is to hopefully allow you to gain some insight to what you’re working with, and also just to accommodate those of all math backgrounds. The conceptual understanding of vectors and their properties will aid in not just scripting on Roblox, but also thinking about how other game engines work behind-the-scenes, or how to even model the world around us (physics).

So, why not start here?!

:triangular_ruler: What Are Vectors?

In the most general sense, vectors are a collection of values. These values (sometimes called components) in a vector come together to give you a information that you might need. Think of them like a list of numbers that describe something.

For example, this is a vector:
{1, 2, 3, 4}

And this:
{0, 0, 0, 0, 0, 0, 0}

And technically, this too!

local vectorThree = {-1, 1, 10, 2}

It’s this broad definition that lets them be very versatile, and used in a bunch of places. In physics, vectors can be used to describe acceleration and velocity. In databases, vectors can describe the ratings of restaurants nearby. In linear algebra, they can be columns of a matrix, and so on…

In Roblox, vectors are commonly used to help describe:

  • Positions & Offsets (Where am I in the 3D world? Where am I on the 2D screen?)
  • Rotations (What angle am I tilting this part at?)

When transforming vectors using their mathematical properties, we’re able as developers to:

  • Create animations through Tweening
  • Make characters and NPCs move around
  • Script stunning UI effects :sparkles:

For the rest of this tutorial, we will be working with vectors written like this, so we can use some built-in mathematical operations later.

local vectorThree = Vector3.new(x, y, z)
local vectorTwo = Vector2.new(x, y)

:framed_picture: Visualizing Vectors in Roblox

From the moment you step into Roblox Studio, you’re greeted with this screen:

Isn’t it beautiful? You’ve got all your tools, your explorer, your properties tab, and your very own, 3D world! You’re looking at it right now, and can even move around! But where in the world are we looking from in this view? What angle are we looking at this even?

This is the same scenario that your own game faces when a player joins, and unfortunately “this cool spot over there” doesn’t work as a very accurate location for where we are in-game :frowning:

At its core, every game that you enter in Roblox is a 3D world, so if it were empty, it’s just 3D space. Let’s bring out our Desmos 3D coordinate plane.


Quick disclaimer: Roblox organizes the world so Y is up & down, so just flip Z & Y in your mind for these Desmos pics.

Anywhere and everywhere that we can move in-game can be plotted as a point in space. So let’s have this blue dot be where we’re currently looking at the world from:


It’s kind of hard to describe where it is on its own, though, right? It kind of to the left and up, but in comparison to what?

In Roblox (and most physical systems), vectors typically start at the origin, which is the center of the entire world. That way, we can describe the steps it takes to get from Point A to Point B. In this hypothetical, I travel 1 unit to the X, 2 units to the Y, and 1 unit to the Z:

We can put that set of instructions into a 3D vector, like Vector3.new(1, 1, 2). The result is this vector arrow getting us to our blue dot!

Notice here, that we can observe 2 properties of our red vector:

  • Direction: Where we’re pointing to in space.
  • Magnitude: Our distance away from the center.

2D vectors follow the same principle, except that we move on a 2D plane (like your screen) rather than 3D space. With one less instruction, we move up/down, then left/right like this:

Given how well they can document where we are in space, vectors are used in the Roblox environment to capture positions of various assets. Where they also come into hand is measuring the rotation of objects (i.e. Orientation). In this instance, instead of thinking of moving out X amount of units on each axis, think about rotating it X degrees or radians around each axis.

:abacus: Simple Vector Operations

The rules of arithmetic work with vectors like how you’d expect they work. When it comes to adding or subtracting 2 vectors, you can think of it as adding and subtracting their corresponding numbers with each other.

It also makes sense intuitively, if we think about the components like instructions on how we’re traveling. If we travel 1 unit up in vector A, and 2 units up in vector B, we’re going up 3 units in total if added!

For example:

local v1 = Vector3.new(1,1,1)
local v2 = Vector3.new(3,2,1)
local additionVector = v1 + v2
print(additionVector) --> Vector3.new(4, 3, 2)

--> Take the first component in v1 & v2: 1 + 3 = 4
--> Then the 2nd components: 1 + 2 = 3
--> Take the last components: 1 + 1 = 2

local subVector = v1 - v2
print(subVector) --> Vector3.new(-2, -1, 0)

--> Take the first component in v1 & v2: 1 - 3 = -2
--> Then the 2nd components: 1 - 2 = -1
--> Take the last components: 1 - 1 = 0

You can also multiply & divide vectors with a scalar (aka a regular number). For example:

local v1 = Vector3.new(1,1,1)
local v2 = Vector3.new(3,2,1)
local multVector = v1 * 2
print(multVector) --> Vector3.new(2, 2, 2)

--> Take the first component & mult. by 2: 1 * 2 = 2
--> Then the 2nd components: 1 * 2 = 2
--> Take the last components: 1 * 2 = 2

local divVector = v2 / 3
print(divVector) --> Vector3.new(1, 2/3, 1/3)

--> Take the first component & div. by 3: 3/3 = 1
--> Then the 2nd components: 2/3
--> Take the last components: 1/3

Take a minute to think about how these look from the eyes of Roblox Studio!

Answer Key

Scalar Multiplication & Division

Remember how I talked about the magnitude? Since we know the direction of our vector already, we can leave that as is. When doing scalar multiplication/division, we’re just adjusting the magnitude (length) of our vector itself! When multiplying, you are elongating it, and when dividing, you are shrinking it!


This is a visual of scalar multiplication & division. Notice how the direction stays the same, but the magnitude changes.

Vector Addition & Subtraction

Let’s say we have our first vector from before. The components in the vector give us instructions on how to move in each for the 3 axes in space.

The second vector that we add, just like the first, gives us instructions on how to move. So, the second vector picks up where the first vector ends. It’s reference point is not the origin, but rather the first vector itself. As such, that 2nd vector is placed as such like this.

The resultant vector (describing where we are now) would look like this, in green.

For subtraction, recall that it is in essence addition, but with flipping the sign (i.e. subtracting 1 is just adding -1). The same logic applies to vectors, in the sense that subtracting 2 vectors means you add the 2 vectors, with the second vector’s direction being flipped (flipping the signs of the components),

The logic for these operations is the underlying logic for the offsets that you’ll see for equipping Tools and Accessories! Roblox places these avatar bits onto the character with a vector offset based off the current position of the player itself.

:magnet: Vector Products & Other Operations

Dot Products

Vectors come in all kinds of directions: how do we know if 2 vectors are similar to each other, or polar opposites? That’s where dot products come in.

Say you have two, 2D vectors that are the same. Let’s multiply the x-components together and y-components together.

local v1 = Vector2.new(1, 1)
local v2 = Vector2.new(1, 1)
local dotProd = (v1.X * v2.X) + (v1.Y * v2.Y) --> (1*1) + (1*1) = 2!

--> Dot Product: 2

What if the 2nd vector was the opposite direction?

local v1 = Vector2.new(1, 1)
local v2 = Vector2.new(-1, -1)
local dotProd = (v1.X * v2.X) + (v1.Y * v2.Y) --> (1*-1) + (1*-1) = -2!

--> Dot Product: -2

What about 2 orthogonal vectors?
(Orthogonal vector have a right (90 degree) angle between them. Like an L.)

local v1 = Vector2.new(1, 0)
local v2 = Vector2.new(0, 1)
local dotProd = (v1.X * v2.X) + (v1.Y * v2.Y) --> 0 + 0 = 0!

--> Dot Product: 0

Based on these, we can generalize a spectrum of interpreting dot products.


Dot products are a measure of how aligned and orthogonal 2 vectors are. Vectors with similar alignments and facing the same general direction will tend to produce positive dot products, while those facing away would produce negative dot products. Crucially, though, orthogonal vectors that form right angles have a dot product of 0.

In Roblox game development, dot products could come up when attempting to mimic physics (i.e. weapons systems), checking if a player is facing something (like an NPC), or manipulating the camera.

In physical systems, you may also see the dot product calculated as:

||Vector 1|| ||Vector 2|| cosθ

|| represents the magnitude/length of the vector.

This is also a valid way to take the dot product in cases where you know the angle between 2 vectors, but not their actual directions. In general, you won’t need this as Roblox APIs provide you with :Dot()

local v1 = Vector2.new(1, 1)
local v2 = Vector2.new(-1, -1)
local dotProd = v1:Dot(v2) --> -2!

Cross Products

Computing the cross product by hand is a bit more hectic, and for purposes of Roblox game development, you won’t need to compute them manually. For this tutorial, I’ll leave you with this neat API tidbit.

local v1 = Vector3.new(1, 0, 0) --> pointing right
local v2 = Vector3.new(0, 0, -1) --> pointing forward
local crossProd = v1:Cross(v2) --> pointing up, orthogonal to both right and forward!

At its core, the cross product transforms the 2 input vectors to find a vector that is orthogonal to both of them. In an abstract sense, taking the cross product of the x and y axes in 3D space, would give you the z axis!

In terms of Roblox applications, you would see this to potentially measure trajectories, apply some lighting, custom terrain generation, and more.

Normalization & Unit Vectors (Vector.Unit)

Sometimes, you want a small, consistent-length vector for your use-cases. Maybe you just need to adjust the general direction of something moving, but a 100-stud-long vector is overkill. Or maybe you’re doing something on a smaller scale, like raycasting or applying velocity objects. In that case, you’ll likely want unit vectors, which are vectors of magnitude 1 (stud).

In the math sense, the derivation of how to get the unit vector of any vector (normalization) is straightforward:

  • Vector A has some magnitude || A ||
  • If we want magnitude to be 1, why not just shrink it to size 1?
  • We can shrink by dividing the Vector A by magnitude (scalar) || A ||!

u = A / || A ||

Thankfully, if you’re lazy like me, Roblox has the .Unit method for you to just get the unit-vector without having to normalize it manually. For example:

local longVector = Vector2.new(100, 0) --> woah
print(longVector.Unit) --> Vector2.new(1, 0) --> better :)

Vector Interpolation (Vector:Lerp(goal, alpha))

Getting a hang of interpolation is quite a hurdle. Knowing how to use it well is probably worth its own tutorial outright, but here, vector interpolation is about transforming a vector to a “fraction” of itself to fit your needed use case.

local longVector = Vector3.new(100, 0, 0)
local origin = Vector3.zero
local interp = longVector:Lerp(origin, 0.5)

Notice here that the :Lerp() method takes in 2 arguments: a goal and an alpha.

The goal represents where you want your current vector to approach. In this case, I essentially want wherever I am in longVector to eventually return to the center of the world.

The alpha represents the “fraction” I’m shrinking the vector to. It takes a value from 0 to 1 (0 meaning we stay put, 1 being we go all the way to the center). In this case, 0.5 means that our :Lerp() will produce a vector that describes moving from (100, 0, 0) to about (50, 0, 0), so halfway through to the center!

Typically, interpolation is often used to create smooth-moving animations of physical and/or interface objects, as you assign the position of it to an interpolated vector over time. This is critical for frame-dependent actions, such as cameras and dynamic lighting, where you would interpolate almost every frame to make the animation smooth.

:dvd: Applications of Vectors

You will use vectors everywhere when scripting games on Roblox, whether you are handling NPC character movements, teleporting players around, making sick effects on UIs, and more! While there are numerous (and complicated) applications of vectors, I’ll narrow it down to a couple cool trinkets you can use for yourself!

Anti-Exploit 101 (Anti-TP)

Whether you’re a genre group owner looking to prevent script kiddies from ruining your experience, or preventing hacking in-game, you might wanna check if a player seems to teleport in-game. We can do that through vectors!

Let’s say the average WalkSpeed of all the players in your game is 16, but you want to catch the big cases where someone might be teleporting (which would ruin the game for everyone!) You think.

  • Let’s check in on where players are periodically
  • Let’s see if they’ve traveled WAY TOO MUCH in between (like 30)
  • If they have, let’s kick 'em out!

(This is a very rough server-side anti-exploit, intended for your education. Highly recommended to build a more robust, flexible system to your game needs)

--> SERVER SIDE
local function checkForTeleport(player: Player)
    local char = player.Character or player.CharacterAdded:Wait()
    local root = char:WaitForChild("HumanoidRootPart")

    --> Let's have some variables to measure our past & new positions
    local oldPos = root.Position --> Positions are vectors, remember??
    local newPos = root.Position


    while root do
        task.wait(1)
        oldPos = newPos
        newPos = root.Position
        
        --> Vector SUBTRACTION (& magnitude) to get the difference!
        local diff = (newPos - oldPos)
        local distanceTraveled = diff.Magnitude
        if distanceTraveled > 30 then
             player:Kick("Teleporting detected :(")
        end
    end
end

--> Execute!
game:GetService("Players").PlayerAdded:Connect(checkForTeleport)

Cutscenes

To recreate your own “absolute cinema” moment, it’s all about panning Camera from Point A to Point B…by describing your points with vectors! (We then convert into CFrames, which I can go into detail another time).

--> CLIENT SIDE!
local cam = workspace.CurrentCamera
local function doCutscene(
     startingPos: Vector3,  --> Position represents where we're at
     startingFocus: Vector3, --> Focus represents where we're *looking* at
     endingPos: Vector3, 
     endingFocus: Vector3)

     --> Lock the camera and put us to the starting scene
     cam.CameraType = Enum.CameraType.Scriptable
     local startingScene = CFrame.lookAt(startingPos, startingFocus)
     local endingScene = CFrame.lookAt(endingPos, endingFocus)

     --> I'd normally do TweenService for best smoothing
     --> But I'll do (CFrame) interpolation just to demonstrate the idea.
     for i = 1, 100 do
          cam.CFrame = startingScene:Lerp(endingScene, i / 100)
          game:GetService("RunService").PreSimulation:Wait()
     end

     --> Back to normal!
     cam.CameraType = Enum.CameraType.Custom
end

--> When needed, execute!
local start = Vector3.new(...)
local ending = Vector3.new(...)
local startFocus = Vector3.new(...)
local endFocus = Vector3.new(...)
doCutscene(start, startFocus, ending, endFocus)

If you’re interested in taking this, I have a standalone tutorial on making cutscenes just for you! And if you’re REALLY interested in camera manipulation, check out my open-source, CameraService.

Resources & Questions

I’m sorry for the word vomit that is this tutorial. I might be tweaking.
Always feel free to reach out to me, and leave any feedback or ideas on a future tutorial! I’d be happy to keep on continuing this series if people find it helpful :slight_smile:

If you want to be more up-to-speed on vectors in Roblox, check out this and that.

If you want to connect with me, you can reach me at:
Discord: @lugical.me
Twitter/X: @LugicalDev
Bluesky: @lugical.bsky.social
Email: lugicalstudio@gmail.com

40 Likes

Thank you for the in-detail tutorial. And,

goodluck!

1 Like

A tutorial that teaches us the bare minimum thanks bro!

1 Like

about time someone came out and made a decent explanation for what a lot of people would consider “basic knowledge”, when it can be quite confusing to new users, can’t wait to see how you do the dreaded CFrame math tutorial, since CFrames are notorious for their strange math compared to just numbers/vectors

3 Likes

Fr I cant wait too this tutorial is something I can learn on my own pace

1 Like

There’s a good chance CFrames will be Tutorial 2 lol.
I’m just a bit curious if there’s anything in particular with CFrames that you (or you think others) perceive as a mystery I can try to explain, perhaps?

I’d recommend covering vector instead of/as well as Vector3.

what I still struggle to grasp is relatively transforming a CFrame to be in another location (with some offsets, either rotational offset or positional), an example is in Pet Simulator; where the pets are positioned in a layout (with offsets depending on the index and how many pets there are). I did figure it out though but I have no idea what did it since it was purely just trial and error

plus the cframe’s rotation matrix doesn’t really click for me (why is a 3x3 matrix represented as 23 values?)

R00‑R22 represent its 3×3 rotation matrix

The two digits in these names should be interpreted as two numbers, not as one two-digit number. One of them is a row index in the matrix and the other is a column index.

The XVector (RightVector) consists of R00, R10 and R20. The YVector (UpVector) consists of R01, R11 and R21. The ZVector (-LookVector) consists of R02, R12 and R22.

So the rotation matrix is represented using 3x3 = 9 numbers. There is no R07 or R16, for example. Each digit in the names is 0, 1 or 2.

1 Like

ah, that really clears up my confusion

so i guess each column represents the axis of rotation, this always confuses me when im doing angles

speaking of angles let me rephrase, the thing i was confused about is the angles part, like how would i make a cframe parallel/perpendicular to another cframe