How to get a reference

Published 3 years ago Updated 2 years ago


“How do I access a variable from another script?” is usually one of the first questions we ask when learning Unity. Whether you are a C# beginner or a C# professional, understanding the way Unity does things differently can be confusing.

Why to avoid Find methods

Find methods in Unity are an often-tempting solution to referencing a scene object. However, there is always a better solution.

Whenever you call a Find method, Unity must traverse your entire scene hierarchy and check every single object until it finds a match; and the methods which return arrays will always traverse the entire hierarchy regardless. This is inefficient! It means that the more objects you add to your scene, the slower these methods become; and it gets even worse if you are calling them multiple times.

A non-exhaustive list of Find methods to avoid include:

A note on dependency injection

If you have worked in other applications of C#, you probably understand the concept of dependency injection on some level. Unity makes this very familiar workflow extremely difficult. Due to the nature of how a MonoBehaviour is instantiated, we cannot have a parametered constructor. However, there are a few packages available which mimic it and bring this methodology to Unity. Zenject and Ninject are a couple of popular ones.

However, this post is not about that. This post is about how to get a reference the traditional way in Unity.

Setting things up

Let's say you have a simple behaviour, a GameManager, attached to an object named Scripts. This class might contain a public property such as Score:

using UnityEngine;

internal class GameManager : MonoBehaviour
{
    [field: SerializeField]
    public int Score { get; set; }
}

Now let us say you have a PlayerController behaviour, attached to an object named Player. Let us also assume the premise of your game is that you wish to increment the score whenever the player jumps:

using UnityEngine;

internal class PlayerController : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetButtonDown("Jump"))
        {
            // increment the score somehow
        }
    }
}

We need a way to reference our GameManager so that we can increment the Score property. There are a few ways we can accomplish this.

Direct reference through a serialized field

The first - and best - way to access another behaviour instance is to already have a direct reference to it. This can be achieved by creating a field of the behaviour type that we want to access like so:

using UnityEngine;

internal class PlayerController : MonoBehaviour
{
    <mark>[SerializeField] private GameManager _myGameManager;</mark>

    private void Update()
    {
        if (Input.GetButtonDown("Jump"))
        {
            <mark>_myGameManager.Score++;</mark>
        }
    }
}

We can assign a GameManager by simply dragging our Scripts object (the one which has the GameManager attached) onto this slot in the inspector! Easy-peasy.

I can't assign via the inspector. What if my object is a prefab?

So your object is a prefab which means you can't simply drag a scene object into the slot in the inspector. So how do we workaround this? By using property injection!

Let's go back to our PlayerController and change our private field into a public property:

using UnityEngine;

internal class PlayerController : MonoBehaviour
{
    <mark>[field: SerializeField] // I will cover this at a later time</mark>
    <mark>public GameManager MyGameManager { get; set; }</mark>

    private void Update()
    {
        if (Input.GetButtonDown("Jump"))
        {
            <mark>MyGameManager.Score++;</mark>
        }
    }
}

Now when we instantiate our prefab, we set the public property.

using UnityEngine;

internal class Spawner : MonoBehaviour
{
    [SerializeField] private GameManager _myGameManager; // we assign THIS through the inspector instead
    [SerializeField] private PlayerController _playerPrefab;

    public void Spawn()
    {
        var clone = Instantiate(_playerPrefab);
        clone.MyGameManager = _myGameManager;
    }
}

That's it! That's all there is to it.

Unity's unique approaches to doing very normal things is always confusing, even for experienced devs. But that doesn't mean we can't work around it.