Lesson 17 - Variable Scope

Tutorial Series: Introduction to Unity with C# Series

Previous Article  |  Next Article


Transcript

Now we are ready to talk about variable scope, which is an important coding concept in general. First, when I say “scope” I am referring back to that concept of containment that we started out with. Remember when I hauled out all those boxes to hopefully illustrate this inner and outer containment structure? That is what I am referring to when I talk about scope. Whatever is in each container is within the scope of that container. It is pretty simple to understand. When you have jellybeans in a jar, those jellybeans are immediately within the scope of that jar. The jellybeans belong within the scope of that jellybean jar, you could say. Unless there is a way that you can reach in and take them out of that scope and put it into our scope, which would be the room we are standing in, our container at that moment. That is what I mean by scope. We look at our containment structure, we have been writing most of our code in our method containers. So far most of what we have written belongs in that scope but actually, perhaps without completely realizing it, we have created yet another containment scope in the last few videos when we touched upon conditionals. Remember how I mentioned that those curly Braces can be seen as the walls of your container? Creating an if statement introduced another containment scope within our method container. Just like with the jellybeans in a jar, the code within that if statement for example belongs to that scope, it most immediately belongs to that scope and the outer scope would be the method container. Let me write some code and you can try to Guess at the output and why it may or may not work. Let's actually create a new script for this, call it ScopeTest or just Scope. Then we will as usual, attach it to our Test game object so we can see its output. Click and drag it there. Just as before, tidying up our script, our class, and making sure that it's as simple as possible for what we need to do. Right now let me just create a method, call it scopeTest() and we will write in this code. If conditional. Now we are going to create another containment structure, type this in: string InsideVar.

We will assign a string to that "...outside of this scope" Then, we will put in the Debug.Log() that output for InsideVar. Here in order to just make it evaluate true we will just type in true. That is just useful for testing purposes, don't let that confuse you. We will just make it true and that will absolutely evaluate that if statement. Already we see a red squiggly line so that will probably give you a hint as to what to expect with this output. Here, we have a variable which is first declared in the conditional code block making it immediately belonging to that scope. The scope of the conditional code block. When we try to reference it outside of that scope it simply doesn't work. Here, we would have to put the Debug.Log() output inside of that conditional block in order for that to be able to reference that variable and get that output, or else we would have to first declare the variable in the outer scope, the same scope that the Debug.Log() is in. That is another option. Let's do that and demonstrate how that would look. Type in string OustideVar and I will rename that to OutsideVar and type in "accessible to inner scope." OutsideVar. Let's see if this works. Build it. Excuse me. I already declared it as a string and I don't have to do it again, so remove the type in the conditional and there you go. It builds fine, build succeeded. As you can see here, the rule of thumb is that the inner scopes can access elements that are first declared outside of its scope but the reverse is not possible. In other words, inner containers can reach out and grab elements from outer containers, but outer containers can't reach in and grab elements from inner containers.

Real simple rule of thumb to generally keep in mind. Here is another example with another level of containment just to make this explicitly clear what we are doing here. Start from scratch. Type in string OutsideVar. We are declaring that variable. And if (true) creating a conditional block, OutsideVar = "accessible to inner scope." Now we are going to create yet another containment structure, a nested if and we will type in... We will reference that variable and we will assign it a new string "accessible to inner, inner scope." It is accessible to this inner inner scope, and then in this conditional block, this inner inner scope, we will declare a new variable called InsideVar and we will say it is "inaccessible to outer scope" because it is, as we will see. Outside of that scope we will try to output it, the InsideVar variable, to a Debug.Log() IntelliSense doesn't even work which again is a hint that this is not going to work. Try to build it, doesn't work. See the name InsideVar does not exist in the current context, line 23. Just to demonstrate that we can output the outside variable, OutsideVar, by building again. Alright, no new errors because OutsideVar is -- the Debug.Log() is in the same scope of which the OutsideVar is declared. In the end, this is important because you will want to determine if and when variables are declared first. If they are needed in a broader scope, then make sure you first declare it from the outermost scope it needs to be able to be referenced from.

