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

Game Development Math for Unity3D June 2018 20 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.

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.

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);
    }
}

3. Calculating the Distance Between Two Points

Knowing how far apart two objects are is one of the most common needs in game programming. 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)² )

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;

Performance: sqrMagnitude vs magnitude

When you only need to compare distances, you can avoid the square root entirely by comparing squared distances instead:

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();
}

4. Value Clamping with Mathf.Clamp

Clamping means constraining a value so that it cannot exceed a specified range. Unity provides this as Mathf.Clamp():

// Clamp a player's health between 0 and 100
float health = 150f;
health = Mathf.Clamp(health, 0f, 100f);
Debug.Log(health); // 100

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!");
    }
}

Mathf.Clamp01

The range 0 to 1 is so common in game development normalized fractions, interpolation parameters, alpha values that Unity provides a dedicated shorthand: Mathf.Clamp01(t).

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

5. Calculating a Percentage

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:

// 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

Always make sure the denominator (max) is never zero before performing the division:

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

6. Calculating a Value from Its Percentage

The reverse operation given a percentage, find the corresponding value uses the formula: value = t × max

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

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

Connection to Lerp

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))

7. Inverting a Value

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. The classic use case is fading same timer, opposite effect:

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;
    }
}

Absolute Value

float speed = -3.5f;
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.");
}

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.

Unity3D Transform Vector3 Distance Clamp Mathematics C#

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