D I G T E K

Loading...

Nullam dignissim, ante scelerisque the is euismod fermentum odio sem semper the is erat, a feugiat leo urna eget eros. Duis Aenean a imperdiet risus.

The Unity 3D developers provided us with MonoBehaviour which is a class that grants our C# classes to have direct access to the inner workings of the Unity 3D game scene. The MonoBehaviour class is a collection of every function and data type available to Unity 3D. We’ll learn how to use these parts of Unity 3D in the following tutorials.

NOTE: It’s also important to know that when learning the specifics of Unity 3D, you should be aware that these Unity 3D-specific lessons can be applied to other development environments. When you use C# with Windows or OSX, you’ll be able to write applications for other operating systems for software other than Unity 3D.

Think of MonoBehaviour as a set of tools for talking to Unity 3D. Other systems have different sets of tools for making apps for other purposes. Learning how to use the other systems should be much easier after reading this book and learning how to talk to Unity 3D.

When dealing with most other C# applications, you’ll need to use Main() as the starting point for your C# application. This is like many other development environments using C++ and C.

When Unity 3D creates a class it automatically added in void Start() and void Update() to the body of the class. These two functions are called entry points. Basically, the base MonoBehaviour class has several functions, that include Start() and Update(), which are called based on events that happen when your game is running.

There are other functions that are also automatically called when MonoBehaviour is used: Awake(), OnEnable(), Start(), OnApplicationPause(), Update(), FixedUpdate(), and LateUpdate(); these functions are commonly called when the object is created and your game is running. When the object is destroyed, OnDestroy() is called. There are also various rendering functions that can be called, but we don’t need to go over them here.

To better understand what happens during each frame of the game it’s best to imagine the game operating a bit like a movie. At the very beginning of the game, when the player presses Start, every actor in the scene with a script based on MonoBehaviour has its Start() function called.

Calling a function basically executes the lines of code that live inside of the curly braces that follow the function’s declaration. Before the end of the first frame, the Update() functions of each script in the scene are called in no particular order.

At the beginning of the next frame, if there are no new scripts in the scene, the Start() functions are not called. Only Update() functions are called from that point on. Only if new scripts are introduced to the scene are Start() functions called on scripts new to the scene. If there is no code in the Start() function or if there is no Start() function in the script, then nothing happens.

To benefit from anything happening on each frame you’ll need to put code into the Update()  function. When a class is first introduced into the game world you need to add code into the Start() function. There are several other entry points that we’ll be working with. And you’ll be able to make use of them all as you see fit. It’s important to know that without these entry points your class will be functioning on its own.

Functions all have specific names or identifiers. So far we’ve seen Start() and Update(), but there are many more functions which we’ll make use of throughout this book. The first function we’ll make use of the Log();, which is a function found inside of the Debug class. We’ll return in a moment to how this function is used, but first we’ll demonstrate how it’s used.

If Debug is a class, this means that Log() is a member of Debug. The dot operator allows us to access the Log() function found in the Debug class.

Inside of the Start() function add in Debug.Log(“Start”);, as seen in the example above. When the game is started, the class will execute the code found inside of the Start() function. This means that the Debug.Log(“Start”); statement is executed.

Continuing in the Functions_Scene, click on the Play button and then click on the Console panel to bring it to the top. You should see the following lines of text in the Console panel.

The first line says Start, followed by UnityEngine.Debug:Log(Object), which is the expected result. If not, then double check a few different things. First check that the line of code you wrote ends with a semicolon (;) and is not left empty. Next, make sure that you’re using quotes around the word: “Start.”

Then, check that your spelling and case are correct: Debug and Log. Also make sure you didn’t forget the dot operator (.) between Debug and Log. The punctuation matters a great deal. Missing any one of these changes the outcome of the code. Syntax is important for programming. Missing one detail breaks everything.

There are a few important things happening here, though many details left out for now. By the end of this chapter everything should be clear.

4.6.3 Writing a Function

A function consists of a declaration and a body. Some programmers like to call these methods, but semantics aside, a function is basically a container for a collection of statements. Let’s continue with the Functions.cs script in the Chapter 4 Assets directory.