Normally you would declare variables with the broadest scope in mind and only declare variables with the narrower scopes if they are needed only temporarily, for a specific task, for example. This brings us back to our class container which is our outermost container that we see right now. Because you may have noticed that we created our method definitions at the class level. The scope being directly within the class container are where our methods are being defined. As we have seen one method definition can reach out from within its scope and call a method that belongs to the outer scope. Let me just delete this. Actually no, we will just leave that and I will go back to Conditionals, for example. The Start() method definition is able to call the stateHandler(), it is able to reach to its containment level, which is the class level, and reference the other methods at the same scope, at the class level. This is the same behavior that we just described with variables. The logical question is, "Can we declare variables at the class level" to get the same kind of functionality" so that all of the methods can reach out" and reference those variables declared outside of the method scope?" The answer is yes, absolutely. You maybe remember back in our first few videos we start off by doing just that. It is actually crucial that this is the case that I will explain in a moment. This whole time we looked at method definitions as the members of our classes. When you have a variable that is a member of your class within the immediate scope of the class, that variable is a special kind of variable called a field. It still functions under the same rules that we've seen with other variables thus far, no need to wring your hands and worry about getting fields straight... Is this a field, is it a variable? It doesn't really matter, they are all the same in terms of how they work. It is just the scope that they are declared in that is the difference.

When you have a variable at the class level it's called a field, otherwise if it is declared first at the method level it is just an ordinary variable. As we will soon see, this is actually a big difference in how we can make use of those variables, again in a broader scope. For now, just know of that when you have a field, a class-level, class-scope variable, it can be referenced by the methods within that class. Let's see very quickly how this can be useful to us. Let's forget about this class here and let's create a new class. Let's go back to Unity, create C# Script and we will call this FieldTest. We will just tidy it up and get it to our liking first then let's be sure to attach it to our game object. We are just going to get an error with this code so let's comment that out first before we move forward. With FieldTest, let's make sure to attach that to our test game object as we did before. Turning off scope and putting FieldTest there. Here let's create our field, our class level variable. Call it Message. "Hello," harkening back to our hello world beginning. That is a field because it is immediately within the scope of the class. It is a class level variable. In Start() Let's not write anything just yet. Let's actually create a custom method, and we will want to output something and write this: Message = Message + “World.” Here in Start() type in Debug.Log(Message) Then let's run that right now so you can see that this is working. There you go, Hello is our output.

Nothing too fancy and interesting yet but now let's do this. Let's call our method which itself references our Message string within it, so just call the method like that then we will run that method. And now we will output Message again after that method runs. There we go, we get Hello first and then Hello World. The contents of Message which say Hello stay alive in between the method calls. To make this obvious, I will just run the method again and output to the Debug.Log() once again so you can see that the contents are kept between method calls. There you go, see? It retains Hello World from our first method call and after the second method call, it post-fixes another World to our variable Message, our field variable Message. You see here that reference from Message stays alive between method calls. Normally, our variables that are first declared within our methods are only alive until that method completes executing. The method then resets at the end of the execution and all of the variables originally declared in it do so, as well. The compiler essentially removes those variables from memory space and starts over again if you run the method again. Variables declaring methods are kind of like groundhog day, repeating the same thing every time.

You can see how declaring variables at the class level, fields, can be very useful to keeping certain values alive between method calls. This is extremely useful for a lot of reasons. One of the most obvious reasons being that, related to game design, is imagine you have a field for your character. Let's say his health meter, which could be an int, let's say you want to run a method on it that deals out damage depending on certain circumstances. If you declare the character's variable holding his health points within that method that deals the damage, his health will always be reset whenever you deal damage but that is not how games work. You want to subtract from those health points without caring how much those health points are. Let me quickly illustrate how this could be useful. Let's create a new script called DamageTest. Let's create first a field that holds our health points, let's create an int and call it HealthPoints. Let's arbitrarily assign 100 to it and let's make a custom method that deals damage, call it DxealDamage(). Makes sense. Let's simply take those HealthPoints -= ... Remember minus equals is just the same thing as saying HealthPoints = HealthPoints minus whatever the values on the right. And we will just say five to make things simple. Here we will actually call DealDamage(). So we can see the result, we will simply output to the Debug.Log() as usual.

Our simulated HealthPoints for our mock video game example. I will save that and in Unity ... I don't believe I attached it, so let's attach it first and disable FieldTest. Go to console and see what we get as an output. 95, right? If we do this again, if we DealDamage() again, we will get the functionality we want where it just takes off 5 points every single time we DealDamage() and we don't have to worry about what value our HealthPoints are at. If we made the variable first declared, not as a field, but in DealDamage(), just move it simply to our DealDamage() method... I am just going to comment that out so that we are simulating not having a field anymore. Already, we are getting these squiggly lines because we are not going to be able to reference healthPoints. Remember back to our demonstration in our previous video. What I will do is just simply put the Debug.Log() into our DealDamage() method so that it is at least correctly scoped. I didn't remember to declare that as an int, which it of course is. I will see what happens. We are calling the method DealDamage() twice, let's see what the output is. 95 and 95, see? That is because HealthPoints in this context is no longer a field. It doesn't stay alive in between these method calls.

