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);
// Rotate around all three axes at once
// transform.Rotate(30f * Time.deltaTime, 45f * 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()
{
// GetAxis returns a float: -1, 0, or 1 (with smoothing)
// "Horizontal" = A/D keys or Left/Right arrows or left stick X
// "Vertical" = W/S keys or Up/Down arrows or left stick Y
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
{
// Cache expensive references
private Rigidbody rb;
private Animator anim;
private AudioSource audioSource;
void Awake()
{
// Resolve once — not every frame
rb = GetComponent<Rigidbody>();
anim = GetComponent<Animator>();
audioSource = GetComponent<AudioSource>();
}
void Update()
{
// Use cached references — no per-frame lookup cost
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).
A classic use case is checking whether an enemy is roughly in front of the player — within a field of view (FOV) cone. If the angle between the player's forward vector and the vector to the enemy is less than half the FOV angle, the enemy is visible:
using UnityEngine;
public class FieldOfView : MonoBehaviour
{
[SerializeField] private Transform enemy;
[SerializeField] private float fieldOfViewAngle = 90f; // total FOV in degrees
void Update()
{
// Vector from this object to the enemy
Vector3 directionToEnemy = enemy.position - transform.position;
// Angle between our forward direction and the direction to the enemy
float angle = Vector3.Angle(transform.forward, directionToEnemy);
// The enemy is within the FOV cone if the angle is within half the FOV
bool isVisible = angle < fieldOfViewAngle / 2f;
if (isVisible)
{
Debug.Log($"Enemy is visible! Angle: {angle:F1}°");
}
}
}
Note that Vector3.Angle does not account for distance — it only checks the angular
relationship between the two directions. In a real game you would also check distance separately (using
sqrMagnitude as discussed in Chapter 1) and potentially add a line-of-sight raycast.
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.
This is particularly useful for capping movement speed when a player is using multiple simultaneous inputs. Without clamping, moving diagonally (combining horizontal and vertical input) produces a vector with magnitude ≈1.414 rather than 1, making diagonal movement ~41% faster than axis-aligned movement.
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);
// Without clamping, diagonal input gives magnitude ~1.414
// With clamping, magnitude is always ≤ 1
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.
Every Vector3 in Unity exposes two key properties related to this:
vector.normalized— returns a new vector with the same direction and magnitude = 1 (does not modify the original).vector.magnitude— returns the length of the vector (uses square root — prefersqrMagnitudefor comparisons).
Vector3 velocity = new Vector3(3f, 0f, 4f);
// Magnitude: √(9 + 0 + 16) = √25 = 5
float length = velocity.magnitude;
Debug.Log($"Length: {length}"); // 5
// Normalized: direction only, length = 1
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;
Be careful: if you call .normalized on a zero vector (Vector3.zero), the
result is still Vector3.zero — Unity handles this gracefully, but moving toward a target
that is at your exact position will produce no movement rather than an error.
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°)
- Any value between −1 and +1 for angles in between
using UnityEngine;
public class DotProductExample : MonoBehaviour
{
[SerializeField] private Transform target;
void Update()
{
// Direction from this object to the target
Vector3 toTarget = (target.position - transform.position).normalized;
// Dot product of our forward direction with the direction to target
float dot = Vector3.Dot(transform.forward, toTarget);
// dot > 0: target is somewhere in front of us
// dot < 0: target is somewhere behind us
// dot ≈ 1: target is almost directly in front
// dot ≈ -1: target is almost directly behind
if (dot > 0.5f) // angle < 60°
{
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");
}
// You can recover the angle using Mathf.Acos (returns radians, convert to degrees):
float angleRadians = Mathf.Acos(Mathf.Clamp(dot, -1f, 1f));
float angleDegrees = angleRadians * Mathf.Rad2Deg;
Debug.Log($"Angle to target: {angleDegrees:F1}°");
}
}
The dot product is faster than Vector3.Angle() for threshold checks because it avoids
the inverse-cosine computation. When you only need to know "is the target in front or behind?" a
single dot product and a comparison against 0 is all you need.
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). The magnitude of the result equals the area of the parallelogram formed by the two input vectors; the direction follows the right-hand rule: if you point your right hand's fingers along A and curl them toward B, your thumb points in the direction of the cross product.
In Unity's left-handed coordinate system, the right-hand rule is applied accordingly. The key identity is:
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)
Practical uses of the cross product include:
- Finding the normal of a surface given two edge vectors
- Computing the perpendicular direction to a slope
- Determining whether a target is to the left or right of a direction (the sign of the Y component tells you)
- Building orthonormal bases (creating a local coordinate frame from a single forward vector)
using UnityEngine;
public class CrossProductExample : MonoBehaviour
{
[SerializeField] private Transform target;
void Update()
{
Vector3 toTarget = (target.position - transform.position).normalized;
// The cross product of forward and toTarget
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
// cross.y ≈ 0: target is directly in front or behind
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");
// Visualize the perpendicular vector in the Scene view
Debug.DrawRay(transform.position, cross * 2f, Color.yellow);
}
}
14. The Distance() Method
As covered in Chapter 1, Vector3.Distance(A, B) returns the Euclidean distance between
two points. It is provided here in the vectors chapter to show how it relates to vector operations:
it is exactly equivalent to (A - B).magnitude.
Vector3 playerPos = transform.position;
Vector3 enemyPos = enemy.transform.position;
// These three are mathematically identical:
float d1 = Vector3.Distance(playerPos, enemyPos);
float d2 = (playerPos - enemyPos).magnitude;
float d3 = (enemyPos - playerPos).magnitude; // subtraction order doesn't matter for 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. This is
different from multiplying a vector by a scalar — scalar multiplication multiplies all three components
by the same number, while Scale can apply a different multiplier to each axis.
Vector3 velocity = new Vector3(3f, 5f, 2f);
Vector3 damping = new Vector3(0.8f, 1f, 0.5f); // slow down X, preserve Y, halve Z
Vector3 dampedVelocity = Vector3.Scale(velocity, damping);
// Result: (3*0.8, 5*1, 2*0.5) = (2.4, 5, 1)
// Common use: non-uniform scaling of an offset
Vector3 localOffset = new Vector3(1f, 2f, 1f);
Vector3 objectScale = transform.localScale;
Vector3 worldOffset = Vector3.Scale(localOffset, objectScale);
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. This means it is guaranteed to reach the target eventually and will
never overshoot it — when the distance is smaller than maxDelta, it returns exactly
target.
This makes MoveTowards ideal for any situation where you want constant-speed movement
with a hard stop at the destination: enemy waypoint navigation, a UI element sliding into position, an
object being placed precisely.
using UnityEngine;
public class MoveTowardsExample : MonoBehaviour
{
[SerializeField] private Transform targetPoint;
[SerializeField] private float speed = 3f;
void Update()
{
// maxDelta = how many units to move this frame at most
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(
transform.position,
targetPoint.position,
step
);
// Check arrival
if (transform.position == targetPoint.position)
{
Debug.Log("Arrived at destination.");
}
}
}
MoveTowards vs Lerp
The key difference: Lerp moves a fraction of the remaining distance each frame,
which produces a pleasing ease-out effect but mathematically never fully arrives (the gap halves each
frame). MoveTowards moves a fixed distance each frame, so arrival is guaranteed and the
speed is constant. Choose Lerp for visual smoothing; choose MoveTowards for game-logic precision.
// 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
One of the most common vector calculations is finding the direction from point A to point B. The formula is simply Direction = B − A (Target minus Origin). The result is a vector that, when added to A, gives you B. 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 Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Shoot();
}
}
void Shoot()
{
// Direction from shooter to target
Vector3 direction = target.position - transform.position;
// Normalize to get unit direction (magnitude = 1)
Vector3 normalizedDirection = direction.normalized;
// Spawn bullet at shooter's position
GameObject bullet = Instantiate(bulletPrefab, transform.position, Quaternion.identity);
// Apply velocity in the normalized direction
Rigidbody bulletRb = bullet.GetComponent<Rigidbody>();
bulletRb.linearVelocity = normalizedDirection * bulletSpeed;
}
}
Notice the critical step: we normalize the direction vector before multiplying by speed. Without normalization, the bullet speed would vary depending on how far the target is, since the raw direction vector has magnitude equal to the distance between the objects.
18. Calculating a Position on a Vector (Lerp)
Vector3.Lerp(A, B, t) — linear interpolation between two points — 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; when t = 0.5
it returns the midpoint.
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 oneQuarter = Vector3.Lerp(start, end, 0.25f); // (2.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);
}
Mathf.Lerp works the same way for scalar values, and Quaternion.Slerp
(spherical interpolation) is the rotation equivalent, which we will cover in depth in Chapter 3.
19. Switching Between Spaces
Unity's Transform component provides methods for converting points and directions between local space and world space. These are essential when you have a position or direction expressed in one space and need it in the other — for example, when you know a target's world position but need to express it relative to a character's body for animation purposes.
// --- POINT CONVERSION ---
// Local point → World point
// Takes a position defined in this object's local space and converts to world space
Vector3 localPoint = new Vector3(0f, 1f, 2f); // 2 units in front of object, 1 up
Vector3 worldPoint = transform.TransformPoint(localPoint);
// World point → Local point
// Takes a world position and converts to this object's local space
Vector3 worldPosition = new Vector3(5f, 0f, 10f);
Vector3 localPosition = transform.InverseTransformPoint(worldPosition);
// --- DIRECTION CONVERSION ---
// Local direction → World direction (takes into account rotation only, not position/scale)
Vector3 localDirection = Vector3.forward; // (0, 0, 1) in local space
Vector3 worldDirection = transform.TransformDirection(localDirection);
// If object is rotated 90° around Y, worldDirection becomes Vector3.right
// World direction → Local direction
Vector3 worldDir = Vector3.right;
Vector3 localDir = transform.InverseTransformDirection(worldDir);
A practical example: checking if a world-space target is in front of or behind an object, expressed in that object's local coordinate system:
// 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;
Debug.Log($"Enemy is {(isInFront ? "in front of" : "behind")} the player");
Debug.Log($"Enemy is to the {(isToTheRight ? "right" : "left")} of the player");
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. This makes debugging movement, physics, and AI behaviour vastly easier — you can see at a glance whether your direction vectors are pointing where you expect, how far your raycasts reach, and what the velocity of an object looks like.
Unity provides two methods for this in the Debug class. They only draw in the editor
(they produce no output in builds) and only in the Scene view (not the Game view), so you can leave
them in your code during development without any runtime cost in shipped builds.
// Debug.DrawRay(origin, direction, color, duration)
// Draws a ray starting at 'origin' going in 'direction' for 'duration' seconds
// If duration is omitted, the ray lasts for one frame only
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);
// Visualize velocity (if we have a Rigidbody)
Rigidbody rb = GetComponent<Rigidbody>();
if (rb != null)
{
Debug.DrawRay(transform.position, rb.linearVelocity, Color.yellow);
}
}
// Debug.DrawLine(start, end, color, duration)
// Draws a line from 'start' to 'end'
void DrawConnectionLine(Transform from, Transform to)
{
Debug.DrawLine(from.position, to.position, Color.cyan);
}
The difference between DrawRay and DrawLine: DrawRay takes a
start point and a direction vector (the endpoint is start + direction), while
DrawLine takes explicit start and end points. Both are equally useful; the choice
depends on what data you have available.
using UnityEngine;
public class VectorDebugger : MonoBehaviour
{
[SerializeField] private Transform target;
void Update()
{
Vector3 toTarget = target.position - transform.position;
Vector3 normalizedToTarget = toTarget.normalized;
// Draw the full displacement vector to the target (white)
Debug.DrawLine(transform.position, target.position, Color.white);
// Draw the normalized direction (scaled to 2 units) in magenta
Debug.DrawRay(transform.position, normalizedToTarget * 2f, Color.magenta);
// Draw the object's actual forward direction (green)
Debug.DrawRay(transform.position, transform.forward * 2f, Color.green);
// Draw the perpendicular (cross product) direction (yellow)
Vector3 perp = Vector3.Cross(transform.forward, normalizedToTarget);
Debug.DrawRay(transform.position, perp * 2f, Color.yellow);
}
}
Make it a habit to add Debug.DrawRay calls whenever you are working with direction vectors
or movement logic. The ability to see your vectors in the Scene view will save you hours of debugging
time over the course of a project.
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 from the sphere to the cube every frame.
- Draws a green ray showing the sphere's forward direction (length: 3 units).
- Computes and draws a yellow ray showing the perpendicular direction (using Cross product, length: 2 units).
- Displays in the Inspector (using a
[SerializeField]float) the current angle between the sphere's forward direction and the direction to the cube. - Rotates the sphere with the horizontal mouse axis (
Input.GetAxis("Mouse X")) so you can see the angle and perpendicular direction update in real time.
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()
{
// Rotate with mouse
float mouseX = Input.GetAxis("Mouse X");
transform.Rotate(0f, mouseX * rotationSpeed * Time.deltaTime, 0f, Space.World);
// Direction to target
Vector3 toTarget = target.position - transform.position;
Vector3 dirNorm = toTarget.normalized;
// Draw vectors
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);
// Angle
angleToTarget = Vector3.Angle(transform.forward, toTarget);
// Side (using dot product on right axis)
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. This kind of live visual feedback is exactly how experienced Unity developers debug spatial problems.
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