In this tutorial we will use game objects to build a graph, so we can show mathematical formulas. We'll also make the function time-dependent, resulting in an animating graph.

This tutorial assumes you've done the Game Objects and Scripts tutorial and that you're using at least Unity 2017.1.0.

Creating a Line of Cubes

A good understanding of mathematics is essential when programming. At its most fundamental level math is the manipulation of symbols that represent numbers. Solving an equation boils down to rewriting one set of symbols so it becomes another—usually shorter—set of symbols. The rules of mathematics dictate how this rewriting can be done.

For example, we have the function `f(x)=x+1`. We can substitute a number for `x`, say 3. That leads to `f(3)=3+1=4`. We provided 3 as input and ended up with 4 as output. We can say that the function maps 3 to 4. A shorter way to write this would be as an input-output pair, like (3,4). We can create many pairs of the form `(x,f(x))`. For example (5,6) and (8,9) and (1,2) and (6,7). But it is easier to understand the function when we order the pairs by the input number. (1,2) and (2,3) and (3,4) and so on.

The function `f(x)=x+1` is easy to understand. `f(x)=(x-1)^4+5x^3-8x^2+3x` is harder. We could write down a few input-output pairs, but that likely won't give us a good grasp of the mapping it represents. We're going to need many points, close together. That will end up as a sea of numbers, which are hard to parse. Instead, we could interpret the pairs as two-dimensional coordinates of the form `[[x],[f(x)]]`. This is a 2D vector where the top number represents the horizontal coordinate, on the X axis, and the bottom number represents the vertical coordinate, on the Y axis. In other words, `y = f(x)`. We can plot these points on a surface. If we use enough points, we end up with a line. The result is a graph.

Graph with `x` between −2 and 2.

Looking at a graph can quickly give us an idea of how a function behaves. It's a handy tool, so let's create one in Unity. Start with a new scene via File / New Scene for this purpose, or use the default scene of a new project.

Prefabs Graphs are created by placing points at the appropriate coordinates. To do this, we need a 3D visualization of a point. We'll simply use Unity's default cube game object for this. Add one to the scene and remove its collider component, as we won't use physics. Are cubes the best way to visualize graphs? You could also use a particle system or line segments, but individual cubes are the simplest to use. We will be using a script to create many instances of this cube and position them correctly. In order to do this, we'll use the cube as a template. Drag the cube from the hierarchy window into the project window. This will create a new asset, with a blue cube icon, known as a prefab. It is a pre-fabricated game object that exists in the project, but not in a scene. A cube prefab. Prefabs are a handy way to configure game objects. If you change the prefab asset, all instances of it in any scene are changed in the same way. For example, changing the prefab's scale will also change the scale of the cube that's still in the scene. However, each instance uses its own position and rotation. Also, game objects can have their properties modified, which overrides the prefab's values. If large changes are made, like adding or removing a component, the relationship between prefab and instance will be broken. We're going to use a script to create instances of the prefab, so we no longer need the cube instance that is currently in the scene. So delete it.

Graph Component We need a C# script to generate our graph. Create one and name it Graph. We begin with a simple class that extends MonoBehaviour so it can be used as a component for game objects. Give it a public field to hold a reference to a prefab for creating points, named pointPrefab . As we'll need access to the Transform component to position the points, make that the field's type. using UnityEngine; public class Graph : MonoBehaviour { public Transform pointPrefab; } Add an empty game object to the scene, via GameObject / Create Empty, place it at the origin, and name it Graph. Add our Graph component to this object, via dragging or via its Add Component button. Then drag our prefab asset onto the Point Prefab field of the graph. It now holds a reference to the prefab's Transform component. Graph object with reference to prefab.

