Accessing variables and communicating between scripts

One of the most common tasks in Unity is to access variables on another script or component.

Remember that a component in Unity is nothing more than a piece of code put into a class with a nice name such as Transform or BoxCollider.

Here’s an overview of different ways to read information and how to communicate between different scripts:

  1. Private variables and scope
  2. Public variables and accessors
  3. GetComponent()
  4. Static variables
  5. Messaging

Private variables and scope

At the most basic level we access information by reading private variables within a single class.

Private variables are also called member variables and are only accessible from within the class they are declared in. Their scope is limited to the containing code block, which, among others, can be a class, function or if-statement. Whenever a variable is declared in between curly braces you can only see and read from it within the same code block or nested blocks.

See code example abovehealth can be read from within the Start() method, because it was declared within the Player class which is a parent element to the Start() method. Generally, you can only read variables, which are declared at the same or a higher level than that of which your call or assignment originates, but not the other way around.

Think of a variable declaration as a “parent” and calls to that variable as “children”. The children must be contained within the parent code block.

Here is an example of a variable that is inaccessible due to scope:

The variable count is declared within a for-loop which is a code block and only exists in between the following curly braces. The print-call happens outside of that code block, it is at a higher level and therefore can’t access the variable.

A variable must be declared before it is used. When declaring  a variable on the class level, the line number on which the declaration happens is irrelevant to the compiler.

Here is an example for a variable declaration which is limited to the scope of the Start() method:

Important: A variable that is declared on a lower level than the class level (meaning: within a method), is always private and can not have any other access operator applied. Therefore, you do not need and can’t use the private keyword.

Tip: Keep in mind, that on the class level, the private accessor is implicit in C#. You may omit the keyword and the variable will still be private by default. However, many consider it best practice to always declare accessors explicitly.

Recap:

Private variables have a scope in which they are accessible; they are members of a class or other code block.

Public variables and accessors

Variables, methods and even classes are declared with a certain protection level, which limits the element’s scope in a specific way. The most common way to define scope is by the use of the private or public accessor.

Declaring a variable or method as public makes it accessible from other scripts. There is, however, a strict rule to follow when attempting to read variables from another class:

Scripts can communicate with each other via public variables if one class has a reference to the other class instance.

Here is a script example of our Player class:

Note that both the class and the variable health have the public accessor applied, which makes them visible to other scripts.

On line 5 you find a variable of the type Player. This will be used to store a reference to the Player class instance. On line 9 this variable is assigned the current player script, which lies on the same GameObject. (See the next section for more information; just know that GetComponent() returns an instance of the class we’re trying to find.)

Reminder: A class is only a template that tells the computer how to build an object. The object is created by constructing an instance from the class. (Unity does this for us automatically when we attach a MonoBehaviour script to a GameObject and press Play.) A reference tells the program where to find a specific object instance in memory.

When our Player variable holds the correct instance/object (meaning that it is not null), we can access all public variables within it by using the dot-notation:

The same works for functions:

Check out MonoDevelop Basics if you need to catch up on how to use auto-completion and other tools for convenient programming.

Remember, to access a class, method or variable from another script, they need to be declared as public. Next, the calling script needs a reference to the other class instance. Finally, you can access a member or method via dot-notation.

GetComponent()

GetComponent() is a Unity method from the MonoBehaviour class that is used to access data on other components. To fully understand how this method works, you have to wrap your head around a few different concepts.

Let’s attach our player script to the Main Camera GameObject in the Unity hierarchy and tell it to find the Camera component:

First, you declare a variable of type Camera to hold a reference to the Camera Component which you are going to find on the same GameObject. Next, you call GetComponent() in it’s generic version on the type Camera, which will return the first object of type Camera that is found.

Above is the “standard” version of GetComponent(), which might be easier to understand, because it is slightly more explicit. The method searches for a class that is called Camera by string and then passes the class as the type of Camera so that we can store it in our variable.

The as operator can be used to convert classes (they must be a reference type, so that they are able to return null!). In this case we want to convert whatever object we find that matches our search string, to a Camera object. If this works, because our camera is indeed a camera object, we basically just pass the reference.

If for whatever reason, our camera would not be of type Camera the method would return null, instead of throwing an error, which is convenient in this case, because now we can search for objects that only might exist.

Because variables which have the value null can cause Null-Reference-Exceptions, it is very common to perform a null-check before accessing such variables.

If you were to access our mainCamera variable without any null-check, Unity might throw an exception, if the variable doesn’t hold a Camera object.

