1. What Are Vectors?
A vector is one of the most fundamental concepts in game mathematics, yet it is often taught in a way that obscures its simplicity. A vector is defined by exactly two things: a direction and a magnitude (length). That is it. A vector has no starting point and no endpoint it is a pure description of "how far, and in what direction." Two vectors are identical if and only if they have the same direction and the same magnitude, regardless of where you draw them on paper.
In Unity3D, vectors are represented by the Vector3 struct (for 3D) and the
Vector2 struct (for 2D). A Vector3 stores three floating-point components:
x, y, and z. Together these three numbers implicitly encode
both direction and magnitude. For example, Vector3(3, 0, 4) points in a direction that is
3 units along X and 4 units along Z, and has a magnitude of exactly 5 (from the Pythagorean theorem:
√(3² + 0² + 4²) = 5).
Vectors serve three distinct roles in Unity code, and it is important to recognize which role a given vector is playing at any moment:
- Position offset (displacement): the difference between two points, such as
target - origin. - Direction: a vector whose magnitude has been normalized to 1, indicating only "which way."
- Velocity / Force: direction and magnitude together represent both where something is going and how fast.
// Creating vectors
Vector3 position = new Vector3(1f, 2f, 3f);
Vector3 direction = new Vector3(0f, 0f, 1f); // points forward
Vector2 uiOffset = new Vector2(100f, -50f);
// Accessing components
float xPos = position.x; // 1
float yPos = position.y; // 2
float zPos = position.z; // 3
// Vector arithmetic
Vector3 a = new Vector3(1f, 0f, 0f);
Vector3 b = new Vector3(0f, 1f, 0f);
Vector3 sum = a + b; // (1, 1, 0)
Vector3 diff = a - b; // (1, -1, 0)
Vector3 scaled = a * 3f; // (3, 0, 0)
Vector3 divided = a / 2f; // (0.5, 0, 0)
2. Vector Shortcuts
Unity provides a set of static shorthand properties on the Vector3 class for the most
commonly needed vectors. These are the unit vectors along each world axis and a few special-purpose
vectors. Learning these shortcuts will make your code significantly more readable than spelling out
new Vector3(0, 1, 0) every time you mean "up."
Static Global Shortcuts (Always the Same Direction)
// Axis directions (world space, always fixed)
Vector3.right // (1, 0, 0) positive X axis
Vector3.left // (-1, 0, 0) negative X axis
Vector3.up // (0, 1, 0) positive Y axis
Vector3.down // (0, -1, 0) negative Y axis
Vector3.forward // (0, 0, 1) positive Z axis
Vector3.back // (0, 0, -1) negative Z axis
// Utility vectors
Vector3.zero // (0, 0, 0) the origin / "no movement"
Vector3.one // (1, 1, 1) used for uniform scaling
Object-Relative Shortcuts (Change with Object Rotation)
The Transform component exposes three direction properties that are relative to the
object's current rotation. These are invaluable for first-person movement and vehicle
controls where "forward" means the direction the character or car is facing not the global Z axis.
// These change as the object rotates
Vector3 myRight = transform.right; // object's local X axis in world space
Vector3 myUp = transform.up; // object's local Y axis in world space
Vector3 myForward = transform.forward; // object's local Z axis in world space
// Example: move this object "forward" in its own local space
transform.position += transform.forward * speed * Time.deltaTime;
// Example: apply force in the object's right direction
rb.AddForce(transform.right * lateralForce);
The key insight: Vector3.forward is always (0, 0, 1) in world space, while
transform.forward gives you the world-space direction that corresponds to the local
forward of this specific object at this specific rotation. If the object has been
rotated 90 degrees around Y, transform.forward will be (1, 0, 0) that is,
Vector3.right.
3. Movement with Vectors
The most direct way to move an object in Unity is to modify its transform.position each
frame by adding a displacement vector. The displacement is typically the product of a direction vector,
a speed scalar, and Time.deltaTime.
using UnityEngine;
public class SimpleMovement : MonoBehaviour
{
[SerializeField] private float speed = 5f;
void Update()
{
// Move forward (along world Z axis) at 'speed' units per second
transform.position += Vector3.forward * speed * Time.deltaTime;
}
}
Why Time.deltaTime?
Time.deltaTime is the time in seconds that elapsed since the last frame. Multiplying your
movement by it makes the motion frame-rate independent. Without it, an object would
move much faster on a machine running at 120 FPS than on one running at 30 FPS because
Update() is called more times per second on the faster machine. With
Time.deltaTime, the total distance traveled per second is always the same regardless of
frame rate: speed (units/s) × deltaTime (s/frame) = units/frame.
// BAD: frame-rate dependent moves 5 units per FRAME
transform.position += Vector3.forward * 5f;
// GOOD: frame-rate independent moves 5 units per SECOND
transform.position += Vector3.forward * 5f * Time.deltaTime;
This simple multiplication is one of the most important habits in game development. Always multiply
by Time.deltaTime whenever you are driving continuous motion or change in
Update().
4. The Translate Function
transform.Translate() is a convenience method for moving an object. At first glance it
looks identical to directly modifying transform.position, but there is a crucial
difference: by default, Translate moves the object relative to its local axes,
not the world axes.
// These are NOT equivalent if the object is rotated:
// 1. Moves along world Z axis regardless of object rotation
transform.position += Vector3.forward * speed * Time.deltaTime;
// 2. Moves along the object's LOCAL Z axis (its "forward")
transform.Translate(Vector3.forward * speed * Time.deltaTime);
// Equivalent to:
transform.Translate(0f, 0f, speed * Time.deltaTime);
You can control which space Translate uses with an optional second parameter:
// Move in local space (default)
transform.Translate(Vector3.forward * speed * Time.deltaTime, Space.Self);
// Move in world space
transform.Translate(Vector3.forward * speed * Time.deltaTime, Space.World);
Local-space Translate is perfect for first-person or third-person character controllers where "press W to move forward" means "move in the direction I am facing," not "move along world Z." World-space movement is better for top-down games or any scenario where you always want to move along fixed axes.
5. Cumulative Rotations
Alongside Translate, Unity provides transform.Rotate() for applying
incremental rotations. Unlike setting transform.eulerAngles directly (which sets an
absolute rotation), Rotate adds to the current rotation making it ideal for
spinning objects, turntable effects, and player-driven rotation.
using UnityEngine;
public class SpinningObject : MonoBehaviour
{
[SerializeField] private float rotationSpeed = 90f; // degrees per second
void Update()
{
// Rotate around the Y axis (spin left/right)
transform.Rotate(0f, rotationSpeed * Time.deltaTime, 0f);
}
}
Like Translate, Rotate also accepts a Space parameter:
// Rotate around the object's own Y axis (local space default)
transform.Rotate(Vector3.up * speed * Time.deltaTime, Space.Self);
// Rotate around the world Y axis (always the vertical axis)
transform.Rotate(Vector3.up * speed * Time.deltaTime, Space.World);
6. Keyboard Input
Unity's Input system provides several ways to read keyboard and gamepad input. The
most common for movement are the GetAxis functions, which return a smooth float value
between -1 and 1. They support both keyboard keys and gamepad sticks and include built-in
smoothing (the value ramps up and down rather than snapping instantly).
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private float speed = 5f;
[SerializeField] private float rotationSpeed = 180f;
void Update()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// Build movement vector
Vector3 movement = new Vector3(horizontal, 0f, vertical);
// Normalize to prevent diagonal movement being faster
if (movement.magnitude > 1f)
movement.Normalize();
// Apply movement
transform.position += movement * speed * Time.deltaTime;
}
}
For raw, unsmoothed input (immediately -1 or 1, no ramping), use Input.GetAxisRaw().
For checking specific keys without an axis mapping, use Input.GetKey():
// Raw input snaps immediately, no smoothing
float rawH = Input.GetAxisRaw("Horizontal");
// Key-specific checks
if (Input.GetKey(KeyCode.W)) Debug.Log("W held");
if (Input.GetKeyDown(KeyCode.Space)) Debug.Log("Space just pressed");
if (Input.GetKeyUp(KeyCode.Space)) Debug.Log("Space just released");
7. Exercise: Movement and Rotation
Exercise: Create a GameObject with a MeshRenderer (a cube will do). Write a script
that allows the player to move it left/right using the Horizontal axis and rotate it on the Y axis
using the mouse's X delta (Input.GetAxis("Mouse X")). The movement speed and rotation
speed should be configurable in the Inspector.
Solution:
using UnityEngine;
public class MovementAndRotation : MonoBehaviour
{
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float rotateSpeed = 120f;
void Update()
{
// Horizontal movement (A/D keys)
float h = Input.GetAxis("Horizontal");
transform.Translate(h * moveSpeed * Time.deltaTime, 0f, 0f, Space.World);
// Y-axis rotation with mouse
float mouseX = Input.GetAxis("Mouse X");
transform.Rotate(0f, mouseX * rotateSpeed * Time.deltaTime, 0f, Space.World);
}
}
8. Performance Best Practices
Two simple habits will significantly improve the performance of any Unity script that runs every frame.
Cache component references. Calling GetComponent<T>() is not
free it searches the component list of the GameObject each call. If you need a component every frame,
retrieve it once in Awake() or Start() and store it in a private field:
using UnityEngine;
public class OptimizedScript : MonoBehaviour
{
private Rigidbody rb;
private Animator anim;
private AudioSource audioSource;
void Awake()
{
rb = GetComponent<Rigidbody>();
anim = GetComponent<Animator>();
audioSource = GetComponent<AudioSource>();
}
void Update()
{
rb.AddForce(Vector3.forward * 10f);
}
}
Always use Time.deltaTime. As covered in section 3, every continuous change applied in
Update() must be multiplied by Time.deltaTime to remain frame-rate
independent. This is not optional it is a correctness requirement.
9. The Angle() Method
Vector3.Angle(from, to) returns the angle in degrees between two direction vectors.
It always returns a value between 0 and 180 it does not distinguish "left of" from "right of." For
signed angles, you need the Cross product (covered in section 13).
using UnityEngine;
public class FieldOfView : MonoBehaviour
{
[SerializeField] private Transform enemy;
[SerializeField] private float fieldOfViewAngle = 90f; // total FOV in degrees
void Update()
{
Vector3 directionToEnemy = enemy.position - transform.position;
float angle = Vector3.Angle(transform.forward, directionToEnemy);
bool isVisible = angle < fieldOfViewAngle / 2f;
if (isVisible)
{
Debug.Log($"Enemy is visible! Angle: {angle:F1}°");
}
}
}
10. ClampMagnitude
Vector3.ClampMagnitude(vector, maxMagnitude) limits the length of a vector
without changing its direction. If the vector's magnitude is already below maxMagnitude,
it is returned unchanged. If it exceeds the limit, it is scaled down to have exactly
maxMagnitude as its length.
using UnityEngine;
public class ClampedMovement : MonoBehaviour
{
[SerializeField] private float maxSpeed = 5f;
void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
Vector3 inputVector = new Vector3(h, 0f, v);
Vector3 clampedInput = Vector3.ClampMagnitude(inputVector, 1f);
transform.position += clampedInput * maxSpeed * Time.deltaTime;
}
}
11. The normalized Getter and magnitude
A normalized vector (also called a unit vector) is a vector that has been scaled so that its magnitude equals exactly 1. It retains its original direction but carries no information about length. Normalized vectors are essential whenever you need a pure direction for shooting a projectile toward a target, for applying a force in a specific direction, for checking angular relationships.
vector.normalizedreturns a new vector with the same direction and magnitude = 1 (does not modify the original).vector.magnitudereturns the length of the vector (uses square root prefersqrMagnitudefor comparisons).
Vector3 velocity = new Vector3(3f, 0f, 4f);
float length = velocity.magnitude;
Debug.Log($"Length: {length}"); // 5
Vector3 direction = velocity.normalized;
Debug.Log($"Normalized: {direction}"); // (0.6, 0, 0.8)
Debug.Log($"Normalized length: {direction.magnitude}"); // 1
// Common pattern: get direction to target, then move at constant speed
Vector3 toTarget = target.position - transform.position;
Vector3 moveDir = toTarget.normalized;
transform.position += moveDir * speed * Time.deltaTime;
12. The Dot() Method
The dot product (also called the scalar product or inner product) is one of the most powerful operations in game mathematics. Given two vectors A and B, the dot product is defined as:
Dot(A, B) = Ax·Bx + Ay·By + Az·Bz = |A| × |B| × cos(θ)
where θ is the angle between the two vectors. When both vectors are normalized (magnitude = 1), this
simplifies to just cos(θ), which gives you an immediately useful result:
- +1 the vectors point in exactly the same direction (θ = 0°)
- 0 the vectors are perpendicular (θ = 90°)
- −1 the vectors point in exactly opposite directions (θ = 180°)
using UnityEngine;
public class DotProductExample : MonoBehaviour
{
[SerializeField] private Transform target;
void Update()
{
Vector3 toTarget = (target.position - transform.position).normalized;
float dot = Vector3.Dot(transform.forward, toTarget);
if (dot > 0.5f)
Debug.Log("Target is directly ahead");
else if (dot > 0f)
Debug.Log("Target is in front (but to the side)");
else
Debug.Log("Target is behind us");
float angleRadians = Mathf.Acos(Mathf.Clamp(dot, -1f, 1f));
float angleDegrees = angleRadians * Mathf.Rad2Deg;
Debug.Log($"Angle to target: {angleDegrees:F1}°");
}
}
13. The Cross() Method
The cross product takes two 3D vectors and returns a third vector that is perpendicular to both of them. This is a uniquely 3D operation (it does not exist for 2D vectors).
Vector3.Cross(Vector3.right, Vector3.up) == Vector3.back // (-Z)
Vector3.Cross(Vector3.up, Vector3.right) == Vector3.forward // (+Z)
// Order matters! Cross(A, B) == -Cross(B, A)
using UnityEngine;
public class CrossProductExample : MonoBehaviour
{
[SerializeField] private Transform target;
void Update()
{
Vector3 toTarget = (target.position - transform.position).normalized;
Vector3 cross = Vector3.Cross(transform.forward, toTarget);
// cross.y > 0: target is to the LEFT of our forward direction
// cross.y < 0: target is to the RIGHT
if (cross.y > 0f)
Debug.Log("Target is to the left");
else if (cross.y < 0f)
Debug.Log("Target is to the right");
else
Debug.Log("Target is directly ahead or behind");
Debug.DrawRay(transform.position, cross * 2f, Color.yellow);
}
}
14. The Distance() Method
Vector3.Distance(A, B) returns the Euclidean distance between two points. It is exactly
equivalent to (A - B).magnitude.
float d1 = Vector3.Distance(playerPos, enemyPos);
float d2 = (playerPos - enemyPos).magnitude;
// For comparison only (no sqrt), use sqrMagnitude:
float sqrDist = (playerPos - enemyPos).sqrMagnitude;
bool inRange = sqrDist < (attackRange * attackRange);
15. The Scale() Method
Vector3.Scale(A, B) performs component-wise multiplication: each component of
A is multiplied by the corresponding component of B.
Vector3 velocity = new Vector3(3f, 5f, 2f);
Vector3 damping = new Vector3(0.8f, 1f, 0.5f);
Vector3 dampedVelocity = Vector3.Scale(velocity, damping);
// Result: (3*0.8, 5*1, 2*0.5) = (2.4, 5, 1)
16. The MoveTowards() Method
Vector3.MoveTowards(current, target, maxDelta) moves current toward
target by at most maxDelta units per call. Unlike Vector3.Lerp,
which moves a fraction of the remaining distance, MoveTowards moves a
fixed distance per frame. It is guaranteed to reach the target and will never overshoot.
using UnityEngine;
public class MoveTowardsExample : MonoBehaviour
{
[SerializeField] private Transform targetPoint;
[SerializeField] private float speed = 3f;
void Update()
{
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(
transform.position,
targetPoint.position,
step
);
if (transform.position == targetPoint.position)
{
Debug.Log("Arrived at destination.");
}
}
}
// Lerp: exponential decay approach smooth but never exactly arrives
transform.position = Vector3.Lerp(transform.position, target.position, 0.1f);
// MoveTowards: constant speed arrives exactly, guaranteed
transform.position = Vector3.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
17. Calculating a Vector Between Two Points
The formula is simply Direction = B − A (Target minus Origin). If you only care about direction and not distance, normalize it to get a unit vector.
using UnityEngine;
public class ProjectileShooter : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private GameObject bulletPrefab;
[SerializeField] private float bulletSpeed = 20f;
void Shoot()
{
Vector3 direction = target.position - transform.position;
Vector3 normalizedDirection = direction.normalized;
GameObject bullet = Instantiate(bulletPrefab, transform.position, Quaternion.identity);
Rigidbody bulletRb = bullet.GetComponent<Rigidbody>();
bulletRb.linearVelocity = normalizedDirection * bulletSpeed;
}
}
18. Calculating a Position on a Vector (Lerp)
Vector3.Lerp(A, B, t) returns the position that is fraction t of the way
from A to B. When t = 0 it returns A; when
t = 1 it returns B.
Vector3 start = new Vector3(0f, 0f, 0f);
Vector3 end = new Vector3(10f, 0f, 0f);
Vector3 midpoint = Vector3.Lerp(start, end, 0.5f); // (5, 0, 0)
Vector3 atStart = Vector3.Lerp(start, end, 0f); // (0, 0, 0)
Vector3 atEnd = Vector3.Lerp(start, end, 1f); // (10, 0, 0)
// Smooth camera follow using Lerp (ease-out effect)
void LateUpdate()
{
float smoothSpeed = 5f;
Vector3 desiredPos = target.position + offset;
transform.position = Vector3.Lerp(transform.position, desiredPos, smoothSpeed * Time.deltaTime);
}
19. Switching Between Spaces
Unity's Transform component provides methods for converting points and directions between local space and world space.
// Local point → World point
Vector3 worldPoint = transform.TransformPoint(localPoint);
// World point → Local point
Vector3 localPosition = transform.InverseTransformPoint(worldPosition);
// Local direction → World direction
Vector3 worldDirection = transform.TransformDirection(localDirection);
// World direction → Local direction
Vector3 localDir = transform.InverseTransformDirection(worldDir);
// Convert enemy's world position to local space relative to player
Vector3 enemyInLocalSpace = transform.InverseTransformPoint(enemy.transform.position);
// In local space, positive Z is always "in front of the object"
bool isInFront = enemyInLocalSpace.z > 0f;
bool isToTheRight = enemyInLocalSpace.x > 0f;
20. Debugging a Vector: DrawRay and DrawLine
One of the most valuable skills in Unity development is being able to visualize your vectors directly in the Scene view as colored lines and rays.
void Update()
{
// Visualize our forward vector (green)
Debug.DrawRay(transform.position, transform.forward * 3f, Color.green);
// Visualize our right vector (red)
Debug.DrawRay(transform.position, transform.right * 3f, Color.red);
// Visualize our up vector (blue)
Debug.DrawRay(transform.position, transform.up * 3f, Color.blue);
}
// Debug.DrawLine(start, end, color, duration)
void DrawConnectionLine(Transform from, Transform to)
{
Debug.DrawLine(from.position, to.position, Color.cyan);
}
using UnityEngine;
public class VectorDebugger : MonoBehaviour
{
[SerializeField] private Transform target;
void Update()
{
Vector3 toTarget = target.position - transform.position;
Vector3 normalizedToTarget = toTarget.normalized;
Debug.DrawLine(transform.position, target.position, Color.white);
Debug.DrawRay(transform.position, normalizedToTarget * 2f, Color.magenta);
Debug.DrawRay(transform.position, transform.forward * 2f, Color.green);
Vector3 perp = Vector3.Cross(transform.forward, normalizedToTarget);
Debug.DrawRay(transform.position, perp * 2f, Color.yellow);
}
}
21. Exercise: 3D Object Visualizer with Mouse
Exercise: Create a scene with a sphere at the origin and a target cube at position (5, 0, 5). Write a script attached to the sphere that draws a white line to the cube, a green ray showing the sphere's forward direction, a yellow perpendicular ray using Cross product, displays the current angle in the Inspector, and rotates the sphere with the horizontal mouse axis.
Solution:
using UnityEngine;
public class VectorVisualizer : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private float rotationSpeed = 90f;
[Header("Debug Info (read-only)")]
[SerializeField] private float angleToTarget;
[SerializeField] private string targetSide;
void Update()
{
float mouseX = Input.GetAxis("Mouse X");
transform.Rotate(0f, mouseX * rotationSpeed * Time.deltaTime, 0f, Space.World);
Vector3 toTarget = target.position - transform.position;
Vector3 dirNorm = toTarget.normalized;
Debug.DrawLine(transform.position, target.position, Color.white);
Debug.DrawRay(transform.position, transform.forward * 3f, Color.green);
Vector3 perp = Vector3.Cross(transform.forward, dirNorm);
Debug.DrawRay(transform.position, perp * 2f, Color.yellow);
angleToTarget = Vector3.Angle(transform.forward, toTarget);
float dotRight = Vector3.Dot(transform.right, dirNorm);
targetSide = dotRight > 0f ? "Right" : "Left";
}
}
Run this in the editor with the Scene view open alongside the Game view. As you move the mouse, watch the green forward ray rotate, see the yellow perpendicular ray respond, and observe the angle value in the Inspector update in real time.
With a thorough understanding of vectors, you now have the tools to handle the vast majority of movement, direction, and spatial relationship calculations in any Unity project. In Chapter 3, we will turn to rotations specifically to the Quaternion type, which Unity uses to represent orientations without the problems caused by Euler angle representations.
Need help with Unity3D development?
I'm a senior developer with 16+ years experience, including AAA projects at Ubisoft. Let's discuss how I can help with your game or interactive project.
Start a Conversation