Chapter 9
Scope and Namespaces

This chapter is sort of a break. Just some rules about different ways and places to declare variables. We’ll use them a little, and every programmer needs to know them, eventually. But they won’t hurt your head as much as those if examples.

9.1 Scope

So far, we’ve been declaring variables in two places: inside of Start and Update, or outside of them (the Inspector variable trick.) Both ways are legal, but they have different rules for how long the variables live and who can see them.

Variables declared outside Start or Update are called global. Global variables live for the entire program, and everyone can see and use them.

Variable declared inside Start or inside Update are local. Local variables can only be used in the part where they were declared. When that part finishes, local variables are destroyed. A local variable in Update is created and destroyed each time Update runs.

The technical term for where a variable exists is scope. The scope of global variables is the entire program. The scope of local variables is inside the {}’s where they were declared. Being destroyed by leaving the {}’s is called going out of scope.

Here’s a small example of local variables not existing outside where they were made. n is declared in Start, so doesn’t exist in Update:

void Start() {  
  string n = "Nif";  
  print("n="+n); // n=Nif  
}  
 
void Update() {  
  print("update n="+n); // ERROR -- no such variable n  
}

Local variables also don’t “use up” the name outside their scope. This is a good thing. It means we can declare one n in Start, and a different n in Update:

void Start() {  
  string n = "Nif"; print("n="+n);  
}  
void Update() {  
  int n=5; // legal. We can reuse the name  
  print(n*2); // 10;  
}

This is why local variables were invented. Imagine you have a very large program, with Start, Update and piles of other things like it. When you declare local variables in one section, you never have to worry about whether someone also declared them in some other section.

You could declare everything as global. But programs are usually easier to read if you use global only for things you need to keep around between Updates.

Here’s an example using move-and-wrap-around. It snaps y lower as x crosses thresholds. x has to be global, since it’s remembering where I am. But y is better as a local, since I recompute it each time:

public float x=-7; // <-global  
 
void Update() {  
  x+=0.1f;  
  if(x>7) x=-7;  
 
  // compute y based on x. 1.5, 0 or -1.5 as we move:  
  float y; // <-local  
  if(x<-2) y=1.5f;  
  else if(x<2) y=0;  
  else y=-1.5f;  
 
  transform.position = new Vector3(x,y,0);  
}

y is really just a temporary, used to help place the Cube at the correct height. Declaring it as a local, right before we use it, makes it easier to see that.

9.1.1 More global rules

You’re allowed to declare globals anywhere in the big curly-braces. It doesn’t have to be at the top. They all get declared first – the compiler scans for global as an early step. The rule about needing to declare a variable before you use it is real, but it’s only for local variables.

Just to show it can be done, this silly program declares n1, n2 and n3 in funny but legal places:

int n1;  
 
void Start() {  
  n1=1; n2=7; n3=45; // legal to use n2 and n3  
}  
 
int n2; // <- also a legal place to declare a global  
 
void Update() {  
  n3++; // this counts as declared  
}  
 
int n3; // a legal place to declare a global

Most people declare them at the top. But I sometimes declare a global just before the part that uses it – like right before Update.

Friendly languages auto-initialize all variables: numbers to 0 and strings to "". Languages that want to run faster never auto-initialize. C# splits the difference – it auto-initializes global variables, but not locals. This can be confusing:

 int n1; // global, this is 0  
 
 void Start() {  
   int n2; // local, not initialized  
   n1++; // legal, increases it from 0 to 1  
   n2++; // error  
 }  
 

There’s a special rule allowing declare-and-init for global vars. It’s a special rule since global variables aren’t run like regular lines – they’re magically declared all at once. This means you can’t give them values based on other globals:

float wide=10; // fine  
float tall=wide*1.5f; // error -- wide doesn’t exist yet  
 
 void Start() {  
   tall=wide*1.5f; // set complicated globals here  
 }  
 

A traditional program set every global’s value as the first thing it does. In Unity, that would be in Start.

9.1.2 block scope

You’re allowed to declare variables inside of an if/else {}. They’re local to that little area and go away after it’s done. That’s called block scope since the thing the {}’s make is officially called a block.