Because searching by string is a relatively costly operation for computers, the generic version GetComponent<type>() is preferable. Nevertheless, GetComponent() is still a costly operation itself, so even in it’s generic version, it should be called as rarely as possible. Use it for caching variables once in Start() or Awake()!

Here’s a more complete example:

Note, that both the Player script and the Camera component are on the same GameObject in this case. (Just for testing purposes, this is not a practical example!)

In Awake() we find a reference to the camera. In Update() we adjust the camera’s field of view, but only if the reference exists.

How does GetComponent() know where to search for objects?

Until now, GetComponent() returned a component on the same GameObject, but you might be guessing that we will use it to find scripts on other GameObjects, too, so how does this work?

Consider this script for an enemy object:

In this example, we want to access multiple components on a different GameObject. Before anything can work, we will need to find a reference to the other GameObject. Once we have found an object, we can perform a GetComponent() search on it, by using the dot-notation. There are multiple ways to find references in Unity C#:

1. Assigning a reference through a public variable in the editor

When you declare a public variable in the Enemy script on the enemy GameObject, Unity will automatically create a field in the inspector, which you can use to assign a reference by visually dragging an object onto the field or selecting one from the dot-picker to the right of the field.

Unassigned reference for a public variable in the inspector
Unassigned reference for a public variable.

 

public-variable-reference_filled
Public variable with assigned reference.

Assigning references through public inspector fields is very easy to understand, but it also requires a null-check, because it is quite possible to forget to populate the inspector field. During development there are also a few situations, which might break such a manually established connection, especially when working with Prefabs in Unity. Still, it’s a very design-friendly solution to quickly hook up things.

Best usage: Use public inspector fields, when you want to give a designer the control over which reference is assigned. For example a Camera-Follow script that takes a public variable Target and follows any object that is assigned through the inspector.

Public inspector variable with unassigned Transform.
Public inspector variable with unassigned Transform.

Note, that inspector fields show us which type a variable/field expects. When you drag a GameObject onto a field that says “None (Transform)” the editor will automatically assign the Transform component of the object.

2. GameObject.Find()

This Unity method returns the first active GameObject that matches a given string parameter. The search is performed on the entire scene.

Recommendation: Be cautious when using this method, because it is slow! If you must use it, then only call it once in Awake() or Start().

Active GameObject in inspector.
Active GameObject in inspector.

Be aware of the fact, that GameObject.Find() only returns active objects. Also, if you rename your objects during development, you will have to adjust your scripts as well, which might become tedious, since Game Designers like to rename things to make them look pretty in the hierarchy…

3. GameObject.FindWithTag()

A better alternative is to search for objects by Unity Tag. First, assign a tag to your GameObject via the Inspector:

Inspector Tag on GameObject
Inspector Tag on GameObject.
Add Tag to GameObject in Inspector
Add Tag to GameObject in Inspector.

Add Tag… will take you straight to Unity’s Tags and Layers editor, which you can also find at Edit > Project Settings > Tags and Layers. Here you can create a custom tag, which will then appear next to the default one’s in the Tag list. Once our player is tagged with the Player tag, we can easily find a reference by using GameObject.FindWithTag():

GameObject.FindWithTag() is not the fastest solution, but a practical one.

You can use this method for individual objects like a player or a game manager. You should only call it once and cache the result in a variable. Performing the search on a hundred objects tagged Enemy might be slow, so reconsider your options when dealing with many entities.

Would you like to use auto-completion for your tags? Here’s another trick when working with strings as parameters: Strings, Tags and Layers

4. Transform.GetChild()

If your GameObjects have a fixed relationship in the hierarchy, you can find them by index with Transform.GetChild().

Note, that the Unity hierarchy is built with Transforms. So when traversing the hierarchy to find another object, you are going to use the Transform class.

Hierarchy Transforms
Hierarchy Transforms

As you can tell from the inspector, all elements in the hierarchy include a Transform component, which can be accessed by an index starting at zero. When trying to find the first child object in a parent-child relationship, we search for index 0.

Now that you have found a reference to a GameObject and stored it in a local variable, you can access it via the dot-notation and use GetComponent() to pick a specific component.

So let’s put it all together:

Here’s another trick, how you can visually check if a private variable has been populated or not.

