As cleverly explained in the book Flatland by Edwin Abbott, reality is made by many dimensions, and depending on your perception skills, you can see and act only on some of them. This is true also in video games: video games can be set in 3D or 2D worlds, and this distinction determines the way in which agents can perceive the surrounding world and so their ability to move and act.
An N-dimensional space is a geometrical setting in which a point in space is identified by N values or parameters, commonly named after the last letters of the alphabet.
So, as we said, depending on the number of dimensions in a space, you need an adequate number of values to identify a point in that space. Those values are represented by vectors.
2.1 Vectors
A straightforward example of a vector is acceleration. Let’s say you’re driving a car at 50 km/h. If you keep going at 50 km/h, the acceleration is 0 km/h2; if you press the accelerator a bit more, the velocity will grow at a pace of – let’s say – 5 km/h2. This acceleration value is a vector with direction equal to the orientation of the car and magnitude 5 km/h2.
In Unity, vectors are represented using specific data types. You can define a 2D vector by using Vector2 and a 3D vector by using Vector3.
Vectors are at the core of every operation in an N-space, both in linear algebra and in Unity. For example, the position of an object is represented by a 3D vector as well as its scale value. Those two values can be modified via vector operations. Let’s take a quick look at the most important operations with vectors and their meaning in the video game context.
2.1.1 Addition
So if, for example, you want to sum the two vectors [1, 2, 3] and [4, 5, 6], you just calculate the resulting vector [1+4, 2+5, 3+6] which is [5, 7, 9].
Since a vector can represent a point in space, the sum between two vectors is used to represent a movement from that point to a new point in the space. So, basically, when you sum a vector A to a vector B, the vector A is going to be the starting point, and the vector B is the offset that leads you to the new point C = A+B.
2.1.2 Subtraction
Subtraction between two vectors is pretty similar to addition. The only different thing is that the direction of the second element is reversed.
For example, if you want to calculate the difference between a vector A = [4,5,6] and a vector B = [1,2,3], you have to calculate the resulting vector C = A - B = [4,5,6] - [1,2,3] = [4,5,6] + [-1,-2,-3] = [4-1, 5-2, 6-3] = [3, 3, 3].
The subtraction between two vectors is used to find out the difference between them, which, in a spatial context, represents the distance between the two points represented by the two vectors.
2.1.3 Scalar Multiplication
As we said, a vector has a magnitude and a direction. The magnitude is the length of the vector.
To calculate the magnitude |V| of a vector V = [ a, b, c ], we apply the following formula:
|V| =
For example, if you want to double the magnitude of the vector V = [1, 2, 3] by a scalar value x = 2, you can just multiply each of V’s elements by x, like this: V*x = [1*2, 2*2, 3*2] = [2, 4, 6].
Similarly, if you want to reduce the magnitude of a vector W = [2, 4, 6] by a scalar value x = 2, you can just divide each of W’s elements by x, like this: W/x = [2/2, 4/2, 6/2] = [1, 2, 3].
2.1.4 Dot Product
Another important operation on vectors is the dot product. This is an algebraic operation you can execute on two vectors to get a scalar value. The result of a dot product between two vectors is the difference between the directions they’re facing.
Generally, the dot product is applied to normalized vectors, which are vectors of length 1. This is because when we want to calculate the difference between the directions of two vectors, we don’t care much about their length, but only about their direction.
One of the practical applications the dot product can have is to calculate the brightness on a surface based on the position of the light source. Let L be the light vector, which represents the position and direction of the light source, and N the vector representing the normal vector to a surface (meaning the vector perpendicular to a surface). Calculating B = L dot N will give us B as a float number representing the brightness of the surface of which N is the normal vector, where a value less or equal to 0 means darkness and a 1 means maximum brightness.
2.2 The First Project!
Previously, we said how vectors are used to represent positions and directions. Let’s put this in practice using Unity!
Unity will set up a project for you in seconds.
- 1.
Toolbar: It gives you access to some essential features like tools to manipulate the Scene View and the Game Objects in it, the buttons to run and stop the game as well as the step button to debug it, the buttons to access cloud services and versioning features.
- 2.
Hierarchy Window: It shows the list of all the objects in the current scene. From the hierarchy panel, you can access every single object and modify their properties via the Inspector.
- 3.
Inspector Window: The Inspector shows you all the details related to the asset currently selected. This window has not a standard view, as different kinds of assets have different kinds of properties.
- 4.
Project Window: It’s basically an assets explorer showing and listing all the assets related to your projects. As new assets will be created, they will be shown in the Project window.
- 5.
Scene View: It shows the selected scene and allows you to navigate it and edit the Game Objects within it. You can interact with the scene in the Scene View in 3D or 2D mode by selecting the corresponding button.
- 6.
Game View: It allows you to see what your final rendered game will look like. Pressing the Play button, you can start the game in this view.
- 7.
Console Window: It shows errors, warnings, and other messages generated by Unity or custom messages created by the programmer using the Debug.Log, Debug.LogWarning, and Debug.LogError functions.
2.2.1 The First Scene!
In our first project, we want to explore the basics of vectors by creating an application that allows us to move an object around by modifying its position vector. The application will consist of a plane with a simple cube on it. Using the mouse, we can click different parts of the plane and change the position of the cube modifying its position vector.
As you may imagine, 3D objects are defined in a 3D space by the 3D position vectors of their vertices. In Unity, though, for simplicity, we will only need to modify a single vector called the pivot point. The pivot point is a vector associated with an object representing its position in the 3D space.
So, let’s get started by adding a couple of 3D objects to our starting Scene.
Right-click the hierarchy or the assets panel and select 3D Object ➤ Plane from the contextual menu. This will create a plane in the Scene.
The origin is the point 0 of any N-space. In the case of a 2D space, like a Cartesian plane, the origin is at position x = 0, y = 0. In a 3D space, like our Scene in Unity, it’s at position x = 0, y = 0, z = 0.
Now that we have our 3D objects in place, the only thing we need is a good point of view! The visible scene, in a game, is defined by the Camera object, which is a GameObject that consists of many properties that allow you to show your game world from different points of view and with different graphic settings. Anyway, we won’t get too deep in this because it would take us too far from the scope of this book. The only thing we need to know is that a Camera allows us to define what we can see and how. Once again, the position of the camera is defined by a vector that we need to modify.
OK, now the scene is complete! We have the camera looking down at the plane with the cube in its center. We need now to add the functionality to make the cube move to the different positions at which we will point with the mouse. We can do this using C# scripting. Let’s see how!
2.2.2 The First Script!
As you know if you already worked with Unity, scripting is made by writing script files and assigning them to objects. Every script can implement standard functions that define the moment in which the actions are executed. What we want to do is to constantly check the position of the mouse, and if the user clicks, we want the cube to move to those coordinates.
Create the script by right-clicking the Project window and selecting Create ➤ C# Script . Rename the script to Move. Now double-click the script to open the text editor and start writing the code.
Lines 1–3 are just lines to include some libraries and modules. The interesting part starts just after: a new class with the name of the file is declared inheriting from the class MonoBehaviour (line 5); this allows us to override some useful methods like Start (line 8) that is called every time the game starts and Update (line 14) that is called every frame. We can get rid of the Start function, as we won’t use it.
The plan is to wait for the user to click a point in the space and then read the coordinate of the mouse cursor and move the cube to those coordinates. To do that, we will make use of the raycasting technique, which is widely used in Unity for so many purposes. Every frame, we will cast a ray from the camera to the mouse position, and when the user clicks, we will calculate the position at which the ray collides with the 3D plane and move the cube to that position.
This is the full code to add the functionality we talked about; let’s analyze it better!
We already saw the structure of the file, with the using statement and the class declaration. We won’t need the Start method, so we can just get rid of it. We will only need the Update method; let’s see how!
At line 7, we declare the hit variable of type RaycastHit. This is a structure used to get information from a ray collision against a Collider.
The idea is to cast a ray from the camera to the coordinates of the cursor in the moment the user clicks. The collision point between the ray and the first collider – in this case, the collider of the 3D plane – will be stored inside the hit variable. This will allow us to calculate the position we want the cube to move to.
At line 8, we create the ray variable of type Ray: this is the actual ray we are going to shoot from the camera to the mouse position via the function ScreenPointToRay passing as argument the position of the mouse contained in the vector Input.mousePosition.
At line 9, we call the Physics.Raycast(ray, out hit) function that casts the ray from the camera to the mouse position. That function returns true if the ray hit a collider, and the hit position is stored in the hit variable we passed to the function. In the same line, we also call the Input.GetMouseButtonDown(0) function that returns true if the mouse button indexed with 0 is pressed. As you can imagine, by default, the mouse indexed as 0 is the primary button: the left button; the right button is indexed as 1 and the middle button as 2. We put those two function calls in conjunction using the AND operator.; if both return true, we execute the instructions from lines 11 to 13.
At line 11, we create the new position vector using the x and z coordinates we found in the hit variable (the coordinates at which the ray hit the collider of the 3D plane) and for the y coordinate, we use the one of the cube object so we can keep it at the same height.
At line 12, we assign the position vector to the current object’s position, and at line 13 we print that information to the debug console using the Debug.Log function, just to see the position vector values changing.
Now that we explored the vectors a bit also in practice, let’s take a step further. It would be nice to make the cube move toward the point we click, instead of just teleporting it there. Let’s see how we can do this.
2.2.3 Moving Toward a Point
In the previous section, we managed to move the cube instantly to a certain position that we selected with the click of the mouse. What we want to achieve in this section is to move it gradually toward the point selected in a certain amount of time. The basic difference is that instead of moving to that point all in one movement, the object will make several small steps toward the goal position.
We want to recreate the idea of movement in space from a point A to a point B in a certain span of time; to do that, we can use the concept of geometric translation.
Geometric translation is a geometric transformation that moves all the points of a figure or a space in the same direction. The movement is achieved by adding a constant vector to every point of the figure; that constant vector is the movement vector, which defines the point in space we want to reach.
In Unity, we have the Translate method of the Transform class that implements exactly the concept of geometrical translation. Every object in a scene in Unity has a Transform, which allows for storing and manipulating the position, rotation, and scale of the object.
The movement vector is the amount of space in a certain direction that we want the object to traverse. We want the object to traverse a fraction of the space that divides it from the goal every time interval, but how can we calculate how much space should the object traverse for, let’s say, every second?
In Physics, the average space traversed by an object in a time interval moving at a certain speed is called Δs (delta space), and it’s described by the following formula:
Δs = vm * Δt
where vm is the average speed at which the object is moving and Δt is the amount of time in which the object moved of a quantity Δs (that we want to calculate).
So if our movement vector is Δs, to calculate it, we just need to multiply the average speed to the time interval.
The speed at which we want to move our object is an arbitrary quantity that we can make up. I will choose a value of 1, meaning that the object will move at 1 unit per second.
For the time interval Δt, we have a value provided to us by Unity that already has this information ready. This is Time.deltaTime, which is the difference between the last frame’s frametime and the current frame’s frametime.
Frametime is the time it takes for a frame to be rendered. It’s a floating value and can be different frame by frame, depending on the complexity of the scene to be rendered. Of course, an inconstant average frametime value (meaning the average between all the frametimes for every frame) is a symptom of bad performances, since it may cause stuttering, ruining the experience for the player.
Time.deltaTime is expressed in seconds; this means that it also helps us represent the movement as the amount of space traversed per second at speed vm.
We applied to transform.Translate a vector [0, 0, speed * Time.deltaTime] because we want the object to be moved forward.
where positionToLookAt is a Vector3 representing the point in space we want the object to turn toward.
At lines 6–8, we define the variables we will use later in the code to define the goal position, the movement speed, and the accuracy. The accuracy is an offset value that we need to avoid the object to constantly do infinitesimal movement and instead use a more approximation-based movement. This means that if the object is close enough to that point, it will stop and the accuracy of this approximation is represented by the accuracy variable.
We restored the Start method, and we are using it to initialize the goal vector, which at the beginning stores the current position of the object (lines 10–13).
We already saw that we need to create the hit and ray variables (lines 17–18) to be used in the Physics.Raycast method (line 20). When the left mouse button is clicked, we are going to set the goal 3D vector with the coordinates taken from the hit variable, but keeping the current y coordinate (lines 20–23).
At line 25, we turn the object to face the new position we want to reach, and if the distance between the object and the goal is greater than the accuracy value we set (line 26), we use the Translate method to move the object toward the goal at the speed we defined (line 28).
Save the code and press the play button to test it! Now, when you click a point on the 3D plane, the cube will move little by little (depending on the speed you set) toward that point you clicked.
Here you are, you just created your first NPC walking toward a point!
We want to go one step further by making our cube rotate toward the goal gradually, just as we did with the actual movement. This concept is called steering, and it’s very much used in games to simulate the natural rotation of an object. Let’s see how it works!
2.2.4 Steering Behaviors
Steering behaviors are very important for nearly every kind of game and especially for simulation games like car games, and they are commonly implemented using the concept of linear interpolation. Without going too deep in the maths, linear interpolation between two points A and B calculates the points that take us from point A to point B. This concept can be applied to traverse the space from point A to point B as well as to rotate an object from an angle ɑ to an angle β.
Lerp (Linear intERPolation)
Slerp (Spherical Linear intERPolation)
The main difference between the two is that Lerp moves the object using a constant speed, while Slerp does it by using a variable speed. This variable speed is basically the effect of the object gradually accelerating after starting moving and then gradually slowing down when approaching the goal.
This all looks pretty easy, does it? Let’s use it in our script!
Here, we are declaring a direction vector that tells us the distance and direction of the goal compared to the cube’s current position; then we use this information to calculate the rotation angle using the LookRotation method, and we pass this information to the Slerp method along with the current cube’s rotation value and the rotation speed times delta time.
Once again, let’s save the script and press Play! You will see that now the cube will gradually rotate toward the goal point while moving forward.
Good job! You just created your very first and basic algorithm to move an object from a point A to a point B! That’s an important foundation for what’s coming next: pathfinding!
In the next chapter, we will learn the foundations of pathfinding to allow our little cube to find its way toward the goal even with obstacles and no obvious path.
2.3 Test Your Knowledge!
- 1.
What is a 2D space?
- 2.
How are points defined in a 2D space?
- 3.
What is a 3D space?
- 4.
How are points defined in a 3D space?
- 5.
What is a vector?
- 6.
What is the difference between a 2D and a 3D vector?
- 7.
What are the possible applications of vectors in video games?
- 8.
How does vector sum work?
- 9.
How does vector subtraction work?
- 10.
What is scalar multiplication? How does it work?
- 11.
What is dot product? How does it work? How can you use it in video games?
- 12.
What is a geometric translation? How can you use it in a video game?
- 13.
Explain the concept of steering behavior. Why is it important?
- 14.
How can you implement a steering behavior in Unity?
- 15.
Analyze the code we just wrote for this chapter’s project and locate all the places where we used (or Unity probably used under the hood) the vector operations we just learned.