The idea is the same as global vs. local – keep variables from cluttering things up by getting rid of them when you’re done. Here if I have more cats than dogs I’ll temporarily compute extraCats. It’s gone when the {} block ends, which is what we want:

  if(cats>dogs) {  
    int extraCats=cats-dogs;  
    if(extraCats>2) { ... }  
    cost = extraCats*3;  
    ...  
  }  
else {  
  int extraDogs=dogs-cats;  
  ...  
}

It works in either place. extraDogs only lives inside of the else brackets.

A short, real use is for a variable swap. We need an extra variable to make the switch, but not afterwards:

  if(x>y) {  
    int tmp=x;  
    x=y;  
    y=tmp;  
  }  
  // tmp no longer exists

It looks nice to declare tmp exactly when we need it, and it won’t interfere with the rest of our program, since it’s deleted right away.

9.1.3 Common errors

Sometimes when you want to change a global, you accidentally redeclare it, like this:

int cats;  
 
void Start() {  
  int cats=10; // Oppss! But not an error. Hides the global cats  
  // cats=10; // We meant to do this  
  cats += 5; // even this is messed up -- changes the local cats  
}

This isn’t a red-dot error – it’s a much worse non-error error. The warning is local cats shadows global cats.

We now have a local cats which hides the global one we meant to use. That code sets local cats to 15. But when Start ends local cats is thrown away. Global cats stayed 0 the whole time.

Whenever you use a variable, just think “Am I changing an existing global, or making a new local variable?”

A similar error can happen with block scope. This accidentally makes food only exist inside the if:

  if(ani=="cat") string food="mouse";  
  else string food="canned";  
 
  print(ani+" eats "+food); // error -- no variable named food

We have to declare it outside the if:

  string food;  
  if(ani=="cat") food="mouse";  
  else food="canned";

9.2 Namespaces

Most systems have lots and lots of built-in globals. A standard computer trick is to group them into things like folders, called namespaces.

For example, Unity has global variables for the screen’s height and width, which way a mobile device is being held, and so on. These are all in a namespace which they named Screen.

The rule for looking inside a namespace is to use a dot. If you type Screen-dot (screen and then a period) the pop-up will show the options. Screen.width is the width, in pixels, of the window. It’s really the width variable in the Screen namespace.

For fun, you can test by having Update copy Screen.width into an Inspector global. While running this, you can resize your window and watch ht change:

public float ht;  
void Update() { ht=Screen.width; }

You might notice that the pop-up for Screen says public sealed class, and not namespace. namespace is just the general term. Some languages use the actual word namespace in the language. Others use the term package. C# uses a few different ways. But no matter what, they all work like folder.variable.

Here are more namespaces and stuff in them, just for fun examples. You won’t need to know them:

Time.time is one of my favorite examples of how namespaces work. The Time namespace holds a lot of things about time and timing things, so Time is a good name for it. The most commonly used thing in it is how many seconds the game has been running. That variable should have an easy, short name, so we picked time.

Together, it’s the time variable in the Time namespace – Time.time.

9.2.1 Notes/rules

You can use the same variable in different namespaces. That’s really a version of the scope rules. That’s why System.Math.PI and Mathf.PI are both legal. You could have Screen.height and Raccoon.height and also name one of your variables height.

In the editor, typing the dot triggers the pop-up. Suppose you type Screen.wi, click away, come back, and the pop-up is gone. To get it back, delete down to just Screen and retype the dot.

Later on, C# re-uses the dot in a different way. When we come to it, I’ll write this again and explain it. I want to sort of pre-warn you about it, since it’s one of those things that can be really confusing.

The using’s at the top of the program are about namespaces. You don’t need to know this – only keep reading if those two lines at the top of every program are bugging you. UnityEngine is the master namespace for all the Unity built-ins. Screen is really inside of UnityEngine. The real width of the screen is UnityEngine.Screen.width.

using UnityEngine; at the top lets you skip it everywhere else. it says: if you can’t find something they typed, try looking for it in UnityEngine. But, again, not vital to know just now.