Table of Contents
Advanced Unity Programming Concepts
Author: <Nathan Kassai> Email: kassan@unlv.nevada.edu
Date: Last modified on <01/29/2023>
Keywords: <Unity, Tutorial, Step-by-Step>
Time to complete - 1 hour
In Session #3 of the Unity crash course, we will cover the following:
- Functions
- Classes
- Scope Access Modifiers
- Modifying Components via Scripts
- Coroutines
- Final Words
- Refresher Questions
Prerequisites
Please make sure to finish the last session titled “Learning the Language of Unity” before starting this session! The previous session gave you a crash course in basic C# programming and we wrote a few scripts together to demonstrate those concepts. If you have already completed the previous session, you may proceed.
Functions
Throughout this tutorial, we have utilized Debug.Log() to print out information to the terminal. Log() is known as a function, which is pre-defined code designed to be reused and sometimes return a value. Additionally, Start() and Update() are also functions which we have been using throughout this tutorial. However, Start and Update do not have any definitions, rather, they are called by Unity at specific intervals (Start is called once the scene starts, and Update is called every frame). We get to define them with our own code!
Let's say we wanted to write a script that would print out the value of two integers, and then their sum. Let's create a new script called functions and write this code in Start.
Now, let's save this script, remove the Loops component from the Cube, and add our Functions script to it. Then, play the Unity scene!
Now, this script works out no problem, but what if we wanted to repeat this process again in Start? Well, we would have to basically copy and paste the code again to get the following result:
While this will still work, the code is starting to look a little messy. What if we wanted to repeat this process 10 times? 20? 40? Well, our code would start becoming hundreds of lines long, even though the same 10 lines are repeating over and over again! Let's place the repeated block of code in a function. As I've mentioned previously, functions are used to help reduce code redundancy, but they also have the additional ability to return a value. Whenever you declare a function, you also have to declare it's return type:
dataType FunctionName(parameters){
reusable code
}
For this purpose, we will use the return type of void which just means that we do not want this function to return a value. I'll explain this more as we write more functions, but for now, write the following block of code above start:
In our function, we have defined two parameters, two integers, a and b. We then have the same block of code as before. Now, if we want to be able to use this function in our code, write the name of the function in Start, pass in the correct parameters and save. Note: When I say correct parameters, that means the number of parameters and datatypes must match the function! If you pass in three integers, or an integer and a string, our script won't be able to find what function we are talking about.
Now, go ahead and run our Unity scene to see that we get the same output as before. The difference now, is that if you wanted to run that block of code again, all you have to do is write one line instead of the whole block!
Let's change the return type of the function to be an int instead of a void. Once we do so, we get an error saying that this function needs to return something! Instead of printing out the value to the console, let's just return the value of sum.
If we were to run this code now, we wouldn't see what sum equals to! However, since this function returns a value of int, we can actually assign the function to an integer variable!
Classes
Before we get started, go ahead and create a brand new Unity project and called it Session_3.
Every time we create a brand new script, we always see the same thing: two functions called Start and Update, some statements at the top which allow us to use some code, and a line that contains the name of our script with the word public class and monobehavior attached to it. What does it actually mean? Let's try to understand it by opening a new script called LearningClasses, and opening it in Visual Studio.
In programming, you can think of a Class as a blueprint for your Game Objects where each blueprint contains some sort of functionality. For instance, last session, we saw that a Cube by default has several components added to it: A transform to determine its position, rotation, and scale in space, a box collider to determine if any collision has occurred, and a Mesh Renderer to render (visually display) the mesh of the cube. Each of those components are scripts which contain classes, and in those classes, we have functions and variables that define what that component is and what it does. Imagine instead of individual classes, we had one large script that defined what a Cube was. Now this nice and easy to organize inspector has become a source code/debugging nightmare! Additionally, we are able to reuse these components for other Game Objects too!
Now, in that same line, we see a colon followed by the phrase MonoBehaviour. In C#, that colon represents something known as inheritance, and MonoBehaviour is the base class from which every Unity script derives. What that means is, MonoBehaviour is a class which defines what a Unity component is, and what it is allowed to do. In fact, if you hover your mouse over MonoBehaviour and click on the Class declaration, you can see that MonoBehaviour also inherits another class called Behavior, and Behavior inherits another class, which then inherits another class, and so on and so forth.
(show gif of inheritance tree here)
With that being said, what does inheritance mean then? All it means is that all (or most) of the functionality of one class, can now be given to another. In other words, it allows you to create new classes that can reuse, extend, and modify behaviors defined in other classes. Since we are inheriting MonoBehaviour (and MonoBehaviour inherits the class Component), our newly created script is now a Component as well! If we did not inherit MonoBehaviour, this class would not then be a component, meaning we could not attach this script to a game object in the inspector.
With that being said, let's start learning about some use cases of our classes!
Scope Access Modifiers
In the same script, you might have noticed that in the class declaration, you also see the keyword public attached to it. In programming, public is known as a scope access modifier, which is a keyword that changes the visibility of a variable, function, or class. Let's write an example of what I mean by scope.
In the LearningClasses script, declare an int variable called value right above start and set it equal to zero.
This variable is known as a global variable. This means that the variable is visible to the entire script in all of our functions. However, if you were to move this entire variable into start like so:
Now, this variable is a local variable, which means that the variable only exists within start and cannot be accessed by any other script.
Go ahead and move this variable back to its original place and let's attach the keyword public to the beginning of the initialization of value:
Go back to our Unity scene and add an empty game object to the hierarchy. Then, add our LearningClasses script to this game object and take a look at the inspector.
Our value variable is now visible in the inspector! Now, this scope access modifier does two things:
- If we were to create an instance of our class in a different script, this variable would also be visible in that script too!
- Allows us to see and modify the variable in the inspector!
By default, if you do not add a scope access modifier to your variables and functions, they will have the private scope access modifier by default. This modifier is the exact opposite of public:
- If we were to create an instance of our class in a different script, this variable could not be accessed!
- Prevents us from seeing and modifying it in the inspector
This logic is the same for functions too! (However, you wouldn't be able to see a function show in the inspector).
Let's demonstrate an example with our LearningClasses script and an additional script. First, go back into our LearningClasses script and add the following code:
Remember, if the scope access modifier wasn't specified, it is private by default. If you omit the private keywords attached to a and b, it would be the same. The AddInt function should look familiar to you, as we implemented it last session.
In this code, we have two variables that we do not wish to be modified by another script: a and b. We have a public function which will take the sum of a and b, and display it to the console. Since the function AddInt is public, if we create an object of this class, that object will be able to use that function, but won't be able to modify the values a and b.
Next, let's create a new script called ObjectTest. Open it and write the following:
At line 7, you can see that we created an instance of this class and called it learningClassess. What this does is allow us to call the various functions or variables from that class into our new class via an object. In Start, we then set learning equal to a new instance of our class LearningClass. Additionally, we are able to call this function AddInt by first writing the object name, a period, then write the name of the function you wish to call. Note, this syntax should look similar since we've been doing it with Debug.Log()!
Now, go ahead and attach ObjectTest to our empty Game Object (make sure to remove the LearningClasses component first) and then run the Unity scene.
Towards the bottom, you can see that Unity gives us an error since we are creating an instance of a class which contains MonoBehaviour. This can be solved by simply removing the MonoBehaviour inheritance line from LearningClasses, if we do not intend on having that class be a component (i.e. we do not want to attach that script to a Game Object).
Modifying Components via Scripts
Now it's time to start doing some interesting experiments with our scripts! Go ahead and create a Cube in the hierarchy, and add the Rigid Body component to it.
Once you've done that, take a look at the inspector and look at some of the public variables that Rigid Body shows; you should see a variable called “Use Gravity” and “Is Kinematic”. These variables simply toggles the gravity that acts on this cube and toggles whether or not this cube experiences forces respectively. Let's write a script that will toggle is kinematic on the cube when I press and hold spacebar.
Go ahead and create a new script called GravityController and open it up in Visual Studio.
Now, if we wanted to be able to modify the components that are on a Game Object, there are a few ways of doing so.
- Attach this script to the Game Object and use the GetComponent function
- Create a public rigid body variable and drag it directly onto your script in the inspector
I will be showing you both ways! Let's use the GetComponent function first!
In your GravityController script, write out the following:
First, we declare a new variable of type Rigidbody and call it cube. Next, in Start, we set cube equal to what GetComponent returns (which in this case is a RigidBody). How GetComponent works, is that it will check every component located on this cube; if it finds whatever is passed in between <> (which we specified Rigidbody), then it will set it equal to the cube variable. Then, we are able to modify the component from out script!
Next, in our Update function, we have this new class called Input which is used to handle various types of input from the computer, and we are using a function called GetKey to monitor if we have pressed the Spacebar. If we press and hold space, then is kinematic will be enabled, if we let go, it will be disabled.
Go ahead and attach this script to our cube in Unity, and press play and watch the cube fall until you press and hold the space bar! (You can also remove the empty Game Object) Note: If you find that the cube still falls, click on the game window to ensure that all input is being read by Unity!
Now, let's use the alternative method to modify a component!
While there isn't a large difference in this code besides the addition of the public keyword and the removal of the GetComponent function, head back to the Unity scene and make sure that you drag and drop the cube Game Object into the Rigid Body Slot on your script!
If you run the code again, the same outcome occurs!
If you plan on modifying components within a Game Object that your script is attached to, then use GetComponent! If you plan on modifying components within a Game Object that your script is NOT attached to, then create a public variable and drag the Game Object that you wish to modify onto your component slot on your script!
Coroutines
One last thing I would like to teach you regarding Unity programming are Coroutines! In Unity, Coroutines allow you to spread execution across some time frame. For instance, maybe you want to perform some calculations every 10 seconds, or you want to create a timer that indicates how much the player has left in a section of a game, or you want to create a transition effect to fade in and fade out, or maybe you want to have some code execute, but you don't need it to execute every frame like Update does, etc. Let's go ahead and demonstrate what a coroutine can do by creating a basic timer.
Create a script in Unity called Timer, and open it up in Visual Studio. Here is the following code for a simple timer:
Let's analyze this line by line.
- On the top of our script, we have our two variables: timeLeft and isTimerCounting which are used to hold time remaining and whether or not the timer has finished respectively.
- Below that, we have our coroutine function which has a return type of IEnumerator (basically, by using this return type, it allows us to stop the process at a specific moment, return that part of the object and gets back to that point whenever needed
- We first create a while loop that will run until the timer has stopped counting
- Within the while loop we have an if and else statement
- If checks if the timer hit zero, and if it did, print out that no time is remaining, and we should terminate the while loop
- else print out the current time remaining and then subtract the time by one.
- This line might seem confusing, but what this line does is wait a second before proceeding with the execution of the function (yield will remember which iteration you are currently on so as to properly end execution once the condition has been met.
- Now that we have our timer Coroutine function created, we must start the coroutine! Normally, if you want to initiate a function, you just call the function. However, with coroutines, you'd need the StartCoroutine function to properly start them!
- Go into Start and write StartCoroutine with our TimeLeft() coroutine as the parameter
Ta-da! Now, we can go back into the Unity project, and add our Timer script onto the Cube. Once that is done, go ahead and hit play to watch our timer display in the console!
Final Words
Now that we have spent a lot of time getting ourselves familiar with the language and functionality of Unity, it's time we start working on some interesting projects! Specifically, time to start working on Virtual Reality and Augmented Reality! The next section will be quite short; it is meant to be a simple introduction to Virtual and Augmented Reality. After that, we get to start working on some cool VR gear!
Refresher Questions
- What is a class?
- What is inheritance?
- What base class do all components inherit?
- By default, all members of a class are public. True or false?
- What is a coroutine? What are they primarily used for?
For questions, clarifications, etc, Email: kassan2@unlv.nevada.edu