View on GitHub

Welcome to Humair's Dev Blog!

This page will serve as a catalog for miscellaneous tasks/procedures as I venture in to the world of game design.

Vector3 Basics (Position, Distance, Direction)

This document describes vectors from the perspective of unity. We will cover vectors in 3D cartesian space. For unity this means Vector3.

Much of this is probably applicable to Vector2 as well.

What is a Vector3?

A Vector3 can be thought of as a point in 3d space that comprises of both a direction and a magnitude.

Direction

Try not to confuse direction with rotation, as these are different things. A vector has a direction, an object has a rotation. A direction for a vector can only be understood relative to another point in space. Let’s take an example:

Vector3 playerOrigin = transform.position;
Vector3 direction = playerOrigin.normalized;

In this example the normalized call gives the direction of the player from the world origin. Why world origin? Because remember that we are looking at the vector that is transform.position, the position of an object in this case is in world space, meaning everything is relative to the origin.

normalized gives you a vector with a magnitude of 1. This basically means that the “distance” portion of the vector is stripped away.

image

In the illustration above, the red is the origin, and the green cube is the player. Then direction from the code snippet above represents the arrow.

Magnitude

The magnitude of a vector can either be thought of as the distance or force. Let’s think of them as distances for now. Like direction, distances are only meaningful relative to another point.

Vector3 playerOrigin = transform.position;
float playerDistance = playerOrigin.magnitude;

Similar to distance, in this example the magnitude can be thought of as the distance of the player from the origin. This is illustrated below:

image

