Callbacks in Unity/C#

What are callbacks and why should I even care?

A callback is defined as an invitation for a second audition or interview…..heh. In programming, callbacks are code that is passed as an argument to other code which is expected to be executed upon fulfilling some conditions. Consider the example below.

  • A multiplayer mobile game where a device is either a server or a client
  • There is a single communications class on all devices that manages that state information of objects in the game. If the device is the server, it will use this class to send updates to other client devices. The client will use the communications class to send what actions its performed back to the server to process.

In this scenario, both server and client will want to run some code whenever the communications class updates its state. One solution is to simply have the components regularly poll the communications class but as you can imagine, this is wildly inefficient. Another solution would be to simply inject server and client code into the communications class and run those whenever the communications class updates. The problem with this approach is that everytime the communications class updates, it must check whether the device is a server or client and then proceed to call only that code. While this approach isn’t wildly inefficient, it does make for uglier code and more tightly coupled code, making maintenance a bit more tricky.

This is where callbacks comes in really handy. With callbacks, the server and client class can simply register its own code to be “called” whenever the communications class updates, making for cleaner code and better decoupling. The rest of this post will show how to build your own callback system with Unity/C# since Unity has its own UnityEvent system that you can utilize.

Method 1: Delegates

In my opinion, delegates are the messiest of all the options I’ve used by my experience with callbacks isn’t extensive. According to TutorialsPoint,

C# delegates are similar to pointers to functions, in C or C++. A delegate is a reference type variable that holds the reference to a method. The reference can be changed at runtime. Delegates are especially used for implementing events and the call-back methods. All delegates are implicitly derived from the System.Delegate class.

Delegates possess a different declaration than usual C# declarations in the form of:

delegate <return type> <delegate-name> <parameter list>

Consider the simple Unity script below.

delegate int ProcessNums(int a, int b);
public class Service : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        //Creating delegate instances
        ProcessNums pn1 = new ProcessNums(AddNumbers);
        ProcessNums pn2 = new ProcessNums(SubtractNumbers);
        //Calling functions
        Debug.Log("AddNumbers: " + pn1(2,2));
        Debug.Log("SubtractNumbers: " + pn2(2,2));
    }

    public int AddNumbers(int a, int b){
        return a + b;
    }
    public int SubtractNumbers(int a, int b){
        return a - b;
    }

}

Note that the delegate blueprint is provided outside the main script. In this example, delegates are used in a similar way to classes. Defining then instantiating objects from it. This particular delegate requires 2 integer inputs and returns an integer result. This means that the callback function must follow this same signature. Delegates are a simple way to implement callback functionality in the form of variables. If multiple callbacks of unknown number need to be called, delegates can be used with lists.

Method 2: Action Delegate

Microsoft defines actions as a delegate that accepts parameters (up to 16) and does not return to value. Consider the code snippet below.

    // Start is called before the first frame update
    void Start()
    {
        //Creating delegate instances
        Action<int,int> pn1 = AddNumbers;
        Action<int,int> pn2 = SubtractNumbers;
        //Calling functions
        pn1(2,2);
        pn2(2,2);
    }

    public void AddNumbers(int a, int b){
        Debug.Log(a + b);
    }
    public void SubtractNumbers(int a, int b){
        Debug.Log(a - b);
    }

Notice from the snippet above that both actions return void as per their definition. This is the biggest difference between actions and delegates. Using actions however does reduce the size of the code as well as makes it more readable. So in development, essentially you should think about using actions first and if unsuitable, use delegates.

Method 3: Unity Events

Unity Events are unity’s specific implementation of callbacks and are quite simple to implement whilst being powerful. The following are the most common statements you may use with Unity Events

//Declares a public unity event variable that can be manipulated from the inspector
public UnityEvent callbacks;

//Adds a callback function
callbacks.AddListener(func);

//Invokes all functions added to unityevent variable
callbacks.Invoke();

The screenshot below shows inspector view of the unity event

UnityEvent variable in inspector

Notice that the UnityEvent appears as a list so you can add multiple callbacks to this single variable. The invoke() method will run all functions added. This makes your codebase alot smaller, moreso since gameobjects can simply be dragged and dropped in the inspector and the function selected to be added as a callback.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.