Instantiating Prefabs Instantiating a game object is done via the Instantiate method. This is a publicly available method of Unity's Object type, which Graph indirectly inherited by extending MonoBehaviour . The Instantiate method clones whatever Unity object is provided as an argument. In the case of a prefab, it will result in an instance being added to the current scene. Let's do this when our Graph component awakens. public class Graph : MonoBehaviour { public Transform pointPrefab; void Awake () { Instantiate(pointPrefab); } } Instantiated prefab. At this point, entering play mode will produce a single cube at the origin, provided that the prefab asset's position is set to zero. To place the point somewhere else, we need to adjust the position of the instance. The Instantiate method gives us a reference to whatever it created. Because we gave it a reference to a Transform component, that's what we get in return. Let's keep track of it with a variable. void Awake () { Transform point = Instantiate(pointPrefab); } Now we can adjust the point's position, by assigning a 3D vector to it. Like we adjusted the local rotation of the handles of the clock in Game Objects and Scripts, we'll adjust the local position of the point via its localPosition property, not position . 3D vectors are created with the Vector3 struct. As it's a struct, it acts like a value, similar to a number, not an object. For example, let's set the X coordinate of our point to 1, leaving its Y and Z coordinates at zero. Vector3 has a right property for this. Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right; Shouldn't properties be capitalized? The convention is to capitalize properties, yes, but Unity often doesn't do this. When entering play mode now, we still get one cube, just at a slightly different position. Let's instantiate a second one and place it an additional step to the right. This can be done by multiplying the right vector by 2. Repeat the instantiation and positioning code, then add the multiplication to the new code. void Awake () { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right; Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right * 2f; } Can you multiply structs and numbers? Normally you cannot, but it is possible to define such functionality. This is done by creating a method with a special syntax, so it can be invoked as if it were a multiplication. In this case, what appears to be a simple multiplication is actually a method invocation, something like Vector3.Multiply(Vector3.right, 2f) . Being able to use methods as if they were simple operations makes writing code faster and easier to read. It is not essential, but nice to have, just like being able to implicitly use namespaces. Such convenient syntax is known as syntactic sugar. Having said that, methods should only be used as operators if they strictly match the original meaning of that operator. In the case of vectors, some mathematical operators are well-defined, so it's fine for those. This code will lead to a compile error, because we attempt to define the point variable twice. If we want to use another variable, we have to give it a different name. Alternatively, we reuse the variable that we already have. We don't need to hold on to the reference to the first point anyway. Simply assign the new point to the same variable. Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right; // Transform point = Instantiate(pointPrefab); point = Instantiate(pointPrefab); point.localPosition = Vector3.right * 2f; Two instances, with X coordinates 1 and 2.