Recap:

  1. Declare variables for components you wish to store in your class (line 5 – 6).
  2. Find a reference to the GameObject on which those components live (line 10). You also declare playerGO locally because you won’t be needing it any longer, once the variables for playerScript and playerTransform are assigned.
  3. Don’t forget a null-check to prevent errors!
  4. Assign the final variables by using GetComponent().

It is true, that there’s a plethora of ways to find references and store them. Remember, it is ok to just go ahead with the first solution, that comes to mind and then improve it later!

Generally, try to get to your data in as few steps as possible. For example, if you’re looking for the weapon’s Transform, consider traversing the hierarchy from the player to the child objects, instead of looking for a GameObject first and then doing a GetComponent() to find the Transform. Save on method calls and dot-notation operations!

Addendum:  We’ve learned that GetComponent() performs a search for a component type/class on a GameObject by using the dot-notation.

By now, you should know exactly what’s going on when you see the above example. We’re looking for the playerPosition by performing a GetComponent() on our player reference and then going one level deeper into the Transform position.

If you’re on top of things, you might wonder:  “Why use GetComponent() in this case, when there clearly is an easier way…”

True, you might as well do this:

Let’s clear things up: transform is a Unity property that does a GetComponent() call with <Transform>. Read more here.

We start to get a grip of the idea, that GetComponent() is always called on some kind of object via the dot-notation, but why are we allowed to do this:

GetComponent() always needs to know on which object to perform a search and here it seems as if this information was missing. However, the above line 9 is basically a shorthand for:

MonoBehaviour offers us another built-in property called gameObject, which will return the GameObject to which our script is attached. Therefore, gameObject and transform are also calls to the GetComponent() function. Read this if you’re interested in knowing why you shouldn’t use those properties in your Update() loop.

Static variables

Now comes a part, in which the humble beginner might think: “well this is the easiest method of accessing variables across scripts, why not use this one first place?” To which I will reply: “static variables are great, but there’s much to learn before one can safely use them!”

A static variable lives on a level with the class template, not with the individual instances of a class. It is a single entity which is shared by all class instances.

We’ve learned, that Unity creates instances of our classes when we attach them to GameObjects and start the game. So if we create multiple enemies, all of which have an Enemy script attached, which stores health, each enemy has their own instance of the health variable. Try it out!

  1. Create a GameObject called Enemy.
  2. Attach the Enemy script to it.
  3. Duplicate the enemy a few times.
  4. Assign each enemy a different health through the public field in the inspector.
  5. Press play and watch the console output different health values for each enemy.

Nothing new here, so let’s add in a static variable:

When you look at the inspector, you will find that Unity doesn’t allow us to set static variables through the editor. (It would be rather confusing, if it were possible, anyway…)

Instead, we increment the count variable whenever an enemy script is started by Unity. Since every enemy is created only once per session, our static count will tell us how many enemies we’ve created.

In short: We’ve created multiple instances of the Enemy class by duplicating the GameObjects in the Editor. Every instance has their own set of private and public variables, but all of them share a single static variable called count.

To avoid confusion: the static keyword is not an accessor, it is meant to create a single variable that lives among your class object.

Let’s make it even clearer and create a new testing script on our Main Camera:

Active camera with Test script.
Active camera with Test script.

Then deactivate our enemies in the inspector. Turning off GameObjects in Unity sets them inactive. On a component level this is called enabled or disabled.

Enemies inactive in hierarchy.
Enemies inactive in hierarchy.

Press play, watch the console and start to activate one enemy after the other by clicking the checkboxes for each GameObject. You will see how the count variable is incremented with every new enemy that comes alive.

The count variable increments.
The count variable increments.

Now, while the Unity player is running, select your enemies and delete them from the hierarchy. The count variable will still be printed to the console, because it still exists, although all of the enemy class instances were deleted.  If you’re window doesn’t show the print output in four compact statements, hit Collapse at the top row of the console tab.

Further reading on counting enemies: If you actually want to use the count variable for something, you probably want it to tell us the amount of enemies that are currently alive. A typical way of doing this:

The OnEnable() and OnDisable() methods are built-in MonoBehaviours just like Start(). They get called whenever the script is turned on or off. This is true, when enabling the script itself, as well as when deactivating the GameObject on which the script resides.

Messaging

I’ve included messaging in this article, because it’s one of the main ways to communicate elegantly between script and you should consider using it in your own projects as soon as you’ve mastered the previous sections.

Because sending messages in Unity is worth it’s own topic, please continue reading here.

Published by

Chris Yarbrough

Game Design student at Mediadesign Hochschule, Munich.

Leave a Reply