Chapter 1: The Basics — Coordinates, Distances and Utility Functions

Game DevelopmentMath for Unity3DJune 201820 min read

1. Local Space and Global Space

Before writing a single line of code, you need to understand one of the most fundamental concepts in any 3D engine: the difference between local space and global space (also called world space). Confusing the two is one of the most common sources of bugs for developers new to Unity — and sometimes even for experienced ones.

Imagine a passenger sitting in a car. From the passenger's perspective, they are always in the same seat — their position relative to the car never changes. That is their local position. But from the perspective of someone standing on the street watching the car drive past, the passenger's position is constantly changing. That is their global (world) position. Both descriptions are correct; they simply use different frames of reference.

In Unity, every GameObject exists in the global world space, but it also has a local space defined relative to its parent. When you look at the Inspector panel with a GameObject selected, the Transform component shows local coordinates — position, rotation, and scale relative to the parent. If the object has no parent, local space and world space happen to be the same thing, which can make the distinction feel invisible — until the day you nest one object inside another and everything starts behaving unexpectedly.

The global/local toggle button in the top-left corner of the Unity editor (next to the transform tool buttons) affects only the manipulation gizmos — that is, it changes whether the move/rotate handles appear aligned to the world axes or to the object's local axes. It does not change how coordinates are stored or reported in the Inspector. Keep this distinction clearly in mind: the toggle is a visual aid for editing, nothing more.

In C# code, you access the two positions through the Transform component:

// World (global) position of this GameObject
Vector3 worldPos = transform.position;

// Local position relative to parent (same as world if no parent)
Vector3 localPos = transform.localPosition;

// Setting position in world space
transform.position = new Vector3(0f, 1f, 5f);

// Setting position in local space
transform.localPosition = new Vector3(0f, 0f, 2f);

A practical rule of thumb: when you want to place an object at a specific point in the world regardless of its hierarchy, use transform.position. When you want to position an object relative to its parent — such as a weapon in a character's hand — use transform.localPosition.

2. Cartesian Coordinates and the Transform Component

Unity uses a left-handed Cartesian coordinate system. The X axis points right, the Y axis points up, and the Z axis points forward (into the screen). Every point in space is described by three numbers (x, y, z), which is exactly what Unity's Vector3 struct stores. Positions and scales are represented as Vector3; rotations are stored as Quaternion internally, though the Inspector displays them as Euler angles in degrees for human readability.

The Transform component is special: it is the only component that is present on every single GameObject in Unity, without exception. You cannot remove it. This makes it the anchor point for all spatial information about an object.

You access the Transform component through the transform getter (lowercase t) available on any MonoBehaviour. An important note on performance: older Unity documentation and tutorials sometimes recommended using GetComponent<Transform>() rather than the transform shortcut, claiming the shortcut performed a component lookup every frame. This is no longer true. Since Unity 4.x, the transform property is cached automatically by the engine. Do not use GetComponent<Transform>() — it is more verbose, less readable, and provides no performance benefit.

using UnityEngine;

public class CoordinatesExample : MonoBehaviour
{
    void Start()
    {
        // Accessing position (world space) — transform is automatically cached
        Vector3 pos = transform.position;
        Debug.Log("World position: " + pos);

        // Accessing local position
        Vector3 localPos = transform.localPosition;
        Debug.Log("Local position: " + localPos);

        // Accessing rotation as Quaternion
        Quaternion rot = transform.rotation;

        // Accessing rotation as Euler angles (degrees) — convenient for display
        Vector3 eulerRot = transform.eulerAngles;
        Debug.Log("Rotation (Euler): " + eulerRot);

        // Accessing local Euler angles (relative to parent)
        Vector3 localEuler = transform.localEulerAngles;

        // Accessing scale (always local — there is no world-space scale getter)
        Vector3 scale = transform.localScale;
        Debug.Log("Scale: " + scale);
    }
}

Note that scale is always expressed in local space through transform.localScale. Unity does not expose a simple world-space scale property because non-uniform parent scales make world-space scale mathematically complex to decompose cleanly. If you need the world-space scale, you can read it from the transformation matrix: transform.lossyScale (read-only).

3. Calculating the Distance Between Two Points

Knowing how far apart two objects are is one of the most common needs in game programming. Is the player close enough to a door to open it? Is the enemy within attack range? Has the projectile reached the explosion radius? All of these questions boil down to calculating a distance.

Mathematically, the distance between two points A and B in 3D space is given by the Euclidean distance formula:

Distance = |A − B| = √( (Ax − Bx)² + (Ay − By)² + (Az − Bz)² )

You could implement this manually using Mathf.Sqrt and Mathf.Pow:

Vector3 A = new Vector3(1f, 0f, 2f);
Vector3 B = new Vector3(4f, 0f, 6f);

float distance = Mathf.Sqrt(
    Mathf.Pow(A.x - B.x, 2) +
    Mathf.Pow(A.y - B.y, 2) +
    Mathf.Pow(A.z - B.z, 2)
);
Debug.Log("Distance: " + distance); // 5