Here is a basic function called MyFunction. We can add in additional keywords to modify the function’s visibility. One common modifier we’ll be seeing soon is public. We’ll get further into the  public keyword in Section 4.13 on accessibility.

The public keyword needs to appear before the return type of the function. In this case, it’s void, which means that the function doesn’t return anything. Return types are something else that we’ll get into in Section 6.3.3, but functions can act as a value in a few different ways. For reference, a function that returns an int would look like this. A return statement of some kind must always be present in a function that has a return type.

The public modifier isn’t always necessary, unless you need to make this function available to other classes. If this point doesn’t make sense, it will soon. The last part of the function that is always required is the parameter list. It’s valid to leave it empty, but to get a feeling for what an arg, or argument in the parameter list, looks like, move on to the next example.

For the moment, we’ll hold off on using the parameter list, but it’s important to know what you’re looking at later so it doesn’t come as a surprise. Parameter lists are used to pass information from outside of the function to the inside of the function. This is how classes can pass information between one another.

We’ve been passing an argument for a while now using the Debug class. The Log member function of the Debug class takes a single argument. In Debug.Log(“start”); the “start” is an argument we’ve been passing to the Log function. The Debug class is in the UnityEngine library and made available because of the using UnityEngine; statement.

We’ll see how all these different parts of a function work as we get to them. Functions are versatile but require to be set up. Luckily, code is easy to build up one line at a time. It’s good to know that very few people are capable of writing more than a few lines of code at a time before testing them.

To use a function, you simply enter the identifier followed by its argument list.

The placement of SimpleFunction() in the class has no effect on how it’s called. For the sake of argument we can use SimpleFunction() anywhere in the class as long as it’s been declared at the class scope.

For a function to work, we need to give it some instructions. If we declare an int   a and set it to 0, we’ll have some data to start with. In a function called SetAtoThree(), we set a to 3. If we call CheckOnA() a in the Start() function we’ll get 0, the value which a was set to in the beginning. Then the function SetAtoThree() is called, which sets a to 3, so when a is logged again its new output is 3.

4.6.4 More on White Space and Tabs

Tabs are used to delineate the contents of a function. So far, we’ve been seeing each function written with its contents tabbed to the right with a single tab.

The contents of the function are tabbed over once. The contents of the while statement inside of the function are tabbed over twice, as indicated by more spaces. This presentation helps to clarify that the contents of the while statement are executed differently from the rest of the function.

To help understand how the previous code fragment is read by the computer, we will step through the code one line at a time. This emulates how the function is run when it’s called upon. Of course, a computer does this very quickly, but it’s necessary for us to understand what the computer is doing many millions, if not billions, of times faster than we can comprehend.

NOTE: For a moment imagine your central processing unit (CPU) was made of gears spinning more than 2 billion times per second. The 2.4 GHz means 2,400,000,000 cps. A cycle is an update of every transistor in the silicon on the chip: over 2 billion pulses of electrons running through the silicon, every second turning on and off various transistors based on your instructions. Of course, not all the updates are dedicated to your game; many of the cycles are taken up by the operating system running Unity 3D, so performance isn’t always what it should be.

The first line void MoreTabs() is identified by the computer and is used to locate the code to execute. Once the function is started the contents of the code begin at the first statement. In this case we reach // tabbed over once!, which is a comment and ignored.

Next we reach int i = 0;, which is a declaration and assignment of a variable that is identified as i, an identifier commonly used for integers. This declaration tells the computer to create a small place in memory for an integer value and it’s told that we’re going to use i to locate that data.

Following the variable declaration, we get to another comment that is also ignored by the computer. The while (i < 10) line of code follows the comment and this opens another space in memory for some operations. This also creates a connection to the i variable to check if the while statement will execute. If i is a value less than 10 then the statements in the following {curly braces} will be executed; otherwise, the contents of the while statement are ignored.

Another important character the computer finds is the opening curly brace ({) that tells it to recognize a new context for execution. The computer will read and execute each line in the while statement till it finds a closing curly brace (}). In this new context we’ll return to the first curly brace until the while statement’s condition is false.

