Vectors

Vector math is not part of C# itself, but is extremely common in Unity and game development in general. Vectors are simply groups of numbers and the most commonly used one in Unity is a Vector3:

public struct Vector3
{
    public float x;
    public float y;
    public float z;
}

There are also Vector2 without the z field and Vector4 with a w field.

Some places where vectors are used throughout Unity include:

  • Every GameObject has a Transform Component which stores the position and scale of the object as Vector3s. Rotation is stored as a Quaternion, but can be converted to and from a Vector3 using the eulerAngles property.
  • A Rigidbody stores its velocity as a Vector3 which represents the direction and distance it will move per second.

Note that vectors store their values using floats (which can hold about 7 decimal digits of precision), but when converted to strings it rounds the values off to one decimal place to make them easier to read. So it is important to be aware that if for example you log the position of an object, it will only display a rounded value unless you specifically log ...position.x + ", " + position.y....

Arithmetic

Simple arithmetic operations work similarly with vectors (Vector2, Vector3, and Vector4) as with individual numbers:

Op Parameters Returns
+ vector, vector A vector with the values of the corresponding fields in each parameter added together.
- vector, vector A vector with the values of the corresponding fields in each parameter subtracted from each other.
* vector, float A vector with the values of the input vector each multiplied by the input float. This esentially multiplies its Magnitude by that value.
/ vector, float A vector with the values of the input vector each divided by the input float. This esentially divides its Magnitude by that value.
== vector, vector true if each of the values of the input vectors are approximately equal, otherwise false. This is actually very unhelpful because it uses an entirely arbitrary threshold to define how close "approximately equal" is. The equality operator should be checking for equality, not approximate equality.

Difference

One particularly useful property of vector subtraction is that it calculates the difference between two values, such as the offset from one position to another or the difference between the velocity of two objects. A vector going from a to b is calculated using b - a. For example:

Single Number Vector
float a = 2;
float b = 7;

float difference = b - a;
// difference = 5.
// So 5 is the "offset" between them.
// a + 5 will give b.
Vector2 a = new Vector2(2, 6);
Vector2 b = new Vector2(7, 13);

Vector2 difference = b - a;
// difference = (5, 7).
// So (5, 7) is the "offset" between them.
// a + (5, 7) will give b.

Magnitude

If you think of a Vector2 as a right-angled triangle where its x value is the length of one side and its y value is the length of another side, then you can calculate the length of the hypotenuse using the Pythagorean Theorem:

// 2D:
length = square root of (x * x + y * y)

// 3D:
length = square root of (x * x + y * y + z * z)

This is the calculation performed by Vector3.magnitude and Vector3.Distance which are very useful for things like checking how far away something is from something else.

Unfortunately, the square root operation required to calculate the magnitude of a vector is quite complex and takes much longer to execute than simple arithmetic. In some situations, this can be avoided by using the Squared Magnitude instead. For example:

  • If you want to display the distance to a destination as text, then you obviously need to calculate the proper distance.
  • But if you just want to know whether that destination is within a certain range, you can square the range and compare it to the squared distance like so: if ((target - start).sqrMagnitude < range * range)

Normalization

A Unit Vector is a vector with a length of 1 unit. Setting the length of a vector to 1 without changing its direction is called "normalization" and can be done using Vector3.Normalize to modify a vector or Vector3.normalized to get a normalized copy of it. Normalization simply divides a vector by its Magnitude, so if you have already calculated the magnitude you can do the division yourself to avoid the performance cost of recalculating it.

Dot Product

The Vector2.Dot method performs the following calculation:

public static float Dot(Vector2 lhs, Vector2 rhs)
{
    return lhs.x * rhs.x + lhs.y * rhs.y;
    // Vector3 has the same thing with the z values as well.
}

// "lhs" is short for "left hand side".
// For example, in an operation like "x + y", the "x" is the "left hand side" of the operation.
// This is common mathematical terminology, but is not particularly helpful as a parameter name.
// The parameters might as well just be called "a" and "b".

Calculating the dot product of two vectors tells you how similar their directions are:

  • If both input vectors are Normalized, the result will be between 1 and -1:
    • 1 means both vectors have the same direction.
    • 0 means they are perpendicular (90 degrees apart).
    • -1 means they are pointing in opposite directions.
    • This is part of the calculation used in Vector2.Angle to calculate the actual angle between two vectors.
  • If one of the input vectors is Normalized, the result will tell you how much of the other vector lies in that direction. The Directional Blending example uses this to find out how much of a movement direction vector lies in the direction of the object's right and forward axes individually to control its animation blending along each axis.
  • If they are not Normalized, the result will range from their magnitudes multiplied together to that same value as a negative. This raw value is less useful for anything other than checking whether they are more or less than 90 degrees apart (based on whether the result is positive or negative).

Raycasting

The Physics.Raycast method allows you to check if a particular line through 3D space (a ray) intersects with any physics objects (objects with Collider components).

You can cast a ray from any origin point in any direction you want, for example:

  • Cast from the head of an enemy towards the player to determine if that enemy can see the player.
  • Cast downwards from a character's feet with a very short maxDistance to determine if they are on the ground.
  • Cast from the camera in the direction of the mouse cursor to find out what the player clicked on. The Camera.ScreenPointToRay method makes it very easy to convert a screen position into a Ray containing an origin and direction to use in a raycast.

Debug Lines

Raycasts have no visual appearance by default, so it can be useful to draw lines in the Scene view to help with debugging.This can easily be done using Debug.DrawLine and Debug.DrawRay which are practically the same method except that DrawLine takes an end parameter to specify the exact end point while DrawRay takes a direction parameter and calculates the end point as start + direction.

Lines will only appear for one frame by default so you can use the duration parameter to specify how long you want it to remain for. They are not visible in the Game window or in a runtime build, only in the Scene window in the Unity Editor.

The following example performs a raycast every frame and uses some lines to visualise the result in the scene view:

using UnityEngine;

public sealed class RaycastVisualisationExample : MonoBehaviour
{
    private void Update()
    {
        const float MaxDistance = 100;

        // Get a ray where the mouse cursor is pointing.
        var ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        RaycastHit raycastHit;

        // Raycast from there.
        // If it hits something, the Raycast method will return true.
        // And the "out raycastHit" will output the results of what it hit.
        if (Physics.Raycast(ray, out raycastHit, MaxDistance))
        {
            // Draw a green line from the start of the ray to the point it hit.
            // Note: this is DrawLine so it takes a start and end point.
            Debug.DrawLine(ray.origin, raycastHit.point, Color.green);

            // The "normal" is a unit vector that tells us which way the surface is facing.
            // Draw a blue line from the hit point in the direction of the normal.
            // Note: this is DrawRay so it takes a start point and direction.
            Debug.DrawRay(raycastHit.point, raycastHit.normal, Color.blue);
        }
        else
        {
            // If the ray did not hit anything, draw a red line out to its max range.
            Debug.DrawRay(ray.origin, ray.direction * MaxDistance, Color.red);
        }
    }
}