After this method is called, the first one, it resets and we call the method again and it is the same, HealthPoints resets, and it is the same value it was before we called deal damage the first time. Groundhog Day, as I mentioned. I am just going to change this back to how it should be. Undo all my changes here. There we go. Hopefully it will make a lot of sense when making games when and if to decide on using fields versus locally-scoped method variables. If that variable represents some kind of essential value in the context of that class, something that is important to the state of that class, that you expect the update engine will want to keep alive in between update calls, for example. That would be a good candidate for a field. Besides all of this, there is actually another reason to create fields, and that is to potentially allow them to be accessible to other classes. It may not seem like it, but this directly leads us into one of the most exciting and interesting aspects of C# and that is object-oriented programming. A big key to understanding unity and C# is unpacking this object-oriented model. It is really fascinating stuff and we will now be setting out to do exactly that. Really interesting stuff right ahead, hope to see you in the next few videos.


Related Articles in this Tutorial:

Lesson 1 - Who This Course is For

Lesson 2 - What to Expect from this Course

Lesson 3 - Installation and Getting Started

Lesson 4 - Starting the First Project

Lesson 5 - Prototype Workflow

Lesson 6 - Basic Code Review

Lesson 7 - Game Loop Primer

Lesson 8 - Prototyping Continued

Lesson 9 - C# Fundamentals and Hello World

Lesson 10 - Variables and Operations

Lesson 11 - Variables and Operations Continued

Lesson 12 - Floats, Bools and Casting

Lesson 13 - If Statement Conditionals

Lesson 14 - If Statements Continued

Lesson 15 - Complex Evaluations and States

Lesson 16 - Code Syntax vs. Style

Lesson 17 - Variable Scope

Lesson 18 - Object-Oriented Programming Intro

Lesson 19 - OOP, Access Modifiers, Instantiation

Lesson 20 - Object Containment and Method Returns

Lesson 21 - "Has-A" Object Containment

Lesson 22 - "Is-A" Inheritance Containment

Lesson 23 - Static Fields and Methods

Lesson 24 - Method Inputs and Returns

Lesson 25 - Reference vs. Value Types

Lesson 26 - Introduction to Polymorphism

Lesson 27 - Navigating the Unity API

Lesson 28 - Applying What You've Learned and Refactoring

Lesson 29 - Constructors, Local Variables in the Update Method

Lesson 30 - Collecting Collectibles, Items and Powerups

Lesson 31 - Spawning and Managing Prefab Powerups

Lesson 32 - Implementing Powerup State Logic

Lesson 33 - Displaying Text, OnGUI, Method Overloading

Lesson 34 - Referencing Instantiated GameObjects, Parenting

Lesson 35 - Understanding the Lerp Method

Lesson 36 - Creating Pseudo Animations in Code

Lesson 37 - Understanding Generic Classes and Methods

Lesson 38 - Animations Using SpriteSheets and Animator

Lesson 39 - Working with Arrays and Loops

Lesson 40 - Debugging Unity Projects with Visual Studio

Lesson 41 - Camera Movement and LateUpdate

Lesson 42 - Playing Audio Clips

Lesson 43 - Routing Audio, Mixers and Effects

Lesson 44 - Adding Scoring Mechanics and Enhancements

Lesson 45 - Scene Loading and Game Over Manager

Lesson 46 - Understanding Properties

Lesson 47 - Controller Mapping and Input Manager

Lesson 48 - Understanding Enums

Lesson 49 - Dealing with Null References

Lesson 50 - Handling Variable Framerates with time.DeltaTime

Lesson 51 - Preparing the Project for Final Build

Lesson 52 - Final Build and Project Settings

Lesson 53 - Introduction to the Unity Physics Engine

Lesson 54 - Understanding FixedUpdate vs. Update

Lesson 55 - Movement Using Physics

Lesson 56 - Attack Script and Collision Events with OnCollisionEnter2D

Lesson 57 - Projectiles and Stomping Attack

Lesson 58 - Parallax Background and Scrolling Camera

Lesson 59 - Infinitely Tiling Background Sprites

Lesson 60 - OOP Enemy Classes

Lesson 61 - OOP Enemy Classes Continued

Lesson 62 - Trigger Colliders and Causing Damage

Lesson 63 - Multi-Dimensional Arrays and Procedural Platforms

Lesson 64 - Finishing Touches

Lesson 65 - Series Wrap


Comments

Please login or register to add a comment