Because i is less than 10, the while statement’s condition is true, so we will proceed to the first line of the while statement. In this case, it’s a comment that is ignored. This is followed by a Debug.Log() function, which also has a connection to the i variable. Therefore, this line prints to the console the value which is being stored at i. Once the value has been printed to the console the computer moves to the next line down.

The i++; statement tells the computer to look at the contents of i, add 1 to that value, and assign that new value back to i. This means that the new value for i is 1 since we started at 0. Once the computer reaches the closing curly brace, we jump back to the top of the while statement.

The contents of the curly braces are repeated until the value for i reaches 10; once the iteration returns 11, the condition of the while statement changes from true to false. Because 11 is not less than 10, the statements found between the curly braces will not be read. Therefore, the code in the while statement will be skipped. Once the computer reaches the closing curly brace of MoreTabs(), the computer stops running the function.

Computers do exactly what they’re told and nothing more. The compiler runs using very explicit rules which it adheres to adamantly. Because of this, any strange behavior, which you might attribute to a bug, should point to how you’ve written your code. In many cases, small syntactical errors lead to errors which the computer will not be able to interpret.

It’s greatly up to you to ensure that all of your code adheres to a format the computer can understand. When you write in English, a great deal of the sentiment and thought you put into words can be interpreted by each reader differently. For a computer there’s only one way to interpret your code, so either it works or it doesn’t.

4.6.5 What We’ve Learned

Function declaration is a bit like class declaration. Functions are the meat of where logic is done to handle variables. How variables make their way into and out of a function is covered in Section 6.18.

There are many different ways to handle this, but we’ll have to handle them one at a time. On your own experiment with functions like the following:

You might want to set int a to a value other than 0 or 1 to get anything interesting out of this. Later, we’ll be able to see how logic and loops can be used to make functions more powerful.

4.7 Order of Operation: What Is Calculated and When

Variables and functions work together to make your class operate as a part of your game. As an example, we’re going to move an object through space using some simple math. To use numbers correctly, we need to be very specific. Remember, computers do exactly what they’re told and nothing more.

Code is executed only when the function the code lives in is called. If the function is never called then nothing in the function will run. Once a function is called, the operation starts with evaluating the code at the top and works its way down to the end of the function one line at a time.

The first order which we’ve been working with so far is by line number. When code is evaluated each line is processed starting at the top; then it works its way down. In the following example, we start off with an integer variable and perform simple math operations and print the result.

As you might expect the first line prints 1, followed by 4, and then by 28. This is a rather long-handed method to get to a final result. However, things get a bit strange if we try to shorten this to a single line, as math operators work in a different order than we just used them.

After shortening the code to a single line like in this example, we get a different result, 22, which means 3 and 7 were multiplied before the 1 was added. Math operators have a priority when being evaluated, which follows the order of precedence. The multiply and divide operators have a higher precedence than add and subtract.

When evaluating int a, C# looked at the math operators first, and then did the math in the following order. Because the * is more important than the +; 3 * 7 was computed, turning the line into 1 + 21; then, the remaining numbers around + were evaluated, resulting a being assigned to 22, and then ultimately printing that to the Console output.

We’ll observe the other behaviors and learn to control how math is evaluated. This section might not be so exciting, but it’s worth the while to have this section as a reference. When your calculations begin to have unexpected results, it’s usually because of order of operation coming into play and evaluating numbers differently from what you expected.

4.7.1 Evaluating Numbers

Calculation of variables is called evaluation. As we had mentioned before, each object added to the scene in Unity 3D’s Editor has several components automatically added when it’s first created. To make use of these components, you simply use their name in your code. The first component we’ll be using is transform attribute. Transform contains the position rotation and scale of the object. You can see how these are changed when you select the object in the scene and move it around.

You can also enter numbers using the Inspector panel, or even click on the variable name and drag left and right as a slider to modify the data in the field. Moving objects around with code works in a similar manner as entering values in the Inspector panel. The position of the object is stored as three float values called a Vector3.

Operators in C# are the tools that do all of the work in your code. The first set of operators we’ll discuss is arithmetic operators.

4.7.1.1 Math