But Unity already provides this as a one-liner with Vector3.Distance():

float distance = Vector3.Distance(A, B);

// Or equivalently, using the magnitude of the difference vector:
float distance2 = (A - B).magnitude;

Both produce identical results. The choice between them is a matter of readability: use Vector3.Distance() when the intent is clearly "how far apart are these two points?", and use (A - B).magnitude when you already have the difference vector and need its length.

Performance: sqrMagnitude vs magnitude

Computing the square root (the final step in the distance formula) is one of the more expensive floating-point operations a CPU performs. When you only need to compare distances — for example, to check if an object is within a certain radius — you can avoid the square root entirely by comparing squared distances instead. Unity provides sqrMagnitude for exactly this purpose:

float attackRange = 5f;
Vector3 enemyPos = enemy.transform.position;
Vector3 playerPos = transform.position;

// Inefficient: performs a square root
if (Vector3.Distance(enemyPos, playerPos) < attackRange)
{
    Attack();
}

// Efficient: compare squares, no square root needed
if ((enemyPos - playerPos).sqrMagnitude < attackRange * attackRange)
{
    Attack();
}

The result is mathematically equivalent — if (A - B).magnitude < r then (A - B).sqrMagnitude < r * r — but the second version avoids the expensive Mathf.Sqrt call, which matters when this check runs hundreds of times per frame (for example, in AI systems with many enemies).

4. Value Clamping with Mathf.Clamp

Clamping means constraining a value so that it cannot exceed a specified range. It is one of those utility operations that appears constantly in game code, often in situations you would not immediately expect: health bars, volume sliders, camera zoom levels, animation blend weights, and many more.

The mathematical definition is simple: Clamp(value, min, max) returns min if value < min, returns max if value > max, and returns value unchanged otherwise. Unity provides this as Mathf.Clamp():

// Clamp a player's health between 0 and 100
float health = 150f; // e.g., after a heal that would overshoot max HP
health = Mathf.Clamp(health, 0f, 100f);
Debug.Log(health); // 100

float damage = -10f; // e.g., a negative damage value from a bug
health = Mathf.Clamp(health + damage, 0f, 100f);
Debug.Log(health); // 90 (100 - 10)

// If we try to go below 0:
health = Mathf.Clamp(health - 200f, 0f, 100f);
Debug.Log(health); // 0 — prevented from going negative

A complete health system example showing clamping in context:

using UnityEngine;

public class HealthSystem : MonoBehaviour
{
    [SerializeField] private float maxHealth = 100f;
    private float currentHealth;

    void Start()
    {
        currentHealth = maxHealth;
    }

    public void TakeDamage(float amount)
    {
        currentHealth = Mathf.Clamp(currentHealth - amount, 0f, maxHealth);
        Debug.Log($"Health after damage: {currentHealth}/{maxHealth}");

        if (currentHealth == 0f)
        {
            Die();
        }
    }

    public void Heal(float amount)
    {
        currentHealth = Mathf.Clamp(currentHealth + amount, 0f, maxHealth);
        Debug.Log($"Health after healing: {currentHealth}/{maxHealth}");
    }

    private void Die()
    {
        Debug.Log("Game over!");
        // Handle death logic
    }
}

Mathf.Clamp01

The range 0 to 1 is so common in game development — it represents normalized fractions, interpolation parameters, alpha values, blend weights, and much more — that Unity provides a dedicated shorthand: Mathf.Clamp01(t), which is exactly equivalent to Mathf.Clamp(t, 0f, 1f) but faster to type and immediately communicates the intended range to anyone reading the code.

float t = 1.5f;
t = Mathf.Clamp01(t);
Debug.Log(t); // 1

float t2 = -0.3f;
t2 = Mathf.Clamp01(t2);
Debug.Log(t2); // 0

float t3 = 0.7f;
t3 = Mathf.Clamp01(t3);
Debug.Log(t3); // 0.7 — within range, unchanged

5. Calculating a Percentage

Percentages appear everywhere in games: health bars, loading screens, experience bars, objective progress trackers, cooldown indicators. The underlying mathematics is always the same formula, just expressed in different contexts.

Given a current value and a maximum value, the percentage is:

percentage = (value / max) × 100

In practice, it is often more useful to work with the normalized form — a number between 0 and 1 rather than 0 and 100. This normalized value is conventionally called t (for "time" or "parameter"), and it feeds directly into Unity's Lerp functions and shader properties:

t = value / max (where t is in the range [0, 1])

// Example: experience bar
float currentXP = 750f;
float xpToNextLevel = 1000f;

// As a 0-100 percentage
float percentageDisplay = (currentXP / xpToNextLevel) * 100f;
Debug.Log($"XP: {percentageDisplay}%"); // 75%

// As a normalized 0-1 value (for UI fill amounts, shaders, etc.)
float normalizedXP = currentXP / xpToNextLevel;
Debug.Log($"Normalized XP: {normalizedXP}"); // 0.75