Let’s prove that we’re right in this case by drawing a line from the origin (red sphere) to the player (green cube) by adding the following script to one of the objects:

    public GameObject origin;
    public GameObject player;
    void Update()
    {
        Vector3 playerPos = player.transform.position;
        Vector3 originPos = origin.transform.position;

        Vector3 directionFromOriginToPlayer = playerPos.normalized;
        
        Debug.DrawRay(
            originPos,
            directionFromOriginToPlayer,
            Color.blue
        );

In this script we draw a line from the origin to the direction of the player, provided that normalized gives us this direction. Running the script results in:

image

Indeed we get the results we expect. The blue ray is going from the origin to the direction of the player. But why is the line not connecting with the player? Remember that the normalized call simply returns another vector with a magnitude of 1, in this case magnitude represents distance, so the blue line only has a distance of 1. To fully connect this to the player, we need to incorporate the vector distance (magnitude) as well!

        ... // same as before
        Vector3 directionFromOriginToPlayer = playerPos.normalized;
        float distanceFromOriginToPlayer = playerPos.magnitude;
        
        Debug.DrawRay(
            originPos,
            directionFromOriginToPlayer * distanceFromOriginToPlayer,
            Color.blue
        );

By multiplying the direction by distance, we are “scaling” the direction vector by that distance value, this can be conceptually thought of as moving in that direction. The amended code gives the following result:

image

Success! Note that we will see the exact same results if we replace playerPos with directionFromOriginToPlayer * distanceFromOriginToPlayer. This is expected, since the whole point of the exercise is to show that the position vector3 is, in fact, comprised of these two properties: direction & distance.

Note that both the Magnitude and Distance were extracted from the vector3 found from the transform.position . This means that the transform.position which is just a vector3 of the form (x,y,z) is more than just some point in space, but retains both these properties. But it’s always important to remember that these properties only have meaning when considering another point in space (in this case the origin).

Distance & Magnitude relative to another Object

So far we have been considering only position relative to the origin for the player. But what if instead of the origin, we have a different object. In other words, what about another object relative to our player?

image

In this new example we have another object, the blue cube. Let’s call this blue cube the “enemy”. Looking at the illustration suggests, what if we want to calculate the distance from the player and the enemy? same with direction. Before, the distance/direction from origin to player was implicit by the call to player.transform.position. We can get a similar result for the enemy player by doing something like enemy.transform.position like so:

    public GameObject enemy;
    public GameObject origin;
    public GameObject player;
    void Update()
    {
        Vector3 playerPos = player.transform.position;
        Vector3 enemyPos = enemy.transform.position;
        Vector3 originPos = origin.transform.position;
        
        Debug.DrawRay(
            originPos,
            playerPos, // now that we know playerPos is distance*direction (from origin) we can directly use playerPos
            Color.blue
        );
        
        Debug.DrawRay(
            originPos,
            enemyPos,
            Color.green
        );

This results in:

image

Great, but we want to draw a line from the player to the enemy (blue cube). What happens if we simply add:

        Debug.DrawRay(
            playerPos,
            enemyPos,
            Color.red
        );

Take a minute to think about why this won’t work.

Okay so let’s see what happens if we run this:

image

What is going on here? Remember that the second argument to DrawRay takes a direction, but a direction is always relative to another point in space right? So we know from earlier enemyPos comes from enemy.transform.position and position of a transform contains the direction/distance relative to… the origin! So all we’ve done here is draw a line from the player in the direction of origin->enemy, which is why the red line is essentially parallel to the green.

What we need is to find the direction of the player to the enemy. This requires us to perform some Vector arithmetic. To get a direction of a vector A to vector B we need to subtract vector B from vector A i.e. AB-> = B - A . It’ easier to explain why this works if we consider 2D vectors and the following diagram of a 2d cartesian space:

image

I’ve kept the color scheme of the lines analogous to our example. The red vector, which is (3, -2) gives the direction from the point (2,4) to (5,2). Think of it it as the “steps” one needs to take to get to point (2,4) to (5,2). In this case we need to go 3 steps right, and 2 steps down from 2,4 to get to 5,2. If we instead subtract (2,4) - (5,2) then we get the reverse i.e. (-3, 2), 3 steps left, 2 steps up. Which is the vector moving from (5,2) in the direction towards (2,4).

Let’s use this new found knowledge in our example:

        Vector3 playerToEnemy = enemyPos - playerPos;  

		// playerToEnemy is made up a direction and distance relative to the enemy
        Vector3 directionFromPlayerToEnemy = playerToEnemy.normalized;
        float distanceFromPlayerToEnemy = playerToEnemy.magnitude; 
        
        Debug.DrawRay(
            playerPos,
            directionFromPlayerToEnemy * distanceFromPlayerToEnemy,  // same as if we just put playerToEnemy
            Color.red
        );

This gives us the following result:

image

And we get the intended result!

How to move an object in a given direction

Earlier we encountered a scenario where we had to scale our direction by the magnitude (in this case also distance) to connect our lines between objects. Scaling the direction in this case allowed us to move the position along the direction we intended. Scaling is achieved by multiplying a vector by some number, which we call a scalar.

When we were drawing our line from player to enemy , we needed three attributes: playerPos, directionFromPlayerToEnemy, distanceFromPlayerToEnemy. Then it was simply a matter of drawing a line from playerPos in the direction of directionFromPlayerToEnemy scaled by the distance of distanceFromPlayerToEnemy. The value distanceFromPlayerToEnemy in this case was our scalar.

The same principle applies when we want to move an object in another direction. Consider the following scenario:

image

Where red is our origin point in our world, green is our player, and blue is our enemy once again. I’ve kept the DrawRays to serve as visual aids (they are not directly relevant to this section however). Code is very similar to what we have been doing in the previous sections:

Vector3 playerPos = player.transform.position;
Vector3 enemyPos = enemy.transform.position;
Vector3 originPos = origin.transform.position;

Debug.DrawRay(
    originPos,
    playerPos,
    Color.red
);

Debug.DrawRay(
    originPos,
    enemyPos,
    Color.blue
);

Suppose I wanted to move the player to where the enemy is? In order to do this, we need to move the player in the direction of the enemy by the distance between the two:

// We first get out direction as before 
Vector3 directionFromPlayerToEnemy = (enemyPos - playerPos).normalized;

// This allows us to calculate the distance between player and enemy
float distanceBetweenEnemyAndPlayer = Vector3.Distance(playerPos, enemyPos);

// We now scale our direction by the distance, this vector now comprises of a direction + distance
Vector3 scaledDirectionByDistance = directionFromPlayerToEnemy * distanceBetweenEnemyAndPlayer;

// By adding this vector to player position, we move the player in the given direction by the distance
player.transform.position = playerPos + scaledDirectionByDistance;   

For the sake of this tutorial, we’ll use a 2nd object to represent the player after it has moved, so that we can see the old player position. Let’s call this futurePlayer, and add it to our world like so:

image

The yellow cube can be thought of as a “future” green cube.

We change our code to the following:

    public GameObject enemy;
    public GameObject origin;
    public GameObject player;
    public GameObject futurePlayer;
	void Update()
    {
        Vector3 playerPos = player.transform.position;
        Vector3 enemyPos = enemy.transform.position;
        Vector3 originPos = origin.transform.position;
        
        Debug.DrawRay(
            originPos,
            playerPos,
            Color.red
        );
        
        Debug.DrawRay(
            originPos,
            enemyPos,
            Color.blue
        );
        
        Vector3 directionFromPlayerToEnemy = (enemyPos - playerPos).normalized;
        float distanceBetweenEnemyAndPlayer = Vector3.Distance(playerPos, enemyPos);
        Vector3 scaledDirectionByDistance = directionFromPlayerToEnemy * distanceBetweenEnemyAndPlayer;
        futurePlayer.transform.position = playerPos + scaledDirectionByDistance;        
    }

Running our code yields the following result:

image

As you can see our futurePlayer has moved over to where the enemy is.

Now suppose we don’t want to move all the way to where the enemy is, we want to move only half way. How can we do this? We can reduce our scalar value by 50%:

//...
        Vector3 directionFromPlayerToEnemy = (enemyPos - playerPos).normalized;
        float distanceBetweenEnemyAndPlayer = Vector3.Distance(playerPos, enemyPos);
        Vector3 scaledDirectionByDistance = directionFromPlayerToEnemy * distanceBetweenEnemyAndPlayer;
		// Reduce the scalar value by 50%, this will half the distance we travel in that particular direction
		scaledDirectionByDistance = scaledDirectionByDistance * 0.5f;
        futurePlayer.transform.position = playerPos + scaledDirectionByDistance;    

This gets us the following result:

image

Note that the yellow cube representing our futurePlayer is now permanently situated between our old player and the enemy.

Also note that:

Vector3 directionFromPlayerToEnemy = (enemyPos - playerPos).normalized;
float distanceBetweenEnemyAndPlayer = Vector3.Distance(playerPos, enemyPos);
Vector3 scaledDirectionByDistance = directionFromPlayerToEnemy * distanceBetweenEnemyAndPlayer;
scaledDirectionByDistance = scaledDirectionByDistance * 0.5f;
futurePlayer.transform.position = playerPos + scaledDirectionByDistance;  

// is equivalent to running: 
futurePlayer.transform.position = playerPos + (enemyPos - playerPos) * 0.5f;    

The reason is very similar to the previous section. Recall that a position is comprised of a direction and magnitude (in this case distance) relative to another position (in this case the enemy). By subtracting enemyPos from playerPos we are not only getting the direction from player to enemy, but we’re also getting the distance. The way we initially did things is actually counter intuitive in practice, as we’re stripping the same information away from the positions that we are then adding back in later by recalculating the distance.