+ –/* and %

The + and do pretty much what you might expect them to: 1 + 3 = 4 and 1 – 3 = –2; this doesn’t come as a surprise. Divide uses the /, putting the first number over the second number. However, the number type is important to sort out before we get too much further.

The first line above is a 10/3, which might not return what you would expect. Writing a number without any decimal tells the compiler you’re using integers, which will give you an integer result. Therefore, the result is simply 3, with the remaining one-third cut off since it’s rounded down. The second line has an f after the number, indicating we want a float value. The third line without the f indicates a double value. This has twice the number of digits as a float.

The result here shows the different number of trailing 3s after the decimal. We mentioned in Section 4.4.1.2 that there are different types of numbers. Here is an example of what that really means. An integer is a whole number without any decimal values. A float is a decimal type with only 7 digits in the number; a double is quite a lot bigger, having a total of 15 digits in the number.

Doing this experiment again with larger numbers exposes how our numbers have some limitations.

Like the integer, the large doubles and floats only have so many places where the decimal can appear. The number for the double is huge to begin with, but we should have more 3s after the number than are shown with a decimal. Where did they go? Keep in mind there should be an indefinite number of trailing 3s, but computers aren’t good with indefinite numbers.

NOTE: Computers without special software can’t deal with numbers with an unlimited number of digits. To show bigger numbers, computers use scientific notation like E+15 to indicate where significant numbers begin from the decimal point. Games, in particular, rarely ever need to deal with such large numbers. And to keep processes running fast, numbers are limited.

Software applications designed to deal with particularly huge numbers tend to work more slowly as they need to run with the same limitations, but instead perform operations on the first part of a number and then another operation on the second half of a number. This process can be repeated as many times as necessary if the number is even bigger. Scientific computing often does this and they have special number types to compensate. There are some additional number types, as well as other data types that aren’t numbers. We’ll leave the other data types for later (Section 5.3.4). The operators we’re dealing with here are mainly for numbers.

Next up is the multiply operator (*). All the limitations that apply to the divide operator apply to the multiply operator as well. One important thing to consider here is that multiplication is technically faster than division. Therefore, in cases where you want to do 1000/4, the computer is going to be faster if you use 1000 * 0.25 instead.

This difference is a result of how the computer’s hardware was designed. To be honest, modern CPUs are so much faster than they used to be, so using a / versus a * will usually make no noticeable difference. Old school programmers will beg to differ, but today we’re not going to need to worry about this technicality, though I feel every classically trained programmer wants to scratch out my eyes right now.

Last up is the %, which in programming is called modulo, not percent as you might have guessed. This operator is used in what is sometimes called clock math. An analog clock has 12 hours around it. Therefore, a clock’s hours can be considered %12, or modulo twelve, as a programmer might say.

To use this operator, consider what happens when you count 13 hours on a 12-hour clock and want to know where the hour hand will be on the 13th hour. This can be calculated by using the following term: 13%12; this operation produces the value 1 when calculated. Modulo is far easier to observe in operation than it is to describe.

The output from the code below here repeats from 0 to 11 repeatedly. Clocks don’t start at 0, but computers do. Therefore, even though i is not being reset and continues to increase in value. On each update i is not being reset to 0, instead it continues to increment until you stop the game. A programmer would say “int i mod twelve” to describe i%12 shown here.

Programmers are particular about their vocabulary and syntax, so it’s best to understand how to think and talk like one. Programmers will usually assume you already know something like this, but it is worthwhile elaborating on the topic. Compilers, however, don’t all think in the same way. Therefore, if you’re curious and feel like learning other programming languages in the future, you might find that other languages will behave differently.

4.7.1.2 Operator Evaluation

As we saw before with 1 + 3 * 7 you may end up with different results, depending on the order in which you evaluated your numbers. To prevent any human misinterpretation of a calculation you can reduce the preceding numbers to 4 * 7 or 1 + 21. To make our intent clear we can use parentheses.

To gain control, we use the open and close parentheses to change the order of evaluation. For example, we can do this: 1 – 2 – (3 + 4), which turns into 1 – 2 – 7; a whole different result: –1 – 7 yields –8. A simple change in the order of operation can result in a completely different number when evaluated. This sort of thing becomes more important once we start shooting at zombies and dealing out damage. Some monsters might have thicker skin, and you’ll want to reduce the damage done by a specific amount based on where he was shot.

4.7.1.2.1 A Basic Example

As we saw before, 1 + 3 * 7 will result in 22. We could use the long-winded version and start with a, then add 1 to it, then add 3 to that, and then multiply that by 7 to get 24. There’s an easier way.

By surrounding a pair of numbers with parentheses, we can tell the compiler to evaluate the pair within the parentheses before the multiplication. This results in 28, like the long-handed version of the same math. To elaborate we can add in another step to our math to see more interesting results.

The code above results in 31 printed to the Console panel. We can change the result to 49 by surrounding the 7 + 9 with parentheses, like in the following.

The complier computes the 7 + 9 before the rest of the math is computed. Parentheses take precedence over multiplication, regardless of where they appear. It doesn’t matter where the parentheses appear, either at the beginning or at the end of the numbers: The parentheses are computed first.

Adding another set of parentheses can change the order of computation as well. Parentheses within parentheses tell the computer to start with the innermost pair before moving on. For instance (11 * ((9 * 3) * 2)) starts with 9 * 3; this turns into (11 * ((27) * 2), which then yields 27 * 2, which turns into (11 * (54)); 11 * 54 turns into 594. This can be switched round by shifting the placements of the parentheses to a different number. Try it out!

At this point, you will need to be very careful about the placement of the parentheses. For each opening parenthesis, there needs to be a corresponding closing parenthesis. If you’re missing one, then Unity 3D will let you know. It’s important to have a sense of what sort of math to use and when to use it.

In this case, where the parentheses encapsulate the entire expression their presence is redundant. Taking out the first and last parentheses will have no effect on the final result. This is true for most operations, but for clarity it’s useful to know that they are indeed optional. To be clear about this, we can add any number of extra parentheses to the operation and there will be no other effect to the result, though it may look a bit strange.

To make things clearer, we can rearrange this expression to be a lot more readable by setting up different variables. For each set of operations, we can create a new variable. The operations begin with the first variable; then, after the value is stored, the value is carried to the next time it’s used. Just remember, a variable cannot be used until it’s been created.

In this example, we’ve created a different variable for each part of the math operation. First, we created a 9 * 3 variable assigned to h. After that we multiplied h by 2 and assigned that to i. Finally, we multiplied i by 11 and assigned that result to j. To view the result, we printed out j to get 594, the same result as the long, fairly obscure operation we started with. This example shows a simple use of variables to make your code more readable.

This seems like we’re back to where we started with, at the beginning of this section. To be honest, both methods are perfectly valid. The decision to choose one system over another is completely dependent on which you find easier to understand.

What we should take away from this is the order in which the lines are read. The operations start at the top and work their way line by line to the bottom of the function. As variables are assigned new values, they are kept until they are reassigned.

It’s important to note that by creating more than one variable, you’re asking the computer to use more space in your computer’s memory. At this point we’re talking about very small amounts of memory. A classically trained programmer might want to fix this code upon reading it, and then convert it to a single line. To be honest, it does take up more space and not only visually.

When starting a new set of operations, it’s sometimes helpful to use a more drawn-out set of instructions, using more variables. When you’re more comfortable that your code is doing what you expect it to, you can take the time to reduce the number of lines required and do a bit of optimization. There’s nothing wrong with making things work first and cleaning them up later.

4.7.2 What We’ve Learned

Therefore, you’ve been introduced to variables and you’ve learned about functions, two big components of programming needed to make a game. Directives will allow your code to talk with Unity 3D and put meaning behind your variables and functions.

We’re using C# and .NET, which is an object-oriented programming environment. We just learned objects are all the little bits of information that make up your different C# classes. When we’re done with writing some code, Unity 3D will automatically interpret your code for your game.

At this point, we’ve got a pretty strong grasp of the basics of what code looks like and the different parts of how code is written. We’ve yet to make use of any logic in our functions, but we should be able to read code and know what is and isn’t in a function.

It would be a good idea, by way of review, to make a few other classes and get used to the process of starting new projects, creating classes, and adding them to objects in a Unity 3D Scene. Once you’re comfortable with creating and using new classes in Unity 3D you’ll be ready to move on.

4.8 Scope: A First Look

Encapsulation is the term given to where data can be accessed from. The amount of accessibility is called scope. The visibility of a variable to your logic changes depending on where it appears.

Encapsulation can hide, or change the accessibility of data between statements, functions, and classes. Another way to think about it is keeping data on a need-to-know basis. This invisibility is intended for several reasons. One reason is to reduce the chances of reusing variable names.

4.8.1 Class Scope

To get a better look at how scope and encapsulation work together, Open the Scope_Scene in the Chapter 4 Unity Project. Attached to the Scope GameObject is the Scope script. When the scene is run, the code attached to the Scope GameObject will run as normal.

The MyInt variable exists at the class scope level. MyInt can be used in any function that lives in the class scope. The example code you’re looking at will produce the following Console output:

Both Start() and Update() live in the class scope so both can see the MyInt variable. Therefore, the following code will send a 1 to the console. Both Start() and the Update() functions will keep sending 1s to the Console window.

Placement of a variable at the class scope has little effect on where it’s used. The code above can be rearranged easily. The code below will behave the same as the code above.

The functions and variables act in the same way, irrespective of where they appear in the class. However, class scope is easy to override with other variables of the same name. This is called variable collision, the effects of which you should be aware of, as discussed in Section 7.4.

If we declare a variable of the same name in more than one place then it will be overridden by the newest version of our variable. If you were to run this script in your scene, you’d have the Console printout 2 because of the line that preceded the print function call.

There are problems with how variables can collide with one another. And the best way to avoid this problem would be to come up with good names for your variables. What happens if we try to use both versions of myInt?

The scope in which the new myInt declaration appears wipes out the existence of myInt defined at the class scope. The result of such a declaration will be an error: “A local variable myInt cannot be used before it is declared.” You may have expected the first print function to print out the class scope version of myInt, but this isn’t the case.

Variables named at the class scope still exist even if you reuse the name with new variables named in a function. The word “this” is a keyword. If “this” preceeds a variable, C# understands to use the variable named in the class scope and not the variable named in the function scope.

By creating an int myInt inside of the Start() function in your class you have effectively hidden the class scoped myInt from the function. Although it can be accessed as this.myInt, the function scoped version of myInt in the function takes precedent.

This example should also highlight that a variable can be used only after it’s been initialized. This is a situation best avoided, so it’s good to remember what variable names you’ve used so you don’t use them again.

To elaborate, a function starts from the first curly brace ( { ) and computes one line at a time, working its way from the top, moving toward the bottom until it hits the closing curly brace ( } ). An error occurs if a variable is used in a statement before the variable is declared.

Something that might be obvious but should be mentioned is the fact that you can’t reuse a variable name. Even if the second variable is a different type, we’ll get the following error in MonoDevelop explaining our error.

Another effect of creating a variable inside of a function is the fact that another function can’t see what’s going on. In the following code we declare a StartInt in the Start() function. After that we try to use it again in the Update() function, which results in an error.

C# is telling us that we can’t use a variable before it’s declared. Of course, we did declare it once, but that was in a different function. The Update() function can’t see inside of the Start() function to find the declatedInStart variable declared in the Start() function.

You’ll have to keep this in mind once you start writing your own functions. There are two common ways to work with declaring variables. The first is to place all your variables at the top of the function.

This way you ensure that everything you may need to use is already declared before you need them. The second method is to declare the variable just before it’s needed.

In this case, we’re being very clear inside of this function about what variables we may use. For short functions this can sometimes be a lot easier to deal with. You can easily sort your variables and change their values in one place. It’s also easier to check if you’re accidentally reusing a variable’s name.

In this second case, we’re using the variable only after it’s created. When writing long functions you may find the second method a bit more clear as you can group your variables together near the logic and functions that will be using them.

This is a small example of code style. When reading someone else’s code, you’ll have to figure out how they think. In some cases, it’s best to try to match the style of existing code. However, when you’re writing everything on your own, it’s best to keep to your own style and decide how you want to arrange your code.

If we declare a variable at the class scope, all functions inside of the class can see, and access, that variable. Once inside of a function, any new variables that are declared are visible only to that function. Some statements can declare variables as well, and those variables are visible only inside of the statement they are declared. Once we get to know about more statements, we’ll see how they work, but we’ll need to keep scope in mind when we get there.

4.8.2 Function Scope

Variables often live an ephemeral life. Some variables exist only over a few lines of code. Variables may come into existence only for the moment a function starts and then disappear when the function is done. Variables in the class scope exist for as long as the class exists. The life of the variable depends on where it’s created.

In a previous exercise, we focused on declaring variables and showing them in the Unity 3D’s Inspector panel. Then we observed when variables need to be declared to be used without any errors. The placement and public keywords were necessary to expose the variables to the Unity 3D editor.

The public keyword can be used only at the class scope. This is called class scope as the variable is visible to all of the functions found within the class. Making the variable public means that any other class that can see this class can then also have access to the variable as well. We’ll go into more detail on what this means and how it’s used in a bit.

Adding the public int within the a function will produce an error. You’re not allowed to elevate a variable to the class scope from inside of a function. Visual Studio will not give you the most informative error message, but Unity will have a different message for you. Unexpected symbol “public”; the reason is that variables within a function cannot be made accessible outside of the function.

There are systems in C# to make data within a function accessible, but using the keyword public is not it. We’ll look into how that’s done in Section 6.1. A variable declared inside of a function exists only as the function is used. The functions declared in the class are also a part of the class scope, and at the same level as the variables declared at the class scope level. If a function in a class is preceded by the public keyword, it’s accessible by name, just like a variable. Without the public keyword the function will remain hidden. We’ll make use of public functions in Section 6.3.2, but it’s important to know that a function can be made publicly available, like variables.

Referring to the Scope script created for this chapter, we’re going to make use of the functions given to us by the programmers at Unity 3D. The location of a variable in code changes where it can be seen from and how it can be used.

As noted, limiting accessibility of any variable is called encapsulation. The curly braces, {}, and parentheses, (), have a specific context. This context can keep their contents hidden from other sections of code. This means that Start() can have one value for myInt, and if you a different myInt in Update(), it will not be affected by the myInt in Start().

If we look at the above figure we can visualize how scope is divided. The outer box represents who can see ClassInt. Within the Start() function we have a StartInt that only exists within the Start() function. The same is repeated for the UpdateInt, found only in the Update() function. This means that Start() can use both ClassInt and StartInt but not UpdateInt. Likewise, Update() can see ClassInt and UpdateInt but not StartInt.

In the diagram below the boxes represent different levels of scope.

Within the Start() function is a while loop. The while() loop can use both ClassInt and myInt. The following Update() function can see ClassInt but not myInt, which exists in the Start() function. The variable ClassInt was declared in the class scope, whereas the myInt variable was only declared inside of the Start() function’s scope and isn’t accessible outside of the Start() function.

4.8.3 Blank Scope

Blank Scope is obscure and not often used, but worth mentioning. Curly braces are used to encapsulate code, we’ve observed plenty of how that works with classes and functions. However, a pair of curly braces can encapsulate code without a specific reason.

In the code above, we have a function named BlankScope but there’s also a pair of curly braces where int  b; has been declared. This is called a blank scope. The Debug.Log(b); cannot see int  b outside of the blank scope. This does help in cases where working on a particularly tricky code might require some isolation.

With blank scope you can declare variables with identifiers that were already used elsewhere in the code.

If you were testing out different algorithms you might want to use blank scope to isolate different ideas.

If you’re using blank scope you’re also able to fold code bracketed by the curly braces.

One clever trick is to put a comment at the bottom of the curly braces.

With the comment at the bottom, when your code is folded it will have a title so you can identify what the code does. Organizing your code is something that comes after quite a lot of practice. Eventually you’ll end up coming up with schemes of your own to help keep your code organized. No suggestions on how to organize code have been made. Tools and systems have been introduced, but no style has been recommended. I’ll leave the specific style up to you to make your own decisions on.

4.8.4 What We’ve Learned

Encapsulation describes a system by which a variable’s scope is defined. Depending on where a variable is declared, the scope of the variable changes. Encapsulation prevents variables from overlapping when they’re used. Encapsulation, therefore, allows one function to operate independently from another function without our needing to worry about a variable’s name being reused.

We’ll cover more situations where scope becomes a useful tool to help classes message one another. Encapsulation and scope define a major principle of object oriented programming, and understanding this concept is important to learning how to write C#.

4.9 This

What happens when a class needs to refer to objects in itself? In most cases, a class can use its own properties and fields and not get confused what variable is being referred to. For instance, we can take a look at a basic class that has the following.

Here, we have someInt defined at the class scope. We’ve have a function that assigns the someInt to i. There are no problems with this setup. On the other hand, should the AssignInt be modified to look like the following:

4.9.1 A Basic Example

In the KeywordThis_Scene open the KeywordThis script found on the KeywordThis object. Looking at the AssignInt function, there’s no confusion with the assignment of someInt. The situation changes when the parameter name in AssignInt changes to someInt.

The AssignSomeInt(int someInt) function has no errors, and the project builds. The expectation is that someInt will be assigned the value that was coming into the function through the parameter. However, we get a different result.

If we use this to assign someInt in the Start() function we get the Log output of 0 both before and after using the function. Hover over the assignment in the AssignSomeInt function and you’ll observe the message “Assignment made to same variable; did you mean to assign something else?” To solve this problem, we’ll look at the simple solution.

This assigns the class scoped someInt the value of the incoming parameter someInt. This is because in the function AssignThisSomeInt, where we assign the parameter someInt variable this. someInt. Without the keyword this, the local scope of the function overrides the class scope use of someInt.

4.9.2 When This Is Necessary

With the addition of the keyword this to the function above, we can specify which version of the someInt variable we’re talking about. It’s easy to avoid the requirement of the this keyword. Simply use unique parameter names from the class’s variables and you won’t need to use the this keyword.

There is no unexpected behavior if we superfluously use the this keyword anytime we want to.

The use of this can help make the code more readable. In a complex class where we have many different variables and functions, it sometimes helps to clarify if the variable being assigned is something local to the function we’re reading or if the variable is a class scoped variable. Using this we can be sure that the variable is assigned to a class variable, and not something only in the function.

Getting into something somewhat obscure, but worth mentioning is self-referencing. In some cases, an object may need to look to itself as a variable. To do this you’d have an object store itself as a variable.

The example above shows a class called ThisThing. Inside of it there’s a variable declaration called myThing that’s the same type as itself. To assign the class itself to the myThing variable there’s a function called AssignThing() where the contents of that function assigns this to myThing.

The function AssignAThingToItself() executes the code and creates a thing and has it assign itself to the myThing variable. With some data types where objects need to reference to both themselves and similar components you’ll create what’s called a linked list. One of the components of a linked list is being able to reference itself among other objects it might be linked to.

We may be jumping ahead into some complex data structures, so we’ll hold off more complex subjects for now. It’s not too common for an object needing to reference itself in this way, but C# lets you do it.

4.9.3 What We’ve Learned

In general, this is a somewhat awkward keyword. The necessity of using the keyword this arises in very few use cases.

When naming variables and functions, it’s best to keep to a consistent pattern. In general, many people use longer names at the class scope. For instance, the following code uses a pattern of shorter and shorter variable names depending on the variable’s scope:

At the scope of the entire class the int is MyInt. Within the function MyFunction in MyClass a local variable visible to MyFunction in the argument list is called mInt. The variable defined inside of the function is simply m.

There are obvious limitations to using a convention like shortening variable names. It’s very easy to run out of letters and end up with unclear variable names, but it’s a simple place to get started.

This isn’t a common practice convention by any means; it’s one that I’ve personally become used to using. These sorts of personal conventions are something that everyone needs to invent for his or her individual use. I’d expect you to find your own convention for naming variables.

The flexibility of C# allows everyone to have a personal style of writing code. Identifiers aren’t the only place where one can find self-expression through code. Another way to express one’s self through code is deciding to use a for-while or do-while loop. Both can be used for the very similar tasks, but the decision is ultimately left to the programmer writing the code.

For any reason should you feel like you prefer one method over another you should follow your own intuition and write how you like to. Programming is a creative process and writing expressive code is a process just like any other form of art.

Leave a Reply