// Driving a UI Image fill amount (Image component, Fill mode)
// progressBarImage.fillAmount = normalizedXP;

// Loading screen example
float assetsLoaded = 37f;
float totalAssets = 80f;
float loadPercent = (assetsLoaded / totalAssets) * 100f;
Debug.Log($"Loading: {loadPercent:F1}%"); // "Loading: 46.3%"

Always make sure the denominator (max) is never zero before performing the division, or you will produce a divide-by-zero error. In cases where max could legitimately be zero, add a guard:

float safeNormalized = (maxHealth > 0f) ? currentHealth / maxHealth : 0f;

6. Calculating a Value from Its Percentage

The reverse operation — given a percentage (or normalized t), find the corresponding value — is equally common. If a player is at 30% of maximum health, what is their actual health value? If a progress bar is 60% filled, how many items have been processed?

The formula inverts the previous one:

value = (percentage / 100) × max

Or in normalized form:

value = t × max

float maxHealth = 200f;
float healthPercentage = 30f; // 30%

// Converting percentage to actual value
float currentHealth = (healthPercentage / 100f) * maxHealth;
Debug.Log($"Current health: {currentHealth}"); // 60

// Using normalized t directly
float t = 0.3f; // same as 30%
float healthFromT = t * maxHealth;
Debug.Log($"Health from t: {healthFromT}"); // 60

// Example: damage scaling
// A spell deals 25% of the target's max health as damage
float targetMaxHP = 500f;
float spellDamagePercent = 25f;
float damage = (spellDamagePercent / 100f) * targetMaxHP;
Debug.Log($"Spell damage: {damage}"); // 125

Connection to Lerp

You may have noticed that value = t × max (where t is between 0 and 1) is essentially what Mathf.Lerp does when the minimum is 0. More generally, Mathf.Lerp(min, max, t) gives you the value at fraction t of the way between min and max:

// These are equivalent when min = 0:
float value1 = t * max;
float value2 = Mathf.Lerp(0f, max, t);

// But Lerp is more powerful — it works for any range:
float minSpeed = 2f;
float maxSpeed = 10f;
float speedT = 0.75f; // 75% of the way to max speed

float currentSpeed = Mathf.Lerp(minSpeed, maxSpeed, speedT);
Debug.Log($"Speed: {currentSpeed}"); // 8  (2 + 0.75 * (10 - 2))

We will cover Lerp in depth in the vectors chapter, where it becomes one of the most important tools in your animation toolkit.

7. Inverting a Value

Inversion is another simple but constantly useful operation. The most common form in Unity game development is the inversion of a normalized value: if you have a number t that goes from 0 to 1, the inverted value 1 - t goes from 1 to 0. This sounds trivial, but it is used everywhere to create the "opposite" effect of a normalized parameter.

The classic use case is fading. Suppose you have a t value that drives a fade-in: when t = 0 the object is invisible, and when t = 1 it is fully visible. To create a fade-out with the same underlying timer, simply invert it: alpha = 1 - t.

using UnityEngine;

public class FadeEffect : MonoBehaviour
{
    [SerializeField] private float fadeDuration = 2f;
    private float timer = 0f;
    private SpriteRenderer spriteRenderer;

    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    void Update()
    {
        timer += Time.deltaTime;
        float t = Mathf.Clamp01(timer / fadeDuration);

        // Fade-in: alpha goes from 0 to 1 as t goes from 0 to 1
        float fadeInAlpha = t;

        // Fade-out: alpha goes from 1 to 0 as t goes from 0 to 1
        float fadeOutAlpha = 1f - t;

        // Apply fade-out
        Color color = spriteRenderer.color;
        color.a = fadeOutAlpha;
        spriteRenderer.color = color;
    }
}

Inversion also appears in inverted controls, difficulty scaling (harder difficulty = lower assist amount), and any situation where you want the complement of a percentage.

Absolute Value

A closely related utility is the absolute value — stripping the sign from a number so that negative values become positive. In C#, Unity provides this as Mathf.Abs():

float speed = -3.5f; // Moving in a negative direction
float speedMagnitude = Mathf.Abs(speed);
Debug.Log($"Speed magnitude: {speedMagnitude}"); // 3.5

// Useful for checking if a value is close to zero, regardless of sign:
float velocity = -0.001f;
if (Mathf.Abs(velocity) < 0.01f)
{
    Debug.Log("Object is nearly stationary.");
}

// Distance is always positive — Mathf.Abs guarantees this
// (though Vector3.Distance already handles this internally)
float heightDifference = Mathf.Abs(transform.position.y - targetPosition.y);
Debug.Log($"Height difference: {heightDifference}m");

With these foundational tools — coordinate spaces, distances, clamping, percentages, and inversion — you have a solid mathematical toolkit for the most common calculations in any Unity project. In the next chapter, we will build on this foundation by exploring vectors in depth, which are the primary data structure through which all of these concepts interact with movement, physics, and 3D geometry.

Unity3DTransformVector3DistanceClampMathematicsC#

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