Introduction to C# programming
In this section, we will go through an introduction to C# programming and look at key aspects that you will need for your games, including:
- C# Syntax.
- Variable types and scope.
- Useful coding structures (e.g., loops or conditional statements).
- Object-Oriented Programing (OOP) principles.
So, after completing this chapter, you will be able to:
- Understand key concepts related to C# programming.
- Understand the concepts of variables and methods.
- Know how and why inheritance can be applied to your games.
- Statements
- Comments
- Variables
- Arrays
- Constants
- Operators
- Condotional Statements
- Switch Statements
- loops
- Defining A Class
- Accession Class Members
- constructors
- Destructors
- Dictionaries
- Events
- Coroutines
- Delegates
- Events and Delegates
- Static Members
- Inheritance
- Methods
- Accessing Methods
- Overloading
- Member and Local Variables
- Polymorphism
- Dynamic Polymorphism
- Name Spaces
- Lists
Introduction
When you are using scripting in Unity, you are communicating with the Game Engine and asking it to perform actions. To communicate with the system, you are using a language or a set of words bound by a syntax that the computer and you know. This language consists of keywords, key phrases, and a syntax that ensures that the instructions are written and (more importantly) understood properly.
In computer science, this language needs to be exact, precise, unambiguous, and with a correct syntax. In other words, it needs to be exact.
When writing C# code, you will be following a syntax; this syntax will consist in a set of rules that will make it possible for you to communicate with Unity clearly and unambiguously. In addition to its syntax, C# also uses classes, and your C# scripts will, by default, be saved as classes.
In the next section, we will learn how to use this syntax. If you have already coded in JavaScript, some of the information provided in the rest of this chapter may look familiar and this prior exposure to JavaScript will definitely help you. This being said, UnityScript and C#, despite some relative similarities, are quite different in many aspects; for example, in C#, variables and functions are declared differently.
When scripting in C#, you will be using a specific syntax to communicate with Unity; this syntax will be made of sentences that will be used to convey information on what you would like the computer to do; these sentences or statements will include a combination of keywords, variables, methods, or events; and the next section will explain how you can confidently build these sentences together and consequently program in C#.
Statements
When you code in C#, you need to tell the system to execute your instructions (e.g., print information) using statements. A statement is literally an order or something that you ask the system to do. For example, in the next line of code, the statement will tell Unity to print a message in the Console window:
print (“Hello Word”);
When writing statements, you will need to follow several rules, including the following:
- The order of statements: each statement is executed in the same order as it appears in the script. For example, in the next example, the code will print hello, then world; this is because the associated statements are in that particular sequence.
print (“hello”); print (“world”);
- Statements are separated by semi-colons (i.e., semi-colon at the end of each statement).
Note that several statements can be added on the same line, as long as they are separated by a semi-colon.
- For example, the next line of code has a correct syntax, as all of its statements are separated by a semi-colon.
print(“hello”);print (“world”);
- Multiple spaces are ignored for statements; however, it is good practice to add spaces around the operators +, -, /, or % for clarity. For example, in the next code snippet, we say that a is equal to b. You may notice that spaces have been included both before and after the operator =.
a = b;
- Statements to be executed together (e.g., based on the same condition) can be grouped using code blocks. In C#, code blocks are symbolized by curly brackets (e.g., { or }). So, in other words, if you needed to group several statements, you would include all of them within the same set of curly brackets, as follows:
{ print (“hello stranger!”); print (“today, we will learn about scripting”); }
As we have seen earlier, a statement usually employs or starts with a keyword (i.e., a word that the computer knows). Each of these keywords has a specific purpose, and common keywords, at this stage, could be used for the following actions:
- Printing a message in the Console window: the keyword is print.
- Declaring a variable: the keyword, in this case, depends on the type of the variable that is declared (e.g., int for integers, string for text, or bool for Boolean variables), and we will see more about these in the next sections.
- Declaring a method: the keyword to be used depends on the type of the data returned by the method. For example, in C#, the name of a method is preceded by the keyword int when the method returns an integer; it is preceded by the keyword string when the method returns a string, or by the keyword void when the method does not return any information.
What is called a method in C# is what used to be called a function in UnityScript; these terms (i.e., function and method) differ in at least two ways: in C# you need to specify the type of the data returned by this method, and the keyword function is not used anymore in C# for this purpose. We will see more about methods in the next sections.
- Marking a block of instructions to be executed based on a condition: the keywords are if and else.
- Exiting a function: the keyword is return.
Comments
In C# (similarly to JavaScript), you can use comments to explain your code and to make it more readable by others. This becomes important as the size of your code increases; and it is also important if you work in a team, so that other team members can understand your code and make amendments in the right places, if and when it is needed.
Code that is commented is usually not executed. There are two ways to comment your code in C# using either single- or multi-line comments.
In single-line comments, a double forward slash is added at the start of a line or after a statement, so that this line (or part thereof) is commented, as illustrated in the next code snippet.
//the next line prints Hello in the console window print (“Hello”); //the next line declares the variable name string name; name = “Hello”;//sets the value of the variable name
In multi-line comments, any code between the characters forward slash and star “ /*” and the characters star and forward slash “*/” will be commented, and this code will not be executed. This is also referred as comment blocks.
/* the next lines after the comments will print the message “hello” in the console window
we then declare the variable name and assign a value
*/
print(“Hello”);
string name;
name = “Hello”;//sets the value of the variable name
//print (“Hello World”)
/*
string name;
name = “My Name”;
*/
Variables
A variable can be compared to a container that includes a value that may change over time. When using variables, we usually need to: (1) declare the variable by specifying its type, (2) assign a value to this variable, and (3) possibly combine this variable with other variables using operators, as illustrated in the next code snippet.
int myAge;//we declare the variable myAge
myAge = 20;// we set the variable myAge to 20
myAge = myAge + 1; //we add 1 to the variable myAge
In the previous example, we have declared a variable called myAge and its type is int (as in integer). We save the value 20 in this variable, and we then add 1 to it.
Note that, contrary to UnityScript, where the keyword var is used to declare a variable, in C# the variable is declared using its type followed by its name. As we will see later, we will also need to use what is called an access modifier in order to specify how and from where this variable can be accessed.
Also note that in the previous code, we have assigned the value myAge + 1 to the variable myAge; the = operator is an assignment operator; in other words, it is there to assign a value to a variable and is not to be understood in a strict algebraic sense (i.e., that the values or variables on both sides of the = sign are equal).
To make C# coding easier and leaner, you can declare several variables of the same type in one statement. For example, in the next code snippet, we declare three variables v1, v2, and v3 in one statement. This is because they are of the same type (i.e., they are integers).
int v1,v2,v3;
int v4=4, v5=5, v6=6;
In the code above, the first line declares the variables v1, v2, and v3. All three variables are integers. In the second line of code, not only do we declare three variables simultaneously, but we also initialize them by setting a value for each of these variables.
When using variables, there are a few things that we need to determine including their name, their type and their scope:
- Name of a variable: a variable is usually given a unique name so that it can be identified easily and uniquely. The name of a variable is usually referred to as an identifier. When defining an identifier, it can contain letters, digits, a minus, an underscore or a dollar sign, and it usually begins with a letter. Identifiers cannot be keywords, such as the keyword if, for example.
- Type of variable: variables can hold several types of data, including numbers (e.g., integers, doublesor floats), text (e.g., strings or characters), Boolean values (e.g., true or false), arrays, objects (we will see the concept of arrays later in this chapter) or GameObjects (i.e., any object included in your scene), as illustrated in the next code snippet.
string myName = “Patrick”;//the text is declared using double quotes
int currentYear = 2017;//the year needs no decimals and is declared as an integer
float width = 100.45f;//the width is declared as a float (i.e., with decimals)
- Variable declaration: variables need to be declared so that the system knows what you are referring to if you use this variable in your code. The first step in using a variable is to declare or define this variable. At the declaration stage, the variable does not have to be assigned a value, as this can be done later. In the next example, we declare a variable called myName and then assign the value “My Name” to it.
string myName;
myName = “My Name”
- Scope of a variable: a variable can be accessed in specific contexts that depend on where in the script the variable was initially declared. We will look at this concept later.
- Accessibility level: as we will see later, a C# program consists of classes; for each of these classes, the methods and variables within can be accessed depending on their accessibility levels and we will look at this principle later.
Common variable types include:
- String: same as text.
- Int: integer (1, 2, 3, etc.).
- Boolean: true or false.
- Float: with a fractional value (e.g., 1.2f, 3.4f, etc.).
- Arrays: a group of variables of the same type. If this is unclear, not to worry, this concept will be explained further in this chapter.
- GameObject: a game object (any game object in your scene).
Arrays
You can optimize your code with arrays, as they make it easier to apply features and similar behaviors to a wide range of data. When you use arrays, you can manage to declare less variables (for variables storing the same type of information) and to also access them more easily. You can create either single-dimensional arrays or multi-dimensional arrays.
Let’s look at the simplest form of arrays: single-dimensional arrays. For this concept, we can take the analogy of a group of 10 people who all have a name. If we wanted to store this information using a string variable, we would need to declare (and to set) ten different variables, as illustrated in the next code snippet.
string name1;string name2; ……
While this code is perfectly fine, it would be great to store this information in only one variable instead. For this purpose, we could use an array. An array is comparable to a list of items that we can access using an index. This index usually starts at 0 for the first element in the array.
So let’s see how we store the names with an array.
- First we could declare the array as follows:
string [] names;
You will probably notice the syntax dataType [] nameOfTheArray. The opening and closing square brackets are used to specify that we declare an array that will include string values.
- Then we could initialize the array as follows:
names = new string [10];
In the previous code, we just specify that our new array, called names, will include 10 string variables.
- We can then store information in this array as described in the next code snippet.
names [0] = “Paul”;
names [1] = “Mary”;
…
names [9] = “Pat”;
In the previous code, we store the name Paul as the first element in the array (remember the index starts at 0); we store the second element (with the index 1) as Mary, as well as the last element (with the index 9), Pat.
Note that for an array of size n, the index of the first element is 0 and the index of the last element is n-1. So for an array of size 10, the index for the first element is 0, and the index of the last element is 9 (i.e., 10-1).
If you were to use arrays of integers or floats, or any other type of data, the process would be similar, as illustrated in the next code snippet.
int [] arrayOfInts; arrayOfInts [0] = 1;
float [] arrayOfFloats;arrayOfLoats[0]=2.4f;
Now, one of the cool things that you can do with arrays is that you can initialize your array in one line, saving you the headaches of writing 10 lines of code if you have 10 items in your array, as illustrated in the next example.
string [] names = new string [10] {“Paul”,”Mary”,”John”,“Mark”, “Eva”,”Pat”,”Sinead”,”Elma”,”Flaithri”, “Eleanor”};
This is very handy, as you will see in the next chapters, and this should definitely save you a lot of time coding.
Now that we have looked into single-dimensional arrays, let’s look at multidimensional arrays, which can also be very useful when storing information. This type of array (i.e., multidimensional arrays) can be compared to a building with several floors, each with several apartments. So let’s say that we would like to store the number of tenants for each apartment. We would, in this case, create variables that would store this number for each of these apartments.
The first solution would be to create variables that store the number of tenants for each of these apartments with a variable that makes a reference to the floor, and the number of the apartment. For example, the variable ap0_1 could be defined to store the number of tenants in the first apartment on the ground floor, ap0_2, could be defined to store the number of tenants in the second apartment on the ground floor, ap1_1 could be defined to store the number of tenants in the second apartment on the first floor, and ap1_2, could be defined to store the number of tenants in the third apartment on the first floor. So in term of coding, we could have the following:
int ap0_1 = 0;
int ap0_2 = 0;
…
However, we could also use arrays in this case, as illustrated in the next code snippet:
int [,] apArray = new int [10,10];
apArray [0,1] = 0;
apArray [0,2] = 0;
print (apArray[0]);
In the previous code:
- We declare our array. int [,] means a two-dimensional array with integers; in other words, we state that any element in this array will be defined and accessed based on two parameters: the floor level and the number of this apartment on that level.
- We also specify a size (or maximum) for each of these parameters. The maximum number of floors (or level) will be 10, and the maximum number of apartments per floor will be 10. So, for this example we can define levels, from level 0 to level 9 (i.e., 10 levels), and from apartment 0 to apartment 9 (i.e., 10 apartments).
- The last line of code prints the value of the first element of the array in the Console
One of the other interesting things with arrays is that, by using a loop, you can write a single line of code to access all the items in this array, and hence, write more efficient code.
Constants
So far we have looked at variables and how you can store and access them seamlessly in your code. The assumption then was that a value may change over time, and that this value would be stored in a variable accordingly. However, there may be times when you know that a value will remain constant throughout your game. For example, you may want to define labels that refer to values that should not change over time, and in this case, you could use constants.
Let’s see how this works: let’s say that the player has three choices in the first menu of the game, that we will call 0, 1, and 2. Let’s assume that you would like an easy way to remember these values so that you can process the corresponding choices. Let’s look at the following code that illustrates this idea:
int userChoice = 2;
if (userChoice == 0) print (“you have decided to restart”);
if (userChoice == 1) print (“you have decided to stop the game”);
if (userChoice == 2) print (“you have decided to pause the game”);
In the previous code:
- The variable userChoice is an integer and is set to 2.
- We then check the value of the variable userChoice and print a message accordingly in the console window.
Now, as you add more code to your game, you may or may not remember that the value 0 corresponds to restarting the game; the same applies to the other two values defined previously. So instead, we could use constants to make it easier to remember (and to use) these values. Let’s see how the previous example can be modified to employ constants instead.
const int CHOICE_RESTART = 0;
const int CHOICE_STOP = 1;
const int CHOICE_PAUSE = 2;
int userChoice = 2;
if (userChoice == CHOICE_RESTART) print (“you have decided to restart”);
if (userChoice == CHOICE_STOP) print (“you have decided to stop the game”);
if (userChoice == CHOICE_PAUSE) print (“you have decided to pause the game”);
In the previous code:
- We declare three constant
- These variables are then used to check the choice made by the user.
In the next example, we use a constant to calculate a tax rate; this is a good practice as the same value will be used across the program with no or little room for errors when it comes to using the exact same tax rate across your program.
const float VAT_RATE = 0.21f;
float priceBeforeVat = 23.0f
float priceAfterVat = pricebeforeVat * VAT_RATE;
In the previous code:
- We declare a constant float variable for the vat rate.
- We declare a float variable for the item’s price before tax.
- We calculate the item’s price after adding the tax.
It is a very good coding practice to use constants for values that don’t change across your program. Using constants makes your code more readable, it saves work when you need to change a value in your code, and it also decreases possible occurrences of errors (e.g., for calculations).
Operators
Once we have declared and assigned values to variables, we can then combine these variables using operators. There are different types of operators including: arithmetic operators, assignment operators, comparison operators and logical operators. So let’s look at each of these operators:
- Arithmetic operators are used to perform arithmetic operations including additions, subtractions, multiplications, or divisions. Common arithmetic operators include +, -, *, /, or % (modulo).
int number1 = 1;// the variable number1 is declared
int number2 = 1;// the variable number2 is declared
int sum = number1 + number2;// We add two numbers and store them in the variable sum
int sub = number1 – number2;// We subtract two numbers and store them in the variable sub
- Assignment operators can be used to assign a value to a variable and include =, +=, -=, *=, /= or %=.
int number1 = 1;
int number2 = 1;
number1+=1; //same as number1 = number1 + 1;
number1-=1; //same as number1 = number1 – 1;
number1*=1; //same as number1 = number1 * 1;
number1/=1; //same as number1 = number1 / 1;
number1%=1; //same as number1 = number1 % 1;
Note that the = operator, when used with strings, will concatenate these strings (i.e., add them one after the other to create a new string). When used with a number and a string, the same will apply; for example “Hello”+1 will result in “Hello1”.
- Comparison operators are often used in conditional statements to compare two values; comparison operators include ==,!=, >, <, <= and >=.
if (number1 == number2); //if number1 equals number2
if (number1 != number2); //if number1 and number2 have different values
if (number1 > number2); //if number1 is greater than number2
if (number1 >= number2); //if number1 is greater than or equal to number2
if (number1 < number2); //if number1 is less than number2
if (number1 <= number2); //if number1 is less than or equal to number2
Conditional statements
Statements can be performed based on a condition, and in this case, they are called conditional statements. The syntax is usually as follows:
if (condition) statement;
This means if the condition is verified (or true) then (and only then) the statement is executed. When we assess a condition, we test whether a declaration is true. For example, by typing if (a == b), we mean “if it is true that a is equal to b”. Similarly, if we type if (a>=b) we mean “if it is true that a is greater than or equal to b”
As we will see later, we can also combine conditions and decide to perform a statement if two (or more) conditions are true. For example, by typing if (a == b && c == 2) we mean “if a is equal to b and c is equal to 2”. In this case, using the operator && means AND, and that both conditions will need to be true. We could compare this to making a decision on whether we will go sailing tomorrow. For example, “if the weather is sunny and if the wind speed is less than 5km/h then I will go sailing”.
We could translate this statement as follows.
if (weatherIsSunny == true && windSpeed < 5) IGoSailing = true;
When creating conditions, as for most natural languages, we can use the operator OR noted ||. Taking the previous example, we could translate the following sentence “if the weather is too hot or if the wind is faster than 5km/h then I will not go sailing ”, as follows.
if (weatherIsTooHot == true || windSpeed >5) IGoSailing = false;
Another example could be as follows.
if (myName == “Patrick”) print(“Hello Patrick”);
else print (“Hello Stranger”);
In the previous code:
- We assess the value of the variable called myName.
- The statement print(“Hello Patrick”) will be printed if the value of the variable myName is “Patrick”.
- Otherwise, the message “Hello Stranger” will be displayed instead.
When we deal with combining true or false statements, we are effectively applying what is called Boolean logic. Boolean logic deals with Boolean variables that have two possible values 1 and 0 (or true and false). By evaluating conditions, we are effectively processing Boolean numbers and applying Boolean logic. While you don’t need to know about Boolean logic in depth, some operators for Boolean logic are important, including the ! operator. It means NOT (or “the opposite”). This means that if a variable is true, its opposite will be false, and vice versa. For example, if we consider the variable weatherIsGood = true, the value of !weatherIsGood will be false (its opposite). So the condition if (weatherIdGood == false) could be also written if (!weatherIsGood) which would literally translate as “if the weather is NOT good”.
Switch Statements
If you have understood the concept of conditional statements, then this section should be pretty much straight forward. Switch statements are a variation on the if/else statements that we have seen earlier. The idea behind the switch statements is that, depending on the value of a particular variable, we will switch to a particular portion of the code and perform one or several actions accordingly. The variable considered for the switch structure is usually of type integer. Let’s look at a simple example:
int choice = 1;
switch (choice)
{
case 1:
print (“you chose 1”);
break;
case 2:
print (“you chose 2”);
break;
case 3:
print (“you chose 3”);
break;
default:
print (“Default option”);
break;
}
print (“We have exited the switch structure”);
In the previous code:
- We declare the variable called choice, as an integer and initialize it to 1.
- We then create a switch structure whereby, depending on the value of the variable choice, the program will switch to the relevant section (i.e., the portion of code starting with case 1:, case 2:, etc.). Note that in our code, we look for the values 1, 2 or 3. However, if the variable choice is not equal to 1 or 2 or 3, the program will go to the section called default. This is because this section is executed if all of the other possible choices (i.e., 1, 2, or 3) have not been fulfilled (or selected).
Note that each choice or branch starts with the keyword case and ends with the keyword break. The break keyword is there to specify that after executing the commands included in the branch (or the current choice), the program should exit the switch structure. Without any break statement we will remain in the switch structure and the next line of code will be executed.
So let’s consider the previous example and see how this would work in practice. In our case, the variable choice is set to 1, so we will enter the switch structure, and then look for the section that deals with a value of 1 for the variable choice. This will be the section that starts with case 1:; then the command print (“you chose 1”); will be executed, followed by the command break, indicating that we should exit the switch structure; finally the command print (“We have exited the switch structure”) will be executed.
Switch structures are very useful to structure your code and when dealing with mutually exclusive choices (i.e., only one of the choices can be processed) based on an integer value, especially in the case of menus. In addition, switch structures make for cleaner and easily understandable code.
Loops
There are times when you have to perform repetitive tasks as a programmer; many times, these can be fast-forwarded using loops which are structures that will perform the same actions repetitively based on a condition. So, the process is usually as follows when using loops:
- Start the loop.
- Perform actions.
- Check for a condition.
- Exit the loop if the condition is fulfilled or keep looping otherwise.
Sometimes the condition is performed at the start of the loop, some other times it is performed at the end of the loop. As we will see in the next paragraph this will be the case for the while and do-while loop structures, respectively.
Let’s look at the following example that is using a while loop.
int counter =0;
while (counter <=10)
{
counter++;
}
In the previous code:
- We declare the variable counter and set its value to 0.
- We then create a loop that starts with the keyword while and for which the content (which is what is to be executed while we are looping) is delimited by opening and closing curly brackets.
- We set the condition to remain in this loop (i.e., counter <=10). So we will remain in this loop as long as the variable counter is less than or equal to 10.
- Within the loop, we increase the value of the variable counter by 1 and print its value.
So effectively:
- The first time we go through the loop: the variable counter is increased to 1; we reach the end of the loop; we go back to the start of the loop and check if counter is less or equal to 10; this is true in this case because counter equals 1.
- The second time we go through the loop: counter is increased to 2; we reach the end of the loop; we go back to the start of the loop and check if counter is less or equal to 10; this is true in this case because counter equals 2.
- …
- The 11th time we go through the loop: counter is increased to 11; we reach the end of the loop; we go back to the start of the loop and check if counter is less or equal to 10; this is now false as counter now equals 11. As a result, we exit the loop.
So, as you can see, using a loop, we have managed to increment the value of the variable counter iteratively, from 0 to 11, but using less code than would be needed otherwise.
Now, we could create a slightly modified version of this loop, using a do-while loop structure instead, as illustrated in the next example:
int counter =0;
do
{
counter++;
} while (counter <=10);
In the previous example, you may spot two differences, compared to the previous code:
- The while keyword is now at the end of the loop. So the condition will be evaluated (or assessed) at the end of the loop.
- A do keyword is now featured at the start of the loop.
- So here, we perform statements first and then check for the condition at the end of the loop.
Another variation of the code could be as follows:
for (int counter = 0; counter <=10; counter ++)
{
print (“Counter = ” + counter);
}
In the previous code:
- We declare a loop in a slightly different way: we state that we will use an integer variable called counter that will go from 0 to 10.
- This variable counter will be incremented by 1 every time we go through the loop.
- We remain in the loop as long as the variable counter is less than or equal to 10.
- The test for the condition, in this case, is performed at the start of the loop.
Loops are very useful to be able to perform repetitive actions for a finite number of objects, or to perform what is usually referred as recursive actions. For example, you could use loops to create (or instantiate) 100 objects at different locations in your game, or to go through an array of 100 items. So using loops will definitely save you some code and time :-).
Classes
When coding in C# with Unity, you will be creating scripts that are either classes or that use built-in classes. So what is a class?
As we have seen earlier, C# is an objectriented programming (OOP) language. Put simply, a C# program will consist of a collection of objects that interact with each other.
Each object has one or more attributes, and it is possible to perform actions on these objects using what are called methods. In addition, objects that share the same properties are said to belong to the same class. For example, we could take the analogy of a bike.
There are bikes of all shapes and colors; however, they share common features. For example, they all have a specific number of wheels (e.g., one, two or three wheels) or a speed; they can have a color, and actions can be performed on these bikes (e.g., accelerate, turn right, or turn left).
So in object-oriented programming, the class would the Bike, the speed or the color would be referred as member variables, and any action performed on the bike (i.e., accelerating) would be referred as a member method. So if we were to define a common type for all bikes, we could define a class called Bike and for this class define several member variables and attributes that would make it possible to define and perform actions on the objects of type Bike.
This is, obviously, a simplified explanation of classes and objects, but it should give you a clearer idea of the concept of object-oriented programming if you are new to it.
Defining a class
So now that you have a clearer idea of what a class is, let’s see how we could define a class. So let’s look at the following example.
public class Bike
{
private float speed;
private int color;
private void accelerate()
{
speed++;
}
private void turnRight()
{
}
}
In the previous code, we have defined a class, called Bike, that includes two member variables (speed and color) as well as two member methods (accelerate and turnRight).
Let’s look at the script a little closer and you may notice a few things:
- The name of the class is preceded by the keywords public class; in OOP terms, the keyword public is called an access modifier and it defines how (and from where) this class may be accessed and used. In C# there are at several types of access modifiers, including public (no restricted access), protected (access limited to the containing class or types derived from this class), internal (access is limited to the current assembly), orprivate (access only from the containing type).
- The names of all variables are preceded by their type (i.e., int), and the keyword private: this means that these variables will be accessible only for objects of types Bike.
- The name of each method is preceded by the keywords private void: the void keyword means that the method does not return any data back, while the keyword private means that the method will be accessible only from the containing type (i.e., Bike). In other word, only objects of type Bike will be able to access this method.
Accessing class members and variables
Once a class has been defined, it is great to be able to access its member variables and methods. In C#, and as for many other object-oriented programming languages, this can be done using the dot notation.
The dot notation refers to object-oriented programming. Using dots, you can access properties and functions (or methods) related to a particular object. For example gameObject.transform.position gives you access to the position from the transform of the object linked to this script. It is often useful to read it backward; in this case, the dot can be interpreted as “of”. So in our case, gameObject.transform.position can be translated as “the position of the transform of the gameObject”.
Once a class has been defined, objects based on this class can be created. For example, if we were to create a new Bike object based on the code that we have seen above (i.e., based on the definition of the class Bike), the following code could be used.
Bike myBike = new Bike();
This code will create an object based on the “template” Bike. You may notice the syntax:
dataType variable = new dataType()
By default, this new object will include all the member variables and methods defined earlier. So it will have a color and a speed, and we should also be able to access its accelerate and turnRight methods. So how can this be done? Let’s look at the next code snippet that shows how we can access member variables and methods.
Bike myBike = new Bike();
b.speed = 12.3f
b.color = 2;
b.accelerate();
In the previous code:
- The new bike myBike is created.
- Its speed is set to 3 and its color is set to 2.
- The speed is then increased after calling the accelerate
- Note that to assign an object’s attribute or method, we use the dot notation.
When defining member variables and methods, it is usually good practice to restrict the access to member variables (e.g., private type) and to define public methods with no or less strict restrictions (e.g., public) that provide access to these variables. These methods are often referred to as getters and setters, because you can get or set member values through these methods.
To illustrate this concept, let’s look at the following code:
public class Bike
{
private float speed;
private int color;
private void accelerate()
{
speed++;
}
public void setSpeed (float newSpeed)
{
speed = newSpeed;
}
public float getSpeed ()
{
return (speed)
}
private void turnRight()
{
}
}
In the previous code, we have declared two new methods: setSpeed and getSpeed.
- For setSpeed: the type is void as this method does not return any information, and its access is set to public, so that it can be employed with no restrictions.
- For getSpeed: the return type is float as this method returns the speed, which type is float. Its access is set to public, so that it can be accessed with no restrictions.
So, we could combine the code that we have created so far in one program (or a new class) as follows in Unity.
using UnityEngine;
using System.Collections;
public class TestCode : MonoBehaviour {
public class Bike
{
private float speed;
private int color;
private void accelerate(){speed++;}
public void setSpeed (float newSpeed)
{
speed = newSpeed;
}
public float getSpeed (){return (speed);}
private void turnRight(){}
}
public void Start ()
{
Bike myBike = new Bike();
myBike.setSpeed (23.0f);
print (myBike.getSpeed());
}
}
In this code, you may notice at least two differences compared to the previous code snippet:
- At the start of the code, the following two lines of code have been added:
using UnityEngine;
using System.Collections;
- The keyword using is called a directive; in this particular context it is used to import what is called a namespace; put simply, by adding this directive you are effectively gaining access to a collection of classes or data types. Each of these namespaces or “libraries” includes useful classes for your program. For example, the namespace UnityEngine will include classes for Unity development and Collections will include classes and interfaces for different collections of objects. By default, whenever you create a new C# script in Unity, these two namespaces (and associated directives) are included.
- We have declared our class Bike within another class called TestCode that is, in this case, the containing class.
public class TestCode : MonoBehaviour {
- Whenever you create a new C# script, the name of the script (for example TestCode will be used to define the main class within the script (i.e., TestCode).
- The syntax “: Monobehavior” means that the class TestCode is derived from the class MonoBehaviour. This is often referred to as inheritance.
Constructors
As we have seen in the previous sections, when a new object is created, it will, by default, include all the member variables and methods of its class. To create this object, we would typically use the name of the class followed by an opening and closing round bracket as per the next example.
Bike myBike = new Bike();
myBike.color = 2;
myBike.speed = 12.3f;
In fact, it is possible to change some of the properties of the newly created object when it is initialized. For example, instead of setting the speed and the color of the object, as we have done in the previous code, it would be great to be able to set these automatically and to pass the parameters accordingly when the object is created. This can be done with what is called a constructor. A constructor literally helps to construct your new object based on parameters (also referred as arguments) and instructions. So, for example, let’s say that we would like the color of our bike to be specified when it is created; we could modify the Bike class, as follows, by adding the following method:
public Bike (int newColor)
{
color = newColor;
}
This is a new constructor (the name of the method is the same as the class), and it takes an integer as a parameter; so after modifying the description of our class (as per the previous code), we could then create a new Bike object as follows:
Bike myBike = new Bike(2);
//myBike.color = 2;
myBike.speed = 12.3f;
We could even specify a second constructor that would include both the color and the speed as follows:
public Bike (int newColor, float newSpeed)
{
color = newColor;
speed = newSpeed;
}
You can have different constructors in your class; the constructor used at the instantiation stage (i.e., when a new object is created based on an existing class) will be the one that matches the arguments that have been passed.
For example, let’s say that we have two constructors for our Bike class, as follows:
public Bike (int newColor, float newSpeed)
{
color = newColor;
speed = newSpeed;
}
public Bike (int newColor)
{
color = newColor;
}
If a new Bike object is created as follows:
Bike newBike = new Bike (2)
…then the first constructor will be called.
If a new Bike object is created as follows:
Bike newBike = new Bike (2, 10.0f)
…then the second constructor will be called.
You may also wonder what happens if the following code is used, since no default constructor has been defined.
Bike newBike = new Bike ();
In fact, whenever you create your class, a default constructor is also defined implicitly and evoked whenever a new object is created using the new operator with no arguments. This is called a default constructor. In this case, the default values for each of the types of the numerical member variables are used (e.g., 0 is the default value for integers and false is the default value for Boolean variables).
Note that access to constructors is usually public, except in the particular cases where we would like a class not to be instantiated (e.g., for classes that include static members only). Also note that, as for variables, if no access modifiers are specified, member variables will be private by default. This is similar for methods.
Destructors
As for constructors, when an object is deleted, the corresponding destructor is called. Its name is the same as the class and it is preceded by a tilde ~; as illustrated in the next code snippet.
~Bike()//this is the destructor
{
print(“Object has been destroyed”);
}
This being said, a destructor can neither take parameters (or arguments) nor return a value.
Static member variables and methods
When a method or a variable is declared as static, only one instance of this member exists for a class. So a static variable will be “shared” between instances of this class.
Static variables are usually employed to retrieve constants without instantiating a class. The same applies for static methods: they can be evoked without the need to instantiate a class.
This can be very useful if you want to create and avail of tools to manipulate data or objects without the need for instantiation. For example, in Unity, it is possible to use the method GameObject.Find(); this method usually makes it possible to look for a particular object based on its name. Let’s look at the following example.
public void Start()
{
GameObject t = (GameObject) GameObject.Find(“test”);
}
In the previous code, we look for an object called test, and we store the result inside the variable t of type GameObject. However, when we use the syntax GameObject.Find, we use the static method Find that is available from the class GameObject. There are many other static functions that you will be able to use in Unity, including Instantiate. Again, these functions can be called without the need to instantiate an object. The following code snippet provides another example based on the class Bike. The following code illustrates the use of static variables.m
using UnityEngine;
using System.Collections;
public class TestCode : MonoBehaviour {
public class Bike
{
private float speed;
private int color;
private static nbBikes;
private int countBikes()
{
nbBikes++;
}
private int getNbBikes()
{
return(nbBikes);
}
}
public void Start ()
{
Bike bike1 = new Bike();
Bike bike2 = new Bike();
bike1.countBikes();
bike2.countBikes();
print(“Nb Bikes:”+getNbBikes());
}
}
In the previous code:
The member variable nbBikes is static and will be shared between all instanced of the class Bike.
- We create a method called countBikes that will increase the value of the variable nbBikes every time it is called.
- We then instantiate two bikes, bike1 and bike2.
- We also call the method countBikes from both bikes (i.e., from bike1 and bike2).
- Finally, we print the value of the variable nbBikes.
- This code should display the message “NB bikes 2”.
The following code illustrates the use of static functions.
using UnityEngine;
using System.Collections;
public class TestCode : MonoBehaviour {
public class Bike
{
private float speed; private int color;
public static sayHello(){print (“Hello”);}
}
public void Start (){Bike.sayHello();}
}
The previous code would result in the following output:
Hello
In the previous code, we declare a static method called sayHello; this method is then called from the Start method without the need to instantiate (or create) a new Bike. This is because, due to its public and static attributes, the method sayHello can be accessed from anywhere in the program.
Inheritance
I hope everything is clear so far, as we are going to look at a very interesting and important principle for object-oriented programming: inheritance. The main idea behind inheritance is that objects can inherit their properties from other objects (i.e., their parents). As they inherit these properties, the new objects (i.e., the children) can be identical to the parents or evolve and overwrite some of their inherited properties. This is very interesting because it makes it possible to minimize your code by creating a class with general properties for all objects that share similar features, and then, if need be, to overwrite and customize some of these properties.
Let’s take the example of vehicles; generally, vehicles have some of the following properties:
- Number of wheels.
- Number of passengers.
- Capacity to accelerate.
- Capacity to stop.
So we could create the following class for example:
class Vehicles
{
private int nbWheels;
private float speed;
private int nbPassengers;
private int color;
private void accelerate()
{
speed++;
}
}
In the previous code, we have defined a class called Vehicles that includes four member variables and a member method called accelerate.
The member variables defined in this class (i.e., nbWheels, speed, nbPasssengers, or color) could apply to cars, bikes, motorbikes, or trucks. However, all these vehicles also differ; some of them may or may not have an engine or a steering wheel, for example. So we could create a subclass called MotorizedVehicles, based on the class Vehicles, but with specificities linked to the fact that they are motorized. These added attributes could be as follows:
- Engine size.
- Petrol type.
- Petrol levels.
- Ability to fill-up the tank.
The following example illustrates how this class could be created.
class MotorizedVehicles: Vehicles
{
private float engineSize;
private int petrolType;
private float petrolLevels;
private void fillUpTank()
{
petrolLevels+=10;
}
}
In the previous code:
- We create a new class called MotorizedVehicles that inherits from the class Vehicles.
- When the class is defined, its name is followed by “: Vehicles”. This means that it inherits from the class Vehicles. So it will, by default, avail of all the methods and variables already included in the class Vehicles.
- We have created a new member method for this class, called fillUpTank.
- In the previous example, you may notice that the methods and variables that were defined for the class Vehicles do not appear in the code snippet; this is because they are implicitly added to this new class, since it inherits from the class Vehicles.
Whenever you create a new class in Unity, it will, by default, inherit from the MonoBehaviour class; as a result, it will implicitly include all the member methods and variables of the class MonoBehaviour. Some of these methods include Start or Update, for example.
When using inheritance, the parent is usually referred to as the base class, while the child is referred to as the inherited class.
Now, while the child inherits behaviors and attributes from its parents, these can always be modified or, put simply, overwritten. However, in this case, the base method (the method defined in the parent) must be declared as virtual. Also, when overriding this method, the keyword override must be used. This is illustrated in the following code.
class Vehicles
{
private int nbWheels;
private float speed;
private int nbPassengers;
private int color;
private virtual void accelerate()
{
speed++;
}
}
class MotoredVehicles: Vehicles
{
private float engineSize;
private int petrolType;
private float petrolLevels;
private void fillUpTank()
{
petrolLevels+=10;
}
private override void accelerate()
{
speed+=10;
}
}
In the previous example, while the method accelerate is inherited from the class Vehicles, it would normally increase the speed by one. However, by overwriting it, we make sure that in the case of objects instantiated from the class MotoredVehicles, each acceleration increases the speed by 10 instead.
There are obviously more concepts linked to inheritance; however, the information provided in this section should get you started easily. For more information on inheritance in C#, you can look at the official documentation.
Methods
Methods or functions can be compared to a friend or colleague to whom you gently ask to perform a task, based on specific instructions, and to return the information to you then, if need be. For example, you could ask your friend the following: “Can you please tell me when I will be celebrating my 20th birthday given that I was born in 2000”. So you give your friend (who is good at Math :-)) the information (i.e., date of birth) and s/he will calculate the year of your 20th birthday and then give you this information. So in other words, your friend will be given an input (for example, the date of birth) and return an output (for example, the year of your 20th birthday).
Methods work exactly this way: they are given information (and sometimes not), they perform an action, and then, if needed, they return information.
In programming terms, a method (or a function) is a block of instructions that performs a set of actions. It is executed when invoked (or put more simply, when it is called) from the script, or when an event occurs (for example, when the player has clicked on a button or when the player collides with an object). Member methods need to be declared before they can be called.
Methods are very useful because once the code for a method has been created, this method can be called several times without the need to re-write the same code over and over again. Also, because a method can take parameters, it can process these parameters and produce (or return) information accordingly; in other words, a method can perform different actions and produce different results depending on the input. So methods can do one or all of the following:
- Take parameters and process them.
- Perform an action.
- Return a result.
A method has a syntax and can be declared as in at least two ways.
AccessType typeOfDataReturned nameOfTheFunction ()
{
Perform actions here…
}
In the previous code the method does not take any input; neither does it return an output. It just performs actions.
OR
AccessType typeOfDataReturned nameOfTheFunction (typeOfParameater1 param1, typeOfParameater2 param2)
{
Perform actions here…
}
Let’s look at the following method for example.
public int calculateSum(int a, int b)
{
return (a+b);
}
In the previous code:
- The method is of access type public and it can be accessed without restriction.
- The method will return an integer.
- The name of the method is calculateSum.
- The method takes two integer arguments (or parameters).
- The method returns the sum of the two parameters that were passed to this function. These parameters will be referred as a and b within this method.
A method can be called using its name followed by an opening and closing round bracket, as follows:
nameOfTheFunction1();
nameOfTheFunction2(value);
int test = nameOfTheFunction3(value);
In the previous code:
- A method is called with no parameter (line 1).
- A method is called with a parameter (line 2).
- In the third example (line 3), a variable called test will be set with the value returned by the method nameOfTheFunction3.
You may, and we will get to this later, have different methods in a class with the exact same name but each of these methods may require a different number and different types of parameters. This is often referred as polymorphism, as the method literally takes different forms and can process information differently based on the information (e.g., type of data) provided.
Accessing methods and access modifiers
As we have seen previously, in C# there are different types of access modifiers. These modifiers specify from where a method can be called. Common modifiers include:
- public: this means that there is no restricted access.
- protected: this means that access is limited to the containing class or types derived from this class.
- internal:this means that access is limited to the current assembly.
- private:this means that access is limited to the containing type.
Overloading
Overloading is a concept linked to polymorphism, whereby a method can take different forms. By overloading a method, you can create multiple methods with the same name but with a different number and types of parameters. Let’s look at the following example:
public class MyBike
{
int speed;
void accelerate()
{
speed++;
}
void accelerate (int increment)
{
speed = speed+ increment;
}
void runBike()
{
accelerate();
accelerate(10);
}
}
In the previous code:
- We declare a new class called MyBike.
- We also declare a member variable called speed.
- We then create two methods named accelerate.
- The first method takes no parameters and it will increase the speed of the bike by one every time it is called.
- The second method will also increase the speed of the bike; however, it takes a parameter that will be used to specify by how much the speed should be increased.
- Then the method runBike is declared; it calls the two methods accelerate;
- The code accelerate(); calls the first accelerate This is because no parameter is passed to the method accelerate, so it is assumed that we refer to the first version of the accelerate method.
- The code accelerate(10); calls the second accelerate This is because an integer parameter is passed to the method this time, so it is assumed that we refer to the second accelerate method.
So in other words, you can overload functions by creating multiple functions with the same name as long as their argument list (i.e., the number and the types of the arguments) is different.
Local, member and global variables
Whenever you create a variable in C#, you will need to be aware of the scope and access type of this variable so that you know where it can be used.
The scope of a variable refers to where you can use this variable in a script. In C#, we usually make the difference between member variables and local variables.
You can compare the term local and member variables to a language that is either local or global. In the first case, the local language will only be used (i.e., spoken) by the locals, whereas the global language will be used (i.e., spoken) and understood by anyone whether they are locals or part of the global community.
When you create a class definition along with member variables, these variables will be seen by any method within your class.
Member variables are variables that can be used anywhere in your class, hence the name member. These variables need to be declared at the start of the script (using the usual declaration syntax) and outside of any method; they can then be used anywhere in the class as illustrated in the next code snippet.
class MyBike
{
private int color;
private float speed;
public void accelerate()
{
speed++;
}
}
In the previous code, for example, we declare the variable speed as a member variable and we access it from the method accelerate.
Local variables are declared within a method and are to be used only within this method, hence the term local, because they can only be used locally, as illustrated in the next code snippet.
public void Start()
{
int myVar;
myVar = 0;
}
public void Update()
{
int myVar2;
myVar2 = 2;
}
In the previous code, myVar is a local variable to the method Start, and can only be used within this function; myVar2 is a local variable to the method Update, and can only be used within this method.
Last but not least, you can create member variables that can be accessed from anywhere in your game. To do so, this variable will need to be declared as a public static member variable as follows:
class MyBike
{
public static nbBikes;
private int color;
private float speed;
public void accelerate()
{
speed++;
}
}
The reason for the global variable nbBikes to be accessible throughout your game is because it is both public (i.e., accessible from anywhere) and static (i.e., shared across your game).
Polymorphism (general concepts)
The word polymorphism takes its meaning from poly (several) and morph (shape); so it literally means several forms. In object-oriented programming, it refers to the ability to process objects differently depending on their data type or class. Let’s take the example of a simple addition. If we want to add two numbers, we just make an algebraic addition (for example, 1 + 2). However, adding two string variables may mean concatenating them, which means adding them one after the other. For example, adding the text “Hello” and the text “World” would result in the text “HelloWorld”. As you can see, the result of an operation may depend on the data types involved. So again, with polymorphism we will be able to customize methods (or operations) so that data is processed based on its type of class. So, let’s look at the following code which illustrates how this can be done in C#.
public class AddObjects
{
public int add (int a, int b)
{
return (a + b);
}
public string add (string a, string b)
{
return (a + b);
}
}
In the previous code, it is possible to add two different types of data: integers and strings. Depending on whether two integers or two strings are passed as parameters, we will be calling either the first add method or the second add method.
The decision on whether the first or second version of the method add is called will be made at compilation time, because we know, before the script is compiled, what method should be called depending on the number of types of the variables passed to this method; this is, therefore, called static polymorphism. The term static means that after compiling our script we still know for sure that a particular piece of code will call a specific function; for example, in our case, if we add the code add (2 , 2) to our script before compilation, we know for sure that after compilation the first version of the function add will be called.
Dynamic polymorphism and overriding
As we have seen in the previous section, static polymorphism can be achieved by using, for example, overloaded methods. This being said, there are times when we won’t effectively know what code will be executed when we call a function at run-time, and this can be, for example, seen in (and implemented through) dynamic polymorphism.
In dynamic polymorphism, although we may know the name of the method to be called, the actual actions performed by the functions may vary at run-time (hence the term dynamic).
In C#, dynamic polymorphism can be achieved using both abstract classes and virtual functions.
In C#, it is possible to create a class that will provide a partial implementation of an interface. Broadly, an interface defines what a class should include (for example, the member methods, the member variables or some events), but it does not declare how these should be implemented. So, an abstract class will include abstract methods or variables. This means that this class will define the name and the type of the member variables, the name and the types of parameters for member methods, as well as the type of data returned by these methods. This type of class is called abstract because such classes cannot be instantiated. However, these classes can be used as a template (or “dream” class) for derived classes. Let’s look at the following example to illustrate this concept.
abstract class Vehicule
{
public abstract void decelerate();
}
class Bike: Vehicule
{
private float speed;
private int color;
public Bike (float newSpeed)
{
speed = newSpeed;
}
public override void decelerate()
{
speed –;
}
}
In the previous code:
- We declare an abstract class Vehicle.
- We declare an abstract method called decelerate.
- We then create a new class called Bike, inherited from the abstract class Vehicle.
- We then override the abstract method decelerate to use our own implementation.
Using an abstract class just means that we list methods that would be useful for the children; however, the children classes will have to define how the method should be implemented.
The second way to implement dynamic polymorphism is by using virtual methods or variables. In the case of virtual methods, we declare a method that will be used by default by objects of this class or inherited classes; however, in this case, even if the method is ready to be used (this is because we have defined how it should be implemented), it can be changed (or overridden) by the child (or the inherited class) to fit a specific purpose. In this case (i.e., inherited method), we need to specify that we override the method that was initially implemented by the parent by using the keyword override.
The key difference between an abstract and a virtual method is that, while an abstract method should be overridden, a virtual method may be overridden if the base method (which is the method declared in the base or parent class) does not suit a particular purpose.
Let’s look at the following example to illustrate the concept of virtual methods:
class Vehicule
{
private float speed;
public virtual accelerate()
{
speed +=10;
}
}
class Bike: Vehicule
{
public override accelerate()
{
speed++;
}
}
In the previous code:
- We declare a class Vehicle.
- It includes both a private variable speed and a virtual method called accelerate. This method is virtual, which means the child classes (which are the classes inheriting from the class Vehicle) will be able to modify (or override) it, if need be.
- We then create a new class Bike that inherits form the class Vehicle. In this class, we override the method accelerate using the keyword override so that the speed is just incremented by one.
NameSpaces
When you create a new script in Unity, it usually includes the following lines at the top of the script automatically.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
This code is effectively specifying the location of some classes that you may use in your code, and provides information about what namespace (which is comparable to a folder or directory) a specific class belongs to. This is to avoid any clash or confusion and to ensure that even if a class is declared in two different namespaces, that it is clear as to which class (and namespace) you want to use.
So what is a name space?
Namespaces are containers within which you can declare classes; let’s look at the following example:
namespace NameSpace1
{
public class MyClass
{
public static int add(int a, int b)
{
return (a + b);
}
}
}
namespace NameSpace2
{
public class MyClass
{
public static int add(int a, int b, int c)
{
return (a + b + c);
}
}
}
In the previous code:
- We declare two namespaces called NameSpace1 and NameSpace2.
- Within these namespaces, that act as containers, we declare a class called MyClass.
- So we have two classes with the exact same name (i.e., MyClass); however, we can manage to tell them apart based on their namespace (or their container).
Now that the two different namespaces have been defined, we need to find a way to specify which namespace will be used when classes with the same name are used or instantiated.
This can be done in at least two ways. The first way is to implicitly mention the namespace and the nested class within. For example, we could write the following code to refer to the first class, as illustrated in the next code snippet.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyNAmeSpacesExample : MonoBehaviour
{
void Start ()
{
int sum1 = NameSpace1.MyClass.add(1,2);
int sum2 = NameSpace2.MyClass.add(1,2,3);
print (“Sum1:” + sum1 + “; Sum2:” + sum2);
}
}
In the previous code:
- We access the method add, that is both public and static, from the class MyClass that is within the namespace NameSpace1.
- We also access the method add (that is both public and static) from the class MyClass that is within the namespace NameSpace2.
- The add method that is used in the previous example is public, so it can be accessed from anywhere in our programme; it is also static so it can be accessed without the need to instantiate an object of the class MyClass.
Another way to do this is to specify, from the very start of the script, that we will be using the namespaces NameSpace1 or NameSpace2, and this can be done with the keyword called using as follows:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NameSpace1;
public class MyNameSpacesExample : MonoBehaviour
{
void Start ()
{
int sum1 = MyClass.amdd(1,2);
print (“Sum1:” + sum1);
}
}
In the previous code:
- We declare (or we make a reference to) the namespace NameSpace1.
- This means that, if in doubt about where to find a particular class, it may be found in this namespace.
- We then call the method add; however, because we have defined a reference to the namespace NameSpace1, the system will automatically look into this namespace to find the class MyClass and the method add.
So how can namespaces be used in Unity?
Using namespaces in Unity can make your code more concise and it can also save you a lot of time. For example, let’s say that you want to write some text onscreen through UI objects (such as UI Text objects) from your script. In this case, you may use code that is similar to the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class NameSpaceForUI : MonoBehaviour
{
// Use this for initialization
void Start () {
GameObject.Find (“TextUI”).GetComponent<Text> ().text = “”;
GameObject.Find (“scoreUI”).GetComponent<Text> ().text = “Score:”;
}
}
In the previous code, we declare that we will be using the UI namespace with the code:
using UnityEngine.UI;
If we had not used the namespace UnityEngine.UI, the following line
GameObject.Find (“TextUI”).GetComponent<Text> ().text = “”;
may have needed to be written as follows instead:
GameObject.Find (“TextUI”).GetComponent<UnityEngine.UI.Text> ().text = “”;
So by specifying the namespace UnityEngine.UI we can shorten our code and use GetComponent<Text> or GetComponent<Slider> as both the Text and Slider classes are part of the namespace UnityEngine.UI.
More often than not, you may not need to use additional namespaces in Unity unless you create your own library. However, as illustrated previously, it can be useful to shorten your code when classes from the same namespace are employed several times.
When creating your scripts, you may wonder when you need the following statements:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
Whether you will need to use these lines depends on what you need to perform in your code:
- The namespace UnityEngine: most of your scripts will be linked to GameObjects, and will therefore need to extend the class MonoBehaviour; since the class MonoBehaviour belongs to the UnityEngine namespace, you will, in most cases, need this line. In addition, several useful classes are provided within the UnityEngine namespace.
- The namespace Collections: this namespace includes many basic classes including IEnumerators which are often used for coroutines.
- The namespace Collections.Generic: this namespace includes some useful classes provided by C# including Lists or Dictionaries.
So, if you need to keep one of these, you may keep UnityEngine, although, it is good practice to keep all three lines, just in case they may be needed later in your class.
Lists
As we have seen in the previous sections, it is sometimes useful to employ arrays. However, when you are dealing with a large amount of data, or data that is meant to grow overtime, lists may be more useful, as they include built-in tools to sort and organize your data.
In addition, lists are generally more efficient as your data grows. So, you don’t always need to use lists; however, they may be more efficient to organize your data, especially for large and evolving data sets.
So let’s look into Lists.
You can declare a list as follows:
List <int> myList;
The declaration follows the syntax:
List <type> nameOfVariable;
So you could create a list of integers, strings, or Cubes if you wished.
Once the list has been created, C# offers several built-in methods that make it possible to manipulate a list, including:
- Add: adds an item at the end of the list.
- Insert: inserts an item at a specific index.
- Remove: removes an item from the list.
- RemoveAt: removes an item at a specific index
- Count: returns the number of items in a list.
- Sort: sorts the elements of a list.
Each element in the list has a default index that is relative to when it was first added to the list; the earlier the item is added to the list and the lower the index. So the first item added to the list will have, by default, the index 0, the second item will have the index 1, and so on. Let’s look at an example:
List <string> listOfNames = new List<string> ();
listOfNames.Add (“Mary”);
listOfNames.Add (“Paul”);
print (“Size of List” + listOfNames.Count);
//this will display “Size of list 2”
listOfNames.Remove(“Paul”);
print (“Size of List after removing” + listOfNames.Count);
//this will display “Size of list 1”
In the previous code:
- We create a new list of string
- We then add two elements to the list: the strings Mary and Paul.
- We display the size of the list before and after an item has been removed from the list.
Dictionaries
Lists are very useful, and dictionaries, which are special type of lists, take this concept a step further. With dictionaries, you can define a dataset with different records, and each record is accessible through a key instead of an index; for example, let’s consider a class of students, each with a first name, a last name, and a student number. To represent and manage this data, we could create code similar to the following:
public class Student{
public string firstName;
public string lastName;
public Student(string fName, string lName)
{
firstName = fName;
lastName = lName;
}
}
- We could then create code that uses this class as follows:
Dictionary<string, Student> students = new Dictionary<string, Student>();
students.Add(“ST123”,new Student(“Mary”, “Black”));
students.Add(“ST124”,new Student(“John”, “Hennessy”));
print (“Name of student ST124 is ” + students [“ST124”].firstName);
In the previous code:
- We declare a dictionary of Students.
- When declaring the dictionary: the first parameter, which is a string, is used as an index or a key; this index will be the student id.
- The second parameter will be an object of type Student.
- So effectively we create a link between the key and the Student
- We then add students to our dictionary.
- When using the Add method, the first parameter is the key (or the student id in our case: ST123 or ST124 here), and the second parameter is the student object. This student object is created by calling the constructor of the class Student and by passing relevant parameters to the constructor, such as the student’s first name and last name.
- Finally, we print the first name of a specific student based on its student id.
As for lists, Dictionaries have several built-in functions that make it easier to manipulate them, including:
- Add: to add a new item to the dictionary.
- ContainsKey: to check if a record with a specific key exists in the dictionary.
- Remove: to remove an item from the dictionary
Events
Put simply, events can be compared to something that happens at a particular time. When this event occurs, something (an action for example) needs to be performed. To simplify things, we could draw an analogy between events and daily activities: when your alarm goes off in the morning (which is an event) you can either get-up (this is an action) or decide to go back to sleep. Similarly, when you receive an email (this is another event), you can decide to read it (this is another action), and then reply to the sender (another action).
In computer terms, the concept of events is relatively similar, although the events that we will be dealing with in C# will be slightly different to the ones we just mentioned. For example, we could be waiting for the user to press a key (an event) and then move the character accordingly (an action), or wait until the user clicks on a button on screen (an event) to load a new scene (which is an action).
Usually in Unity, whenever an event occurs, a function is called. The function, in this case, is often referred as a handler, because it “handles” the event that just occurred. You have then the opportunity to modify this function and to add instructions (which include statements) that should be executed when this event occurs.
To draw an analogy with daily activities: we could write instructions to a friend on a piece of paper, so that, in case someone calls in our absence, this friend knows exactly what to do. So an event handler is basically a set of instructions, usually stored within a function, that need to be followed in case a particular event occurs.
Sometimes information is passed to this method about the particular event that just occurred, and sometimes not. For example, in Unity, when the screen is refreshed the method Update is called. When a particular script is enabled, the method Start is called. When there is a collision between the player and an object, the method OnControllerColliderHit is called. For this particular event (which is a collision), an object is usually passed to the method that handles the event so that we get to know more about the other object involved in the collision.
As you can see, many events can occur in our game, and in your games, you will more than likely be dealing with the following events:
- Start: when a script is enabled (e.g., start of the scene).
- Update: when the screen is refreshed (e.g., every frame).
- OnControllerColliderHit: when a collision occurs between the player and another object.
- Awake: when the game starts (i.e., once throughout the lifetime of your game).
Using coroutines.
In a nutshell, coroutines are a bit like a soccer team where the ball is passed to a player; this player receives the ball and starts to run, however, s/he needs information from the team doctor to know whether s/he can go ahead and what s/he can do to heal a recent injury. So s/he just passes the ball to another teammate and freezes (or waits) until s/he receives instructions from the doctor. When s/he receives this instruction, the ball is passed to him/her again and s/he resumes to play. So in this case, the players share the ball, but only one player has the ball at one particular time. So coroutines are a way to collaboratively run a program with only one function running at a time. If you have used threads, coroutines and threads differ in that threads run in parallel whereas coroutines work collaboratively to freeze one function and give it the focus again when criteria have been fulfilled (that is, only one coroutine running at any given time). Coroutines are usually referred as concurrency as they pass control to each-other.
A coroutine is interesting in that it can give the control back to the main program and then the coroutine can resume where it stopped. So you can call the coroutine, and have it to complete some work, and give the control back to the main program if need be. So because you can keep track of where the routine paused, it can be useful to schedule events so that the coroutine completes tasks only in specific circumstances.
By default, a coroutine method will always be declared with the keyword IEnumerator beforehand. It also usually includes a yield statement marking where the coroutine should pause and resume. A coroutine is also usually called using the keyword StartCoroutine, as illustrated in the next code snippet.
void Update ()
{
StartCoroutine (myCoRutine ());
}
IEnumerator myCoRutine ()
{
yield return 0;
}
In the previous code;
- We call the method myCoRoutine from the Update
- The method myCoRoutine that will be used as a coroutine, is declared with the keyword IEnumerator. This makes sure that this method can be paused and resumed as a coroutine where the yield keyword appears.
- The coroutine returns directly to the part of the program that called it in the first place.
The only issue with this code is that the coroutine will be called every frame (which defeats the purpose of the coroutine). If instead, we would prefer the coroutine to be called every 5 seconds, we could manage to freeze it for 5 seconds using the method WaitForSeconds. This method, as illustrated in the next code snippet, can be used to pause a coroutine for a specific amount of time.
float time;
void Start ()
{
time = 0;
}
void Update ()
{
time += Time.deltaTime;
StartCoroutine (myCoRoutine ());
}
IEnumerator myCoRoutine ()
{
while (time >5)
{
time = 0;
print (“Hello time: “+ (int)time);
yield return (new WaitForSeconds(5));
print (“Just resuming after 5 seconds”);
}
}
In the previous code:
- We create a variable called time, that will be used to monitor the time.
- This variable is increased every seconds.
- As previously, we call the coroutine myCoRoutine.
- In this coroutine, we create a loop that will be triggered whenever the time goes over 5 seconds.
- As we enter the loop we reset the time to 0.
- We also print a message that includes the time and then pause this coroutine for 5 seconds. So the next time the routine resumes (that is, after 5 seconds), it should print the message “Resuming after 5 seconds” which is located just after the yield statement.
- The time variable will be increased by one every seconds in the meantime. While the coroutine is suspended, the Update function is still executed, so the time is updated every seconds.
- So after 5 seconds, the coroutine is resumed, and the value of the variable time is also more than 5. This means that we will enter the loop and resume just after the yield
- Because the time is reset to 0 every time we enter this loop, and because we are using a while loop, the coroutine will indefinitely be called every 5 seconds.
Note that the condition for the coroutine to pause is that 5 seconds have elapsed. This is done through the statement new WaitForSeconds(5). This being said, it is also possible to employ other conditions to specify when the coroutine should resume, such as WaitUntil or WaitWhile. For example, we could use the following code instead.
yield return (new WaitUntil (()=> time>5));
In the previous code, we will wait to resume the coroutine until the variable time is greater than 5.
So this example shows how you can manage to schedule events and actions that should only be executed at specific times and these actions are embedded in coroutines that can be frozen overtime. To use the analogy of a soccer team: every 5 seconds we pass the ball to the player X (the coroutine) who will perform actions with it (for example, dribble) and will pass the ball back to other members of the team after this time has elapsed.
Note that we have used a loop in the code; otherwise, we could not indefinitely call the coroutine.
So coroutines are very useful because they make it possible to run code asynchronously and they can be used in several occasions, including:
- Scheduling actions based on time (as we have seen above).
- Loading elements in your game at specific times, to ensure that the game will still be responsive.
- Suspending execution until data has arrived (we will have a look at this in the next section).
- Scheduling the actions of Non-Player Characters over time.
- Customizing any action performed in the Update function (including dealing with finite state machines).
Using coroutines is often useful to load data over the internet. The following code could be used to load the content of a PHP page.
using UnityEngine;
using System.Collections;
public class AccessDB : MonoBehaviour {
string url = “http://localhost:8888/updateScore.php”;
// Use this for initialization
IEnumerator Start()
{
WWW www = new WWW(url);
yield return www;
string result = www.text;
print(“data received”+result);
}
void Update () {
}
}
In the previous code:
- We declare the class AccessDB.
- We then create a string called url that stores the address of the PHP page that we will access.
- We declare a function called Start using the keyword IEnumerator This keyword is used to specify that the function Start has become a coroutine, which means that it now has the ability to be paused until a condition has been fulfilled.
In our case, it is necessary to declare this function as a coroutine because the code that we use to gather information from the PHP script will need to send a request to the server and then wait for the server’s answer. However, we don’t want the whole program to stop while this data is on its way because we still need to update the screen and perform other important tasks in the meantime. So in that sense, this function does not act like a usual function, in that it doesn’t just perform actions and return to where it was called from. Instead, because part of its purpose is to gather (and possibly wait for) information from the server, which may involve delays, as a coroutine, this function will fetch for the server’s data and pause itself until the data has been received. Meanwhile, other functions, such as the Update function, will be able to run. Then, when the data is received from the server, the Start function is called again just after the point where it had been paused.
Delegates
A delegate is basically comparable to a container; in the same ways variable hold values, delegates with hold a reference to one or several functions This is very useful if you want to call several methods at the same time, for example.
To be able to use delegates, the following steps are often necessary:
- Define a template for the functions that will be associated with this delegate (for example, their return type and their list of parameters).
- Instantiate a new delegate.
- Associate this new delegate to one or several methods.
Let’s look at the following example to understand how delegates can be employed in C#:
public class delegatesExample : MonoBehaviour
{
delegate void DelegateType1 (string message);
DelegateType1 myDelegate;
void Start () {
myDelegate = fn1;myDelegate (“hello”);
myDelegate = fn2;myDelegate (“hello”);
}
public void fn1 (string string1)
{
print (string1+” from fn1″);
}
public void fn2 (string string1)
{
print (string1+” from fn2″);
}
}
In the previous code:
- We define a type for our delegate. So based on this definition, any delegate of type DelegateType1 can be associated with void methods that take a string
- We then declare a new delegate called myDelegate.
- Once this is done, we instantiate our delegate using both the methods fn1 and fn2 that are defined lower down the code snippet.
- You may notice that both methods match the type of the delegate as they don’t return values (their return type is void) and they take one string variable as a parameter.
- The first time this delegate is instantiated, it is associated to the method fn1. In other words, calling the delegate will result in calling the method fn1.
- The same is done for the method fn2.
So, as you have seen in this example, delegates are comparable to variables in the sense that they are containers, but unlike variables, they refer to methods instead of values.
Now, there are times when you may want to include more than one method in a delegate container so that calling the delegates calls simultaneously several functions instead of one.
This can be done easily by replacing these lines…
myDelegate = fn1;myDelegate (“Hello”);
myDelegate = fn2;myDelegate (“Hello”);
…with these lines:
myDelegate += fn1; myDelegate += fn2;
myDelegate (“Hello”);
In the previous code:
- We successively add fn1 and fn2 to the delegate.
- We then call the delegate.
- As a result, both the messages “Hello from fn1” and “Hello from fn2” should be printed in the Console.
Events and delegates
As we have seen previously, delegates are very useful when the function to be called may differ depending on specific circumstances. Delegates can also be associated to events so that when the event occurs, a specific function can be called based on specific circumstances, and we will look at this principle in the next paragraphs.
Events are useful as they help to optimize your code. Their principle is simple: you define an event and specify what should be done if this event happens. In our case, a function (or a delegate) could be associated to this event and be called when this event occurs.
Instead of constantly pooling (or looking for) a specific event, you will be notified when this event occurs, and this can save significant time and make your code leaner and more efficient. This concept is comparable to asking your friend to call you when s/he knows that concert tickets are on sale, instead or calling him or her every minute.
We could take the analogy of a calendar. You could look at a specific website every day to check whether there will be high temperatures tomorrow, or instead, subscribe to an alert service that will let you know automatically when predicted temperatures are high.
To apply the concept of delegate and events, you will usually need do the following:
- Declare a delegate type.
- Declare an event of the type defined above.
- Make sure that the event can be accessed from outside the class without having to instantiate the class.
- Register this event with any other script that needs to know when this event is happening.
- Define what needs to be done in the previous script in case a notification has been sent and received.
To implement this concept, we could, for example, create a script called MyEventManager, as illustrated in the next code snippet, and link it to an empty object.
public class MyEventManager : MonoBehaviour
{
public delegate void DelegateType1 (string name);
public static event DelegateType1 keyTyped;
void Update ()
{
if (Input.GetKeyDown (KeyCode.A))
{
keyTyped (“A”);
}
}
}
In the previous code:
- We declare a class called MyEventManager.
- We then declare a delegate type called DelegateType1.
- We declare an event called keyTyped of type DelegateType1. Note that an event is a special type of delegate that includes added security features that make them more appropriate for this purpose. This being said, we could have used a delegate instead.
- We detect when the player presses the key A. In this case the event (or delegate) called keyTyped is called.
- If you remember well, based on the delegate type, we pass a string as a parameter;
So, at this stage, we have defined the delegate that should be called when the key A is called. The next step is to make sure that the scripts interested in this event subscribe to this event. We also need to define what these scripts should do when this event occurs and when they are notified accordingly.
This could be done by creating the following script.
public class EventSubscriber : MonoBehaviour
{
void OnEnable()
{
MyEventManager.keyTyped += doSomething;
}
void Disable()
{
MyEventManager.keyTyped -= doSomething;
}
void doSomething (string fname)
{
print (“You pressed” + fname);
}
}
In the previous code:
- We declare a new class called EventSubscriber.
- We use the methods OnEnable and OnDisable.
- In the method OnEnable, that is called whenever the object linked to this script is active and/or enabled, we specify that we subscribe to the event keyTyped, and that in case it happens, we should call the method doSomething.
- In the method OnDisable, that is called whenever the object linked to this script is deleted and/or deactivated, we specify that we unsubscribe to the event keyTyped, and that in case this event happens we should not do anything.
- The methods OnEnable and OnDisable are chosen here, essentially for safety because they are called after the methods Awake but before the method Start.
While Unity provides simple ways to deal with events and notifications using UnityEvents (please see the section on drag and drop), it is always a good idea to understand how events and delegates work, as they are the building blocks of UnityEvents.
>> If you want to download this post as a pdf file or gain access to other FREE guides on Unity and C#, you can find these here. <<