Code Loops Let's create more points, until we have ten. We could repeat the same code eight more times, but this is very inefficient programming. Ideally, we only write the code for one point and instruct the program to execute it multiple times, with slight variation. The while statement can be used to cause a block of code to repeat. Apply it to the first two lines of our method and remove the other lines. void Awake () { while { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right; } // point = Instantiate(pointPrefab); // point.localPosition = Vector3.right * 2f; } Like if statements, while must be followed by an expression within round brackets. Like with if , the code block following while will only be executed if the expression evaluates as true. Afterwards, the program will loop back to the while statement. If at this point the expression again evaluates as true, the code block will be executed again. This repeats until the expression evaluates as false. So we have to add an expression after while . We must be careful to make sure that the loop doesn't repeat forever. Infinite loops cause programs to get stuck, requiring manual termination by the user. The safest possible expression that compiles is simply false . while (false) { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right; } Can we define point inside the loop? Yes. Although the code gets repeated, we've defined the variable only once. It gets reused each iteration of the loop, like we manually did earlier. You could also define point before the loop. That allows you to use the variable outside the loop as well. Otherwise, its scope is limited to the block of the while loop. Limiting the loop can be done by keeping track of how many times we've repeated the code. We can use an integer variable to keep track of this. It will contain the iteration number of the loop, so let's name it i . To be able to use it in the while expression, it must be defined earlier. void Awake () { int i; while (false) { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right; } } Each iteration, increase the number by one. int i; while (false) { i = i + 1; Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right; } This produces a compile error, because we're trying to use i before we've assigned a value to it. We have to explicitly assign zero to i when we define it to make this work. int i = 0 ; Now i becomes 1 at the start of the first iteration, 2 at the start of the second iteration, and so on. But the while expression is evaluated before each iteration. So right before the first iteration i is zero, it's 1 before the second, and so on. So after the tenth iteration i is ten. At this point we want to stop the loop, so its expression should evaluate as false. In other words, we should continue as long as i is less than ten. Mathematically, that's expressed as `i < 10`. It is written the same in code, also with the less-than operator. int i = 0; while ( i < 10 ) { i = i + 1; Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right; } Now we'll get ten cubes after entering play mode. But they all end up at the same position. To put them in a row along the X axis, multiply the right vector by i . point.localPosition = Vector3.right * i ; Ten cubes in a row. Note that currently the first cube ends up with an X coordinate of 1 and the last cube ends up with 10. Ideally, we start at 0, positioning the first cube at the origin. We can shift all points one unit to the left by multiplying right by (i - 1) instead of i . However, we could skip that extra subtraction by increasing i at the end of the block, instead of at the beginning. while (i < 10) { // i = i + 1; Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right * i; i = i + 1; }

Concise Syntax Because looping a certain amount of times is so common, it is convenient to keep the code for a loop concise. Some syntactic sugar can help us with that. First, let's consider incrementing the iteration number. When an operation of the form x = x * y is performed, it can be shortened to x *= y . This works for all operators that act on two operands of the same type. // i = i + 1; i += 1; Going even further, when incrementing or decrementing a number by 1, this can be shortened to ++x or --x . // i += 1; ++i; One property of assignment statements is that they can also be used as expressions. This means that you could write something like y = (x += 3) . That would increase x by three and assign the result of that to y as well. This suggests that we could increment i inside the while expression, shortening the code block. while ( ++ i < 10) { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right * i; // ++i; } However, now we're incrementing i before the comparison, instead of afterwards, which would lead to one less iteration. Specifically for situations like this, the increment and decrement operators can also be placed after a variable, instead of before it. The result of that expression is the original value, before it was changed. // while (++i < 10) { while (i++ < 10) { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right * i; } Although the while statement works for all kinds of loops, there is an alternative syntax particularly suited for iterating over ranges. It is the for loop. It works like while , except that both the iterator variable declaration and its comparison are contained within round brackets, separated by a semicolon. // int i = 0; // while (i++ < 10) { for (int i = 0; i++ < 10) { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right * i; } That would produce a compile error, because there are actually three parts. The third is for incrementing the iterator, keeping it separate from the comparison. // for (int i = 0; i++ < 10) { for (int i = 0; i < 10; i++) { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right * i; } Why use i++ and not ++i in the for loop? As the increment expression is not used for anything else, it doesn't matter which version we use. We could've also used i += 1 or i = i + 1 . The classical for loop has the form for (int i = 0; i < someLimit; i++) . You will encounter that code fragment in a lot of programs and scripts.

Changing the Domain Currently, our cubes are given X coordinates 0 through 9. This isn't a convenient range when working with functions. Often, a range of 0–1 is used for X. Or when working with functions that are centered around zero, a range of −1–1. Let's reposition our cubes accordingly. Positioning our ten cubes along a line segment two units long will cause them to overlap. To prevent this, we're going to reduce their scale. Each cube has size 1 in each dimension by default, so to make them fit we have to reduce their scale to `2/10 = 1/5`. We can do this by setting each point's local scale to the Vector3.one property divided by five. for (int i = 0; i < 10; i++) { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right * i; point.localScale = Vector3.one / 5f; } Small cubes. To bring the cubes back together again, divide their positions by five as well. point.localPosition = Vector3.right * i / 5f ; This makes them cover the 0–2 range. To turn that into the −1–1 range, subtract 1 before scaling the vector. point.localPosition = Vector3.right * ( i / 5f - 1f) ; Now the first cube has X coordinate −1, while the last has coordinate 0.8. However, the cube size is 0.2. As the cube is centered on its position, the left side of the first cube is at −1.1, while the right side of the last cube is at 0.9. To neatly fill the −1–1 range with our cubes, we have to shift them half a cube to the right. This can be done by adding 0.5 to i before dividing it. point.localPosition = Vector3.right * ( ( i + 0.5f) / 5f - 1f);

Hoisting the Vectors out of the Loop Although all the cubes have the same scale, we calculate it in every iteration of the loop. We don't have to do this. Instead, we could calculate it once before the loop, store it in a Vector3 variable, and use that in the loop. void Awake () { Vector3 scale = Vector3.one / 5f; for (int i = 0; i < 10; i++) { Transform point = Instantiate(pointPrefab); point.localPosition = Vector3.right * ((i + 0.5f) / 5f - 1f); point.localScale = scale ; } } We could also define a variable for the position before the loop. As we're creating a line along the X axis, we only need to adjust the X coordinate of the position inside the loop. So we no longer have to multiply by Vector3.right . Vector3 scale = Vector3.one / 5f; Vector3 position; for (int i = 0; i < 10; i++) { Transform point = Instantiate(pointPrefab); // point.localPosition = Vector3.right * ((i + 0.5f) / 5f - 1f); position.x = (i + 0.5f) / 5f - 1f; point.localPosition = position ; point.localScale = scale; } Can we change a vector's components individually? The Vector3 struct has three floating-point fields, x , y , and z . These fields are public, so we can change them. Because structs behave like simple values, the idea is that they should be immutable. Once constructed, they should't change. If you want to use a different value, assign a new struct to the field or variable, like we do with numbers. If we say that `x = 3` and later that `x = 5`, we've assigned a different number to `x`. We didn't modify the number 3 itself to become a 5. However, the vector types of Unity are mutable. This is done both for convenience and performance, because individual vector components are often manipulated independently. To get an idea of how to work with mutable vectors, you can consider the use of Vector3 a convenient substitute for using three separate float values. You can access them independently, yet also copy and assign them as a group. This will result in a compile error, complaining about the use of an unassigned variable. This happens because we're assigning position to something while we haven't set its Y and Z coordinates yet. Explicitly set them to zero before the loop. Vector3 position; position.y = 0f; position.z = 0f; for (int i = 0; i < 10